diff --git a/.gitignore b/.gitignore index 006cab8c27..ba80d79b90 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ tmp dist ts-dist .turbo +.typecheck-profiles **/.serena .serena/ **/.omo diff --git a/.opencode/skills/debug-opencode/SKILL.md b/.opencode/skills/debug-opencode/SKILL.md deleted file mode 100644 index 01e0ca3523..0000000000 --- a/.opencode/skills/debug-opencode/SKILL.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -name: debug-opencode -description: Use when interactively running, debugging, or verifying opencode's own V2 CLI/TUI or server during development in this repo — starting the dev TUI, driving it with termctrl, comparing V2 against the legacy TUI, hitting the V2 server/API directly, reading log files, or attaching Bun's inspector. ---- - -# Debugging opencode itself - -Workflow for interactively exercising the V2 CLI/TUI and server while developing in this repo. All commands below run from `packages/cli` unless noted otherwise. - -## Migration context - -- The TUI is being ported from legacy APIs to the new V2 APIs. New and migrated TUI behavior should use `sdk.client.v2` and the location-scoped data in `packages/tui/src/context/data.tsx` instead of adding dependencies on legacy sync state. -- Preserve established TUI behavior unless the task intentionally changes it. When behavior, copy, keyboard interaction, or layout is unclear, compare the local V2 TUI with the latest released legacy TUI (see "Comparing V2 against the legacy TUI" below) rather than guessing. - -## Server/client model - -opencode V2 is a client/server system, not a single monolithic process: - -- **Server process** runs the Effect HTTP API (`packages/server`) and owns all domain state: sessions, database, plugins, permissions, Location services. It's started by the `serve` command (`packages/cli/src/commands/handlers/serve.ts`). -- **TUI process** is a separate process that runs no application logic itself — it's an HTTP/SSE client of the server via the generated SDK (`createOpencodeClient` / `sdk.client.v2`). -- **Discovery**: CLI processes find the shared server through a JSON registration file at `~/.local/state/opencode/service.json` (or `service-local.json` for the local/dev channel) containing `{id, version, url, pid}`. A separate password file under `~/.config/opencode/service.json` provides HTTP Basic auth. Before reusing a registration, the client calls `GET /health` to confirm the server is alive, authenticated, and version-compatible. -- **Sharing**: because of this registration/health-check dance, many concurrent `opencode`/TUI invocations converge on one shared background daemon rather than each spawning their own. If no compatible healthy daemon is found, a new one is spawned detached (`serve --service`) and registers itself. -- **`bun dev service start|status|stop|restart`** manages this shared background daemon's lifecycle directly — useful when you need to force a fresh server, confirm one is running, or kill a stuck one. -- **Standalone mode** (`--standalone`) opts a single invocation out of the shared daemon: it spawns a private one-off `serve --stdio --port 0` child tied to that invocation's lifetime, with its own random password. Use this to isolate a debugging session from your other running opencode sessions. -- Every log line is tagged `role=server` or `role=cli` and a per-process `run=`, so you can distinguish server-side and client-side activity in one shared log file (see "Logs" below) even when both roles are interleaved from concurrent processes. - -## Starting the dev TUI - -- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI. -- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server. - -## Interactive debugging with termctrl - -- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots. -- Use a dedicated session name and do not reuse or kill an unrelated session. - -```bash -termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev -termctrl wait opencode-v2-dev "Ask anything" --timeout 20000 -termctrl show opencode-v2-dev -``` - -- Wait for visible text before interacting instead of relying on fixed sleeps. Use the text expected from the screen under test, such as `Ask anything` or `Connect a provider`. -- Drive the running TUI with `termctrl send`. Prefix typed input with `text:` and send control keys separately so the interaction matches real terminal input. - -```bash -termctrl send opencode-v2-dev 'text:example prompt' enter -termctrl send opencode-v2-dev ctrl-c -``` - -- Use `termctrl show` after each meaningful interaction and inspect the full visible screen for rendering errors, stale state, error toasts, and unexpected exits. -- Save PNG evidence for every user-visible bug and fix. Do not save text captures; inspect the rendered PNG. Write temporary captures outside the repository unless the artifact is intended to be committed. - -```bash -termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2-tui.png -``` - -- For resize-sensitive changes, resize the viewport, wait for the expected content, and capture the screen again: - -```bash -termctrl resize opencode-v2-dev --cols 100 --rows 30 -termctrl show opencode-v2-dev -``` - -- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change. -- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`. -- Always clean up the Terminal Control session when the check is complete: - -```bash -termctrl stop opencode-v2-dev -``` - -## Comparing V2 against the legacy TUI - -Run both versions in separate Terminal Control sessions and save PNG-only captures at equivalent states: - -```bash -# From packages/cli: local V2 TUI -termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev - -# Released legacy TUI behavior reference -termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest - -termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2.png -termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png -``` - -- Use the same viewport and send equivalent inputs to both sessions before comparing screenshots. The released CLI is a behavioral reference, not a source of V2 API design; keep the local implementation on V2 endpoints. -- Stop both sessions after comparison: `termctrl stop opencode-v2-dev` and `termctrl stop opencode-legacy`. - -## Server/API debugging - -- Use `bun dev api --help` from `packages/cli` to inspect the API debugging command. It sends one request to the V2 server using the same daemon discovery/auth path as the CLI. -- Use `bun dev api` to introspect the server-side data backing the TUI. This is useful when debugging UI bugs: compare what the screen renders with the raw session, message, event, agent, or health data returned by the API to determine whether the bug is in the server state, the client data layer, or the TUI rendering. -- `bun dev api` accepts either an OpenAPI operation ID or a raw HTTP method plus path: - -```bash -bun dev api get /health -bun dev api get /openapi.json -bun dev api --param key=value -``` - -- Pass JSON request bodies with `--data`/`-d`; the command sets `content-type: application/json` automatically unless you provide a header. Add extra headers with `--header`/`-H name:value`. -- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control. -- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters. - -## Auditing installed `opencode2` sessions - -Installed next-channel sessions normally use `~/.local/share/opencode/opencode-next.db` and `~/.local/share/opencode/log/opencode.log`; `OPENCODE_DB` can override the database. Before calling `opencode2 api`, inspect `~/.local/state/opencode/service.json` because the command may start a daemon when none is healthy. - -For a supplied `ses_...` ID, compare three sources: - -- `opencode2 api get /api/session/active` and the Session/message endpoints for live server state. -- The database's ordered `event` rows for durable history. -- `packages/tui/src/context/data.tsx` and the relevant route for client projection and rendering. - -Locate an uncertain database without modifying it: - -```bash -SESSION=ses_... -for db in ~/.local/share/opencode/*.db; do - sqlite3 "file:$db?mode=ro" "select 1 from session where id='$SESSION' limit 1" 2>/dev/null | grep -q 1 && printf '%s\n' "$db" -done -``` - -## Logs - -- Log files live under `~/.local/share/opencode/log/`. In a local/dev checkout the active file is `opencode-local.log`; `opencode.log` is used for non-local (released) channel installs. Both are append-only, shared across every CLI and server process on the machine. -- Each line is structured `key=value` text: `timestamp`, `level`, `run=` (per-process run ID), `message`, and a `role=cli` or `role=server` tag. Use `run=` to isolate one process's activity and `role=` to separate client-side from server-side log lines, since a shared daemon interleaves many processes' output in one file. -- Tail the live file while reproducing an issue instead of guessing from stale output: - -```bash -tail -f ~/.local/share/opencode/log/opencode-local.log -``` - -- Filter to one run or role when the file is noisy: - -```bash -grep 'run=8fc3b1d5' ~/.local/share/opencode/log/opencode-local.log -grep 'role=server' ~/.local/share/opencode/log/opencode-local.log -``` - -- `OPENCODE_LOG_LEVEL` controls verbosity (default `INFO`); set it before starting `bun dev` or `serve` to get `DEBUG` output for a specific repro. -- `OPENCODE_PRINT_LOGS=1` additionally tees log output to stderr of the process that emitted it, which is useful when a process fails before you'd think to check the shared log file. -- `termctrl logs ` surfaces stdout/stderr for a Terminal Control session specifically (e.g. inspector output or startup failures before the TUI renderer starts) — use the log file above for anything emitted by a separate server/daemon process instead. - -## Debugger - -- To debug the V2 CLI or TUI with Bun's inspector, launch the CLI entrypoint through Terminal Control with an inspector URL, then attach a debugger to that URL: - -```bash -termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \ - bun run --inspect=ws://localhost:6499/ src/index.ts -``` - -- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches. -- Use `termctrl logs opencode-v2-debug` for inspector output or startup failures emitted before the TUI renderer starts. Use `termctrl show` for the visible full-screen TUI. - -## Verification - -- Run `bun typecheck` from `packages/cli` after CLI adapter changes. -- Run `bun typecheck` and `bun test` from `packages/tui` after shared TUI changes. Do not run tests from the repository root. -- Treat automated checks and Terminal Control smoke tests as complementary. For user-visible changes, verify initial render, the changed interaction, Ctrl-C exit behavior, and save a screenshot of the corrected state. diff --git a/bun.lock b/bun.lock index fd8ba59e27..4e7e1f72e0 100644 --- a/bun.lock +++ b/bun.lock @@ -442,6 +442,12 @@ "@parcel/watcher-win32-x64": "2.5.1", }, }, + "packages/docs": { + "name": "@opencode-ai/docs", + "devDependencies": { + "mint": "4.2.666", + }, + }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", "version": "1.17.14", @@ -1259,6 +1265,8 @@ "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], @@ -1267,6 +1275,10 @@ "@anycable/core": ["@anycable/core@0.9.2", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="], + "@ark/schema": ["@ark/schema@0.55.0", "", { "dependencies": { "@ark/util": "0.55.0" } }, "sha512-IlSIc0FmLKTDGr4I/FzNHauMn0MADA6bCjT1wauu4k6MyxhC1R9gz0olNpIRvK7lGGDwtc/VO0RUDNvVQW5WFg=="], + + "@ark/util": ["@ark/util@0.55.0", "", {}, "sha512-aWFNK7aqSvqFtVsl1xmbTjGbg91uqtJV7Za76YGNEwIO4qLjMfyY8flmmbhooYMuqPCO2jyxu8hve943D+w3bA=="], + "@ast-grep/cli": ["@ast-grep/cli@0.44.0", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "@ast-grep/cli-darwin-arm64": "0.44.0", "@ast-grep/cli-darwin-x64": "0.44.0", "@ast-grep/cli-linux-arm64-gnu": "0.44.0", "@ast-grep/cli-linux-x64-gnu": "0.44.0", "@ast-grep/cli-win32-arm64-msvc": "0.44.0", "@ast-grep/cli-win32-ia32-msvc": "0.44.0", "@ast-grep/cli-win32-x64-msvc": "0.44.0" }, "bin": { "sg": "sg", "ast-grep": "ast-grep" } }, "sha512-Jf4PuP7XjzsMa3m9gYxmzV8KyWZc4w1ZzKe/t0+90wWxmSasQJe6AtMkJxHEi98MGgfAF1nWziqjDd0/6EsBjA=="], "@ast-grep/cli-darwin-arm64": ["@ast-grep/cli-darwin-arm64@0.44.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bF7euu/hF/cYg4510z8110vh60rrqfrBdsfRqVGd6xqNSPENu7CJnTVN/Z4Nk5U1NM8YKzUD+dYx1ySUJ0CUNQ=="], @@ -1311,6 +1323,10 @@ "@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.4", "", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="], + "@asyncapi/parser": ["@asyncapi/parser@3.4.0", "", { "dependencies": { "@asyncapi/specs": "^6.8.0", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json": "3.21.0", "@stoplight/json-ref-readers": "^1.2.2", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.18.3", "@stoplight/spectral-functions": "^1.7.2", "@stoplight/spectral-parsers": "^1.0.2", "@stoplight/spectral-ref-resolver": "^1.0.3", "@stoplight/types": "^13.12.0", "@types/json-schema": "^7.0.11", "@types/urijs": "^1.19.19", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "avsc": "^5.7.5", "js-yaml": "^4.1.0", "jsonpath-plus": "^10.0.0", "node-fetch": "2.6.7" } }, "sha512-Sxn74oHiZSU6+cVeZy62iPZMFMvKp4jupMFHelSICCMw1qELmUHPvuZSr+ZHDmNGgHcEpzJM5HN02kR7T4g+PQ=="], + + "@asyncapi/specs": ["@asyncapi/specs@6.8.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA=="], + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], @@ -1505,6 +1521,8 @@ "@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.12.0", "", { "dependencies": { "@bufbuild/protobuf": "2.12.0", "@typescript/vfs": "^1.6.2", "typescript": "5.4.5" } }, "sha512-ORlDITp8AFUXzIhLRoMCG+ud+D3MPKWb5HQXBoskMMnjeyEjE1H1qLonVNPyOr8lkx3xSfYUo8a0dvOZJVAzow=="], + "@canvas/image-data": ["@canvas/image-data@1.1.0", "", {}, "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA=="], + "@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="], "@clack/core": ["@clack/core@1.0.0-alpha.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="], @@ -1771,6 +1789,38 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="], + + "@inquirer/checkbox": ["@inquirer/checkbox@4.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA=="], + + "@inquirer/confirm": ["@inquirer/confirm@5.1.21", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ=="], + + "@inquirer/core": ["@inquirer/core@10.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="], + + "@inquirer/editor": ["@inquirer/editor@4.2.23", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/external-editor": "^1.0.3", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ=="], + + "@inquirer/expand": ["@inquirer/expand@4.0.23", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew=="], + + "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="], + + "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], + + "@inquirer/input": ["@inquirer/input@4.3.1", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g=="], + + "@inquirer/number": ["@inquirer/number@3.0.23", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg=="], + + "@inquirer/password": ["@inquirer/password@4.0.23", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA=="], + + "@inquirer/prompts": ["@inquirer/prompts@7.9.0", "", { "dependencies": { "@inquirer/checkbox": "^4.3.0", "@inquirer/confirm": "^5.1.19", "@inquirer/editor": "^4.2.21", "@inquirer/expand": "^4.0.21", "@inquirer/input": "^4.2.5", "@inquirer/number": "^3.0.21", "@inquirer/password": "^4.0.21", "@inquirer/rawlist": "^4.1.9", "@inquirer/search": "^3.2.0", "@inquirer/select": "^4.4.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A=="], + + "@inquirer/rawlist": ["@inquirer/rawlist@4.1.11", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw=="], + + "@inquirer/search": ["@inquirer/search@3.2.2", "", { "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA=="], + + "@inquirer/select": ["@inquirer/select@4.4.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w=="], + + "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], + "@internationalized/date": ["@internationalized/date@3.12.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw=="], "@internationalized/number": ["@internationalized/number@3.6.7", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg=="], @@ -1805,6 +1855,12 @@ "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], + "@jsep-plugin/assignment": ["@jsep-plugin/assignment@1.3.0", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ=="], + + "@jsep-plugin/regex": ["@jsep-plugin/regex@1.0.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg=="], + + "@jsep-plugin/ternary": ["@jsep-plugin/ternary@1.1.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg=="], + "@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="], "@jsx-email/body": ["@jsx-email/body@1.0.2", "", { "peerDependencies": { "react": "^18.2.0" } }, "sha512-NjR2tgLH4XGfGkm+O8kcVwi9MBqZsXZCLlmk3HlMux3/n/+a5zB+yhJqXWZBJl2i+6cSF+E2O6hK11ekyK9WWQ=="], @@ -1879,6 +1935,26 @@ "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], + "@mintlify/cli": ["@mintlify/cli@4.0.1269", "", { "dependencies": { "@inquirer/prompts": "7.9.0", "@mintlify/common": "1.0.985", "@mintlify/link-rot": "3.0.1172", "@mintlify/models": "0.0.333", "@mintlify/prebuild": "1.0.1131", "@mintlify/previewing": "4.0.1197", "@mintlify/validation": "0.1.769", "adm-zip": "0.5.16", "chalk": "5.2.0", "color": "4.2.3", "detect-port": "1.5.1", "front-matter": "4.0.2", "fs-extra": "11.2.0", "ink": "6.3.0", "inquirer": "12.3.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.2.0", "open": "8.4.2", "openid-client": "6.8.2", "posthog-node": "5.17.2", "react": "19.2.3", "semver": "7.7.2", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "4.3.6" }, "optionalDependencies": { "keytar": "7.9.0" }, "bin": { "mint": "bin/index.js", "mintlify": "bin/index.js" } }, "sha512-l9b7InT55JWXV7TU7Jr4Wrijv4/gMFHLQyWJ7fcjqpSxAetR+xNyeEARRlcf7OicGTjZuvmRGGfj75kp3O4p7A=="], + + "@mintlify/common": ["@mintlify/common@1.0.985", "", { "dependencies": { "@asyncapi/parser": "3.4.0", "@asyncapi/specs": "6.8.1", "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.333", "@mintlify/openapi-parser": "0.0.8", "@mintlify/validation": "0.1.769", "@sindresorhus/slugify": "2.2.0", "@types/mdast": "4.0.4", "acorn": "8.11.2", "acorn-jsx": "5.3.2", "color-blend": "4.0.0", "estree-util-to-js": "2.0.0", "estree-walker": "3.0.3", "front-matter": "4.0.2", "hast-util-from-html": "2.0.3", "hast-util-to-html": "9.0.4", "hast-util-to-text": "4.0.2", "hex-rgb": "5.0.0", "ignore": "7.0.5", "js-yaml": "4.1.1", "lodash": "4.18.1", "mdast-util-from-markdown": "2.0.2", "mdast-util-gfm": "3.0.0", "mdast-util-mdx": "3.0.0", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-gfm": "3.0.0", "micromark-extension-mdx-jsx": "3.0.1", "micromark-extension-mdxjs": "3.0.0", "openapi-types": "12.1.3", "postcss": "8.5.14", "rehype-stringify": "10.0.1", "remark": "15.0.1", "remark-frontmatter": "5.0.0", "remark-gfm": "4.0.0", "remark-math": "6.0.0", "remark-mdx": "3.1.0", "remark-parse": "11.0.0", "remark-rehype": "11.1.1", "remark-stringify": "11.0.0", "sucrase": "3.34.0", "tailwindcss": "3.4.17", "unified": "11.0.5", "unist-builder": "4.0.0", "unist-util-map": "4.0.0", "unist-util-remove": "4.0.0", "unist-util-remove-position": "5.0.0", "unist-util-visit": "5.0.0", "unist-util-visit-parents": "6.0.1", "vfile": "6.0.3", "xss": "1.0.15" } }, "sha512-eJPeR99AKgVifXLdiA2hhfNy2+CmZ3zqQAscRXmFbJFST8SgsUj6rU3D2fx0XYt38b7T3LqEMD7DH5ipSC+Zfg=="], + + "@mintlify/link-rot": ["@mintlify/link-rot@3.0.1172", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/models": "0.0.333", "@mintlify/prebuild": "1.0.1131", "@mintlify/previewing": "4.0.1197", "@mintlify/scraping": "4.0.849", "@mintlify/validation": "0.1.769", "fs-extra": "11.1.0", "unist-util-visit": "4.1.2" } }, "sha512-8962sk/WO/0YcSkHTiaVZ2DvrCb8TC4BPgp21a9qW6nsPKKNixMhsC2uUB90D+gOzPTejJl0NNd4gLk4fA3SKw=="], + + "@mintlify/mdx": ["@mintlify/mdx@3.0.4", "", { "dependencies": { "@shikijs/transformers": "^3.11.0", "@shikijs/twoslash": "^3.12.2", "arktype": "^2.1.26", "hast-util-to-string": "^3.0.1", "mdast-util-from-markdown": "^2.0.2", "mdast-util-gfm": "^3.1.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-to-hast": "^13.2.0", "next-mdx-remote-client": "^1.0.3", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "remark-smartypants": "^3.0.2", "shiki": "^3.11.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@radix-ui/react-popover": "^1.1.15", "react": "^18.3.1", "react-dom": "^18.3.1" } }, "sha512-tJhdpnM5ReJLNJ2fuDRIEr0zgVd6id7/oAIfs26V46QlygiLsc8qx4Rz3LWIX51rUXW/cfakjj0EATxIciIw+g=="], + + "@mintlify/models": ["@mintlify/models@0.0.333", "", { "dependencies": { "axios": "1.16.1", "openapi-types": "12.1.3" } }, "sha512-0uAsuTsV8gYCDpv4aA0MWilQu+a/mrK+G6q5FVcgunbEZ3meeCXfrQFTviaEw7A+0cR/7Pc2KLA66cPDm+3Qdg=="], + + "@mintlify/openapi-parser": ["@mintlify/openapi-parser@0.0.8", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-9MBRq9lS4l4HITYCrqCL7T61MOb20q9IdU7HWhqYMNMM1jGO1nHjXasFy61yZ8V6gMZyyKQARGVoZ0ZrYN48Og=="], + + "@mintlify/prebuild": ["@mintlify/prebuild@1.0.1131", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/openapi-parser": "0.0.8", "@mintlify/scraping": "4.0.849", "@mintlify/validation": "0.1.769", "chalk": "5.3.0", "favicons": "7.2.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "sharp": "0.33.5", "sharp-ico": "0.1.5", "unist-util-visit": "4.1.2", "uuid": "11.1.1" } }, "sha512-EbPf1/z1m8K/Jl4qXggiMQwfdqXLF25sj+d5SHaGl6T0auTOYMayN578Rb/qM4fjhhlBQwub919Zsq9syYeYUA=="], + + "@mintlify/previewing": ["@mintlify/previewing@4.0.1197", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/prebuild": "1.0.1131", "@mintlify/validation": "0.1.769", "adm-zip": "0.5.16", "better-opn": "3.0.2", "chalk": "5.2.0", "chokidar": "3.5.3", "express": "4.22.0", "front-matter": "4.0.2", "fs-extra": "11.1.0", "got": "13.0.0", "ink": "6.3.0", "ink-spinner": "5.0.0", "is-online": "10.0.0", "js-yaml": "4.1.1", "openapi-types": "12.1.3", "react": "19.2.3", "socket.io": "4.8.0", "tar": "7.5.15", "unist-util-visit": "4.1.2", "yargs": "17.7.1" } }, "sha512-q4TunK8KjE1k9ve5nOAQTvBpsE7bX+RJN/ozx7RdIyQSCSexpkywmSM6nE3ECJyjUWFy8pg6yrYYbUQLHN/oww=="], + + "@mintlify/scraping": ["@mintlify/scraping@4.0.849", "", { "dependencies": { "@mintlify/common": "1.0.985", "@mintlify/openapi-parser": "0.0.8", "fs-extra": "11.1.1", "hast-util-to-mdast": "10.1.0", "js-yaml": "4.1.1", "mdast-util-mdx-jsx": "3.1.3", "neotraverse": "0.6.18", "puppeteer": "22.14.0", "rehype-parse": "9.0.1", "remark-gfm": "4.0.0", "remark-mdx": "3.0.1", "remark-parse": "11.0.0", "remark-stringify": "11.0.0", "unified": "11.0.5", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "3.24.0" }, "bin": { "mintlify-scrape": "bin/cli.js" } }, "sha512-4aMltLtfSU5rkUJt2SCVaYIbuggiEnx7lMWKM8NW93SaZUeoL0iKNbo0K24SmOqS7kYATRRH8zI7gefPX2ZLDw=="], + + "@mintlify/validation": ["@mintlify/validation@0.1.769", "", { "dependencies": { "@mintlify/mdx": "3.0.4", "@mintlify/models": "0.0.333", "arktype": "2.1.27", "fractional-indexing": "3.2.0", "js-yaml": "4.1.1", "lcm": "0.0.3", "lodash": "4.18.1", "neotraverse": "0.6.18", "object-hash": "3.0.0", "openapi-types": "12.1.3", "uuid": "11.1.1", "zod": "3.24.0", "zod-to-json-schema": "3.20.4" } }, "sha512-8Sg6DCdQ7RSc3NIyaSSWy7G6KaAuw0NfUSBCYLwGhtp6dYPh1jEsPn6YU8hqUMNZn1OQHsA52rA6lZQxjnyLHQ=="], + "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], @@ -1993,6 +2069,8 @@ "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], + "@openapi-contrib/openapi-schema-to-json-schema": ["@openapi-contrib/openapi-schema-to-json-schema@3.2.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw=="], + "@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="], "@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"], @@ -2019,6 +2097,8 @@ "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"], + "@opencode-ai/docs": ["@opencode-ai/docs@workspace:packages/docs"], + "@opencode-ai/effect-drizzle-sqlite": ["@opencode-ai/effect-drizzle-sqlite@workspace:packages/effect-drizzle-sqlite"], "@opencode-ai/effect-sqlite-node": ["@opencode-ai/effect-sqlite-node@workspace:packages/effect-sqlite-node"], @@ -2391,6 +2471,8 @@ "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + "@posthog/core": ["@posthog/core@1.7.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-kjK0eFMIpKo9GXIbts8VtAknsoZ18oZorANdtuTj1CbgS28t4ZVq//HAWhnxEuXRTrtkd+SUJ6Ux3j2Af8NCuA=="], + "@preact/signals-core": ["@preact/signals-core@1.14.3", "", {}, "sha512-m0K3vnbSLC5rHs2ZVfeAMvBtT1zIyq4mxx5OlNncSgMj5Iz6W5Rn3kPrDxAC+iIKmiVe0lSl6U37t5ZkEWoVAw=="], "@protobuf-ts/plugin": ["@protobuf-ts/plugin@2.11.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.4.0", "@bufbuild/protoplugin": "^2.4.0", "@protobuf-ts/protoc": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", "typescript": "^3.9" }, "bin": { "protoc-gen-ts": "bin/protoc-gen-ts", "protoc-gen-dump": "bin/protoc-gen-dump" } }, "sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A=="], @@ -2421,6 +2503,8 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + "@puppeteer/browsers": ["@puppeteer/browsers@2.3.0", "", { "dependencies": { "debug": "^4.3.5", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA=="], + "@radix-ui/colors": ["@radix-ui/colors@1.0.1", "", {}, "sha512-xySw8f0ZVsAEP+e7iLl3EvcBXX7gsIlC1Zso/sPBW9gIWerBTgz6axrjU+MZ39wD+WFi5h5zdWpsg3+hwt2Qsg=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw=="], @@ -2539,6 +2623,8 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="], + "@scarf/scarf": ["@scarf/scarf@1.4.0", "", {}, "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ=="], + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-WILVR8HQBWOxbqLRuTxjzRCMIACGsDTo6jXvzA8rz6ezElElLmIrn3CFAswrESLqEEUa4CQHl5bLgSVJCRNweA=="], @@ -2595,6 +2681,8 @@ "@shikijs/transformers": ["@shikijs/transformers@3.9.2", "", { "dependencies": { "@shikijs/core": "3.9.2", "@shikijs/types": "3.9.2" } }, "sha512-MW5hT4TyUp6bNAgTExRYLk1NNasVQMTCw1kgbxHcEC0O5cbepPWaB+1k+JzW9r3SP2/R8kiens8/3E6hGKfgsA=="], + "@shikijs/twoslash": ["@shikijs/twoslash@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/types": "3.23.0", "twoslash": "^0.3.6" }, "peerDependencies": { "typescript": ">=5.5.0" } }, "sha512-pNaLJWMA3LU7PhT8tm9OQBZ1epy0jmdgeJzntBtr1EVXLbHxGzTj3mnf9vOdcl84l96qnlJXkJ/NGXZYBpXl5g=="], + "@shikijs/types": ["@shikijs/types@3.9.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -2615,6 +2703,10 @@ "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + "@sindresorhus/slugify": ["@sindresorhus/slugify@2.2.0", "", { "dependencies": { "@sindresorhus/transliterate": "^1.0.0", "escape-string-regexp": "^5.0.0" } }, "sha512-9Vybc/qX8Kj6pxJaapjkFbiUJPk7MAkCh/GFCxIBnnsuYCFPIXKvnLidG8xlepht3i24L5XemUmGtrJ3UWrl6w=="], + + "@sindresorhus/transliterate": ["@sindresorhus/transliterate@1.6.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0" } }, "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ=="], + "@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="], "@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="], @@ -2769,6 +2861,36 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@stoplight/better-ajv-errors": ["@stoplight/better-ajv-errors@1.0.3", "", { "dependencies": { "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA=="], + + "@stoplight/json": ["@stoplight/json@3.21.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.3", "@stoplight/path": "^1.3.2", "@stoplight/types": "^13.6.0", "jsonc-parser": "~2.2.1", "lodash": "^4.17.21", "safe-stable-stringify": "^1.1" } }, "sha512-5O0apqJ/t4sIevXCO3SBN9AHCEKKR/Zb4gaj7wYe5863jme9g02Q0n/GhM7ZCALkL+vGPTe4ZzTETP8TFtsw3g=="], + + "@stoplight/json-ref-readers": ["@stoplight/json-ref-readers@1.2.2", "", { "dependencies": { "node-fetch": "^2.6.0", "tslib": "^1.14.1" } }, "sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ=="], + + "@stoplight/json-ref-resolver": ["@stoplight/json-ref-resolver@3.1.6", "", { "dependencies": { "@stoplight/json": "^3.21.0", "@stoplight/path": "^1.3.2", "@stoplight/types": "^12.3.0 || ^13.0.0", "@types/urijs": "^1.19.19", "dependency-graph": "~0.11.0", "fast-memoize": "^2.5.2", "immer": "^9.0.6", "lodash": "^4.17.21", "tslib": "^2.6.0", "urijs": "^1.19.11" } }, "sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A=="], + + "@stoplight/ordered-object-literal": ["@stoplight/ordered-object-literal@1.0.5", "", {}, "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg=="], + + "@stoplight/path": ["@stoplight/path@1.3.2", "", {}, "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ=="], + + "@stoplight/spectral-core": ["@stoplight/spectral-core@1.23.1", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "~3.21.0", "@stoplight/path": "1.3.2", "@stoplight/spectral-parsers": "^1.0.0", "@stoplight/spectral-ref-resolver": "^1.0.4", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "~13.6.0", "@types/es-aggregate-error": "^1.0.2", "@types/json-schema": "^7.0.11", "ajv": "^8.18.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "es-aggregate-error": "^1.0.7", "expr-eval-fork": "^3.0.1", "jsonpath-plus": "^10.3.0", "lodash": "^4.18.1", "lodash.topath": "^4.5.2", "minimatch": "^3.1.4", "nimma": "0.2.3", "pony-cause": "^1.1.1", "tslib": "^2.8.1" } }, "sha512-VLC8OhpO/pMJKb6IHhurxJjXO1qB56Ng1unIb8b+hNxdw0+SEcASvmR+RpjfHYX/jv/DfSaA1x8QhFBJBmqBOQ=="], + + "@stoplight/spectral-formats": ["@stoplight/spectral-formats@1.8.5", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/json": "^3.17.0", "@stoplight/spectral-core": "^1.23.0", "@types/json-schema": "^7.0.7", "tslib": "^2.8.1" } }, "sha512-xaC0rCH0p7/bzNJsz+JgLSj+Cp6uwYGWpePQxdLkF2G6a8Zyp3OyS7umkGYNiimEwKrOjvCNNTFJpeuiENZSBA=="], + + "@stoplight/spectral-functions": ["@stoplight/spectral-functions@1.10.5", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "^3.17.1", "@stoplight/spectral-core": "^1.23.0", "@stoplight/spectral-formats": "^1.8.1", "@stoplight/spectral-runtime": "^1.1.2", "ajv": "^8.18.0", "ajv-draft-04": "~1.0.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "lodash": "^4.18.1", "tslib": "^2.8.1" } }, "sha512-vDCd0NJ93715bcUpZZ5vNHiyxd4cgHF6tuXsDiXOXKAByg+I1fR5/dMijEo6Ce1Lz95a+RZ22JKYhF1YuzVvuA=="], + + "@stoplight/spectral-parsers": ["@stoplight/spectral-parsers@1.0.5", "", { "dependencies": { "@stoplight/json": "~3.21.0", "@stoplight/types": "^14.1.1", "@stoplight/yaml": "~4.3.0", "tslib": "^2.8.1" } }, "sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ=="], + + "@stoplight/spectral-ref-resolver": ["@stoplight/spectral-ref-resolver@1.0.5", "", { "dependencies": { "@stoplight/json-ref-readers": "1.2.2", "@stoplight/json-ref-resolver": "~3.1.6", "@stoplight/spectral-runtime": "^1.1.2", "dependency-graph": "0.11.0", "tslib": "^2.8.1" } }, "sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA=="], + + "@stoplight/spectral-runtime": ["@stoplight/spectral-runtime@1.1.6", "", { "dependencies": { "@stoplight/json": "^3.20.1", "@stoplight/path": "^1.3.2", "@stoplight/types": "^13.6.0", "lodash": "^4.18.1", "node-fetch": "^2.7.0", "tslib": "^2.8.1" } }, "sha512-Y8rEDyMN4bSMJCrDs2shdcVHYyCnH3FvXRP4dBhha4Z8iJv+JPp7KqOV/hwVB/hWFC209upiwj2oDmLfR0qCDg=="], + + "@stoplight/types": ["@stoplight/types@13.20.0", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA=="], + + "@stoplight/yaml": ["@stoplight/yaml@4.3.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.5", "@stoplight/types": "^14.1.1", "@stoplight/yaml-ast-parser": "0.0.50", "tslib": "^2.2.0" } }, "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w=="], + + "@stoplight/yaml-ast-parser": ["@stoplight/yaml-ast-parser@0.0.50", "", {}, "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ=="], + "@storybook/addon-a11y": ["@storybook/addon-a11y@10.4.1", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.4.1" } }, "sha512-MGft/IXjJ20a9KbaSVG9bHTAAoanbucKrgEiJJRNqpim8DsXA01+XTdSk17LmiOCB203Rrq9mWgdQ6+79cc8iA=="], "@storybook/addon-docs": ["@storybook/addon-docs@10.4.1", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.4.1", "@storybook/icons": "^2.0.2", "@storybook/react-dom-shim": "10.4.1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.4.1" }, "optionalPeers": ["@types/react"] }, "sha512-IYqUdjoZe4VO2LFZlKL/gwy7DsQSWCq6hX+zc1MBmZo04yycDASk1tte57n9pdlW3ajw9yYMF/+lVBi+xQjyvw=="], @@ -2847,6 +2969,8 @@ "@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="], + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="], @@ -2869,6 +2993,8 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + "@types/acorn": ["@types/acorn@4.0.6", "", { "dependencies": { "@types/estree": "*" } }, "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ=="], + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -2893,6 +3019,8 @@ "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], + "@types/cross-spawn": ["@types/cross-spawn@6.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA=="], "@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], @@ -2905,6 +3033,8 @@ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -3011,6 +3141,8 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/urijs": ["@types/urijs@1.19.26", "", {}, "sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg=="], + "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], @@ -3109,10 +3241,16 @@ "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], + "address": ["address@1.2.2", "", {}, "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA=="], + + "adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "aggregate-error": ["aggregate-error@4.0.1", "", { "dependencies": { "clean-stack": "^4.0.0", "indent-string": "^5.0.0" } }, "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w=="], + "ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="], "ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="], @@ -3121,6 +3259,8 @@ "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + "ajv-errors": ["ajv-errors@3.0.0", "", { "peerDependencies": { "ajv": "^8.0.1" } }, "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ=="], + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], @@ -3129,6 +3269,8 @@ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -3157,6 +3299,10 @@ "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + "arkregex": ["arkregex@0.0.3", "", { "dependencies": { "@ark/util": "0.55.0" } }, "sha512-bU21QJOJEFJK+BPNgv+5bVXkvRxyAvgnon75D92newgHxkBJTgiFwQxusyViYyJkETsddPlHyspshDQcCzmkNg=="], + + "arktype": ["arktype@2.1.27", "", { "dependencies": { "@ark/schema": "0.55.0", "@ark/util": "0.55.0", "arkregex": "0.0.3" } }, "sha512-enctOHxI4SULBv/TDtCVi5M8oLd4J5SVlPUblXDzSsOYQNMzmVbUosGBnJuZDKmFlN5Ie0/QVEuTE+Z5X1UhsQ=="], + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], @@ -3201,10 +3347,14 @@ "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="], + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "autoprefixer": ["autoprefixer@10.5.0", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-lite": "^1.0.30001787", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "avsc": ["avsc@5.7.9", "", {}, "sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg=="], + "avvio": ["avvio@9.2.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ=="], "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], @@ -3249,14 +3399,20 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="], + "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], + "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], + "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], "bin-links": ["bin-links@6.0.2", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w=="], @@ -3265,6 +3421,8 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], @@ -3335,6 +3493,8 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], @@ -3359,6 +3519,8 @@ "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "chardet": ["chardet@2.2.0", "", {}, "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA=="], + "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="], "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], @@ -3371,6 +3533,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "chromium-bidi": ["chromium-bidi@0.6.2", "", { "dependencies": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-4WVBa6ijmUTVr9cZD4eicQD8Mdy/HCX3bzEIYYpmk0glqYLoWH+LqQEvV9RpDRzoQSbY1KJHloYXbDMXMbDPhg=="], + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], @@ -3381,12 +3545,18 @@ "clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="], + "clean-stack": ["clean-stack@4.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg=="], + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + "cli-spinners": ["cli-spinners@3.4.0", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="], "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + "clipboardy": ["clipboardy@4.0.0", "", { "dependencies": { "execa": "^8.0.1", "is-wsl": "^3.1.0", "is64bit": "^2.0.0" } }, "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w=="], "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], @@ -3403,10 +3573,14 @@ "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], + "color-blend": ["color-blend@4.0.0", "", {}, "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -3445,6 +3619,8 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie-es": ["cookie-es@2.0.1", "", {}, "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA=="], @@ -3455,6 +3631,8 @@ "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + "cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="], + "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], @@ -3481,6 +3659,8 @@ "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], @@ -3515,12 +3695,18 @@ "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], + "decode-bmp": ["decode-bmp@0.2.1", "", { "dependencies": { "@canvas/image-data": "^1.0.0", "to-data-view": "^1.1.0" } }, "sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA=="], + + "decode-ico": ["decode-ico@0.4.1", "", { "dependencies": { "@canvas/image-data": "^1.0.0", "decode-bmp": "^0.2.0", "to-data-view": "^1.1.0" } }, "sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], @@ -3537,12 +3723,16 @@ "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dependency-graph": ["dependency-graph@0.11.0", "", {}, "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg=="], + "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], @@ -3557,12 +3747,16 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "detect-port": ["detect-port@1.5.1", "", { "dependencies": { "address": "^1.0.1", "debug": "4" }, "bin": { "detect": "bin/detect-port.js", "detect-port": "bin/detect-port.js" } }, "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ=="], + "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "devtools-protocol": ["devtools-protocol@0.0.1312386", "", {}, "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA=="], + "dfa": ["dfa@1.2.0", "", {}, "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="], "diacritics": ["diacritics@1.3.0", "", {}, "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA=="], @@ -3585,6 +3779,8 @@ "dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="], + "dns-socket": ["dns-socket@4.2.2", "", { "dependencies": { "dns-packet": "^5.2.4" } }, "sha512-BDeBd8najI4/lS00HSKpdFia+OvUMytaVjfzR9n5Lq8MlZRSvtbI+uLtx1+XmQFls5wFU9dssccTmQQ6nfpjdg=="], + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -3667,6 +3863,8 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "engine.io": ["engine.io@6.6.9", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.21.0" } }, "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg=="], + "engine.io-client": ["engine.io-client@6.6.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.20.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg=="], "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], @@ -3677,14 +3875,20 @@ "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], + "es-aggregate-error": ["es-aggregate-error@1.0.14", "", { "dependencies": { "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "globalthis": "^1.0.4", "has-property-descriptors": "^1.0.2", "set-function-name": "^2.0.2" } }, "sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA=="], + "es-array-method-boxes-properly": ["es-array-method-boxes-properly@1.0.0", "", {}, "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -3701,6 +3905,8 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], @@ -3719,8 +3925,12 @@ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], @@ -3735,6 +3945,8 @@ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], @@ -3753,10 +3965,14 @@ "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + "expr-eval-fork": ["expr-eval-fork@3.0.3", "", {}, "sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="], @@ -3791,6 +4007,8 @@ "fast-json-stringify": ["fast-json-stringify@6.4.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ=="], + "fast-memoize": ["fast-memoize@2.5.2", "", {}, "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="], + "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="], "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], @@ -3805,6 +4023,10 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="], + + "favicons": ["favicons@7.2.0", "", { "dependencies": { "escape-html": "^1.0.3", "sharp": "^0.33.1", "xml2js": "^0.6.1" } }, "sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -3843,6 +4065,8 @@ "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], @@ -3851,10 +4075,16 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + "fractional-indexing": ["fractional-indexing@3.2.0", "", {}, "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ=="], + "framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "front-matter": ["front-matter@4.0.2", "", { "dependencies": { "js-yaml": "^3.13.1" } }, "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], @@ -3873,6 +4103,8 @@ "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], + "gcd": ["gcd@0.0.1", "", {}, "sha512-VNx3UEGr+ILJTiMs1+xc5SX1cMgJCrXezKPa003APUWNqQqaF6n25W8VcR7nHN6yRWbvvUTwCpZCFJeWC2kXlw=="], + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], @@ -3899,10 +4131,14 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#513463a", {}, "anomalyco-ghostty-web-513463a", "sha512-GZR8LSmgGzViWnBJrqRI8MpAZRCJxhcr1Hi9Tyeh7YRooHZQjK9J97FQRD3tbBaM2wjq05gzGY2UEsG+JtZeBw=="], "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "gitlab-ai-provider": ["gitlab-ai-provider@6.10.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-oWEZ06rDO6JjB7INHO882wyBAQqCZVHiDHwCs5M+VPmdDj8TzhGXcYesA2CcV5RoI5lfHLKwGp5uKFB62VWpqw=="], @@ -3959,8 +4195,12 @@ "hast-util-format": ["hast-util-format@1.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0", "hast-util-phrasing": "^3.0.0", "hast-util-whitespace": "^3.0.0", "html-whitespace-sensitive-tag-names": "^3.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA=="], + "hast-util-from-dom": ["hast-util-from-dom@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="], + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + "hast-util-from-html-isomorphic": ["hast-util-from-html-isomorphic@2.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-dom": "^5.0.0", "hast-util-from-html": "^2.0.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw=="], + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], "hast-util-has-property": ["hast-util-has-property@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA=="], @@ -3987,6 +4227,8 @@ "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + "hast-util-to-mdast": ["hast-util-to-mdast@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-phrasing": "^3.0.0", "hast-util-to-html": "^9.0.0", "hast-util-to-text": "^4.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "mdast-util-to-string": "^4.0.0", "rehype-minify-whitespace": "^6.0.0", "trim-trailing-lines": "^2.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-DsL/SvCK9V7+vfc6SLQ+vKIyBDXTk2KLSbfBYkH4zeF/uR1yBajHRhkzuaUSGOB1WJSTieJBdHwxlC+HLKvZZw=="], + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], @@ -4001,6 +4243,8 @@ "heap-snapshot-toolkit": ["heap-snapshot-toolkit@1.1.3", "", {}, "sha512-joThu2rEsDu8/l4arupRDI1qP4CZXNG+J6Wr348vnbLGSiBkwRdqZ6aOHl5BzEiC+Dc8OTbMlmWjD0lbXD5K2Q=="], + "hex-rgb": ["hex-rgb@5.0.0", "", {}, "sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w=="], + "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], @@ -4045,6 +4289,8 @@ "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + "ico-endec": ["ico-endec@0.1.6", "", {}, "sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ=="], + "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], @@ -4057,6 +4303,8 @@ "immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], @@ -4069,8 +4317,14 @@ "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "ink": ["ink@6.3.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-2CbJAa7XeziZYe6pDS5RVLirRY28iSGMQuEV8jRU5NQsONQNfcR/BZHHc9vkMg2lGYTHTM2pskxC1YmY28p6bQ=="], + + "ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + "inquirer": ["inquirer@12.3.0", "", { "dependencies": { "@inquirer/core": "^10.1.2", "@inquirer/prompts": "^7.2.1", "@inquirer/type": "^3.0.2", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", "run-async": "^3.0.0", "rxjs": "^7.8.1" }, "peerDependencies": { "@types/node": ">=18" } }, "sha512-3NixUXq+hM8ezj2wc7wC37b32/rHq1MwNZDYdvx+d6jokOD+r+i8Q4Pkylh9tISYP114A128LCX8RKhopC5RfQ=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], @@ -4079,6 +4333,8 @@ "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], + "ip-regex": ["ip-regex@4.3.0", "", {}, "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], @@ -4131,10 +4387,14 @@ "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-ip": ["is-ip@3.1.0", "", { "dependencies": { "ip-regex": "^4.0.0" } }, "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -4143,6 +4403,8 @@ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-online": ["is-online@10.0.0", "", { "dependencies": { "got": "^12.1.0", "p-any": "^4.0.0", "p-timeout": "^5.1.0", "public-ip": "^5.0.0" } }, "sha512-WCPdKwNDjXJJmUubf2VHLMDBkUZEtuOvpXUfUnUFbEnM6In9ByiScL4f4jKACz/fsb2qDkesFerW3snf/AYz3A=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], @@ -4215,6 +4477,8 @@ "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="], + "jsep": ["jsep@1.4.0", "", {}, "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], @@ -4247,6 +4511,10 @@ "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "jsonpath-plus": ["jsonpath-plus@10.4.0", "", { "dependencies": { "@jsep-plugin/assignment": "^1.3.0", "@jsep-plugin/regex": "^1.0.4", "jsep": "^1.4.0" }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" } }, "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA=="], + + "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], @@ -4261,6 +4529,8 @@ "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], + "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], @@ -4279,8 +4549,12 @@ "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + "lcm": ["lcm@0.0.3", "", { "dependencies": { "gcd": "^0.0.1" } }, "sha512-TB+ZjoillV6B26Vspf9l2L/vKaRY/4ep3hahcyVkCGFgsTNRUQdc24bQeNFiZeoxH0vr5+7SfNRMQuPHv/1IrQ=="], + "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + "leven": ["leven@4.1.0", "", {}, "sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew=="], + "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], @@ -4331,6 +4605,8 @@ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], + "lodash.topath": ["lodash.topath@4.5.2", "", {}, "sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg=="], + "loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -4387,6 +4663,8 @@ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + "mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="], + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], @@ -4399,6 +4677,8 @@ "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + "mdast-util-math": ["mdast-util-math@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.1.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="], + "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="], "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], @@ -4435,6 +4715,8 @@ "micromark-extension-directive": ["micromark-extension-directive@3.0.2", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA=="], + "micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="], + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], @@ -4449,9 +4731,11 @@ "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + "micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="], + "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="], - "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], + "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.1", "", { "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg=="], "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="], @@ -4537,8 +4821,14 @@ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "mint": ["mint@4.2.666", "", { "dependencies": { "@mintlify/cli": "4.0.1269" }, "bin": { "mint": "index.js" } }, "sha512-FsdL35EH++MiVDoKxN8M6/obOsrgx0Ko7P/1Y2lBXh/jEPx1UD1Y8msmAOW6cuwaqGIzAxuC7ySmr7D3G2bmBg=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="], "motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="], @@ -4563,6 +4853,8 @@ "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], + "mysql2": ["mysql2@3.14.4", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], @@ -4573,12 +4865,20 @@ "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], + "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], + + "next-mdx-remote-client": ["next-mdx-remote-client@1.1.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@mdx-js/mdx": "^3.1.1", "@mdx-js/react": "^3.1.1", "@types/mdx": "^2.0.13", "remark-mdx-remove-esm": "^1.3.2", "serialize-error": "^13.0.1", "vfile": "^6.0.3", "vfile-matter": "^5.0.1" }, "peerDependencies": { "react": ">= 18.3.0 < 19.0.0", "react-dom": ">= 18.3.0 < 19.0.0" } }, "sha512-IElOrn02JjGQZxx+re7wMx/1AUG+Arte9aDImAtxjAfMw6xuSCaH5mTCunKelkWzFyFdRb565jO8jRICvvh96g=="], + "nf3": ["nf3@0.1.12", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="], + "nimma": ["nimma@0.2.3", "", { "dependencies": { "@jsep-plugin/regex": "^1.0.1", "@jsep-plugin/ternary": "^1.0.2", "astring": "^1.8.1", "jsep": "^1.2.0" }, "optionalDependencies": { "jsonpath-plus": "^6.0.1 || ^10.1.0", "lodash.topath": "^4.5.2" } }, "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA=="], + "nitro": ["nitro@3.0.1-alpha.1", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.1", "db0": "^0.3.4", "h3": "2.0.1-rc.5", "jiti": "^2.6.1", "nf3": "^0.1.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.96.0", "oxc-transform": "^0.96.0", "srvx": "^0.9.5", "undici": "^7.16.0", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.4" }, "peerDependencies": { "rolldown": "*", "rollup": "^4", "vite": "^7", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-U4AxIsXxdkxzkFrK0XAw0e5Qbojk8jQ50MjjRBtBakC4HurTtQoiZvF+lSe382jhuQZCfAyywGWOFa9QzXLFaw=="], "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], @@ -4611,6 +4911,8 @@ "node-releases": ["node-releases@2.0.46", "", {}, "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ=="], + "non-error": ["non-error@0.1.0", "", {}, "sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ=="], + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -4637,6 +4939,8 @@ "nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="], + "oauth4webapi": ["oauth4webapi@3.8.6", "", {}, "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], @@ -4697,6 +5001,8 @@ "oxlint-tsgolint": ["oxlint-tsgolint@0.21.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.0", "@oxlint-tsgolint/darwin-x64": "0.21.0", "@oxlint-tsgolint/linux-arm64": "0.21.0", "@oxlint-tsgolint/linux-x64": "0.21.0", "@oxlint-tsgolint/win32-arm64": "0.21.0", "@oxlint-tsgolint/win32-x64": "0.21.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg=="], + "p-any": ["p-any@4.0.0", "", { "dependencies": { "p-cancelable": "^3.0.0", "p-some": "^6.0.0" } }, "sha512-S/B50s+pAVe0wmEZHmBs/9yJXeZ5KhHzOsgKzt0hRdgkoR3DxW9ts46fcsWi/r3VnzsnkKS7q4uimze+zjdryw=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], "p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="], @@ -4713,10 +5019,16 @@ "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + "p-some": ["p-some@6.0.0", "", { "dependencies": { "aggregate-error": "^4.0.0", "p-cancelable": "^3.0.0" } }, "sha512-CJbQCKdfSX3fIh8/QKgS+9rjm7OBNUTmwWswAFQAhc8j1NR1dsEDETUEuVUtQHZpV+J03LqWBEwvu0g1Yn+TYg=="], + "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], @@ -4729,10 +5041,14 @@ "param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -4747,6 +5063,8 @@ "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], + "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], @@ -4811,6 +5129,8 @@ "poe-oauth": ["poe-oauth@0.0.8", "", {}, "sha512-zlaRVLR6vuxBIYUkZoTIVo3f8h3qd27gv9Ms+kmGiYEiiV4TdccddTdNcGyI0DnuJ9tVi+5LP3Bvzez59IFbjw=="], + "pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], @@ -4831,6 +5151,8 @@ "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "posthog-node": ["posthog-node@5.17.2", "", { "dependencies": { "@posthog/core": "1.7.1" } }, "sha512-lz3YJOr0Nmiz0yHASaINEDHqoV+0bC3eD8aZAG+Ky292dAnVYul+ga/dMX8KCBXg8hHfKdxw0SztYD5j6dgUqQ=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], @@ -4839,6 +5161,8 @@ "preact-render-to-string": ["preact-render-to-string@6.6.5", "", { "peerDependencies": { "preact": ">=10 || >= 11.0.0-0" } }, "sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="], @@ -4879,14 +5203,22 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "public-ip": ["public-ip@5.0.0", "", { "dependencies": { "dns-socket": "^4.2.2", "got": "^12.0.0", "is-ip": "^3.1.0" } }, "sha512-xaH3pZMni/R2BG7ZXXaWS9Wc9wFlhyDVJF47IJ+3ali0TGv+2PsckKxbmo+rnx3ZxiV2wblVhtdS3bohAP6GGw=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pupa": ["pupa@3.3.0", "", { "dependencies": { "escape-goat": "^4.0.0" } }, "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA=="], + "puppeteer": ["puppeteer@22.14.0", "", { "dependencies": { "@puppeteer/browsers": "2.3.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1312386", "puppeteer-core": "22.14.0" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" } }, "sha512-MGTR6/pM8zmWbTdazb6FKnwIihzsSEXBPH49mFFU96DNZpQOevCAZMnjBZGlZRGRzRK6aADCavR6SQtrbv5dQw=="], + + "puppeteer-core": ["puppeteer-core@22.14.0", "", { "dependencies": { "@puppeteer/browsers": "2.3.0", "chromium-bidi": "0.6.2", "debug": "^4.3.5", "devtools-protocol": "0.0.1312386", "ws": "^8.18.0" } }, "sha512-rl4tOY5LcA3e374GAlsGGHc05HL3eGNf5rZ+uxkl6id9zVZKcwcp1Z+Nd6byb6WPiPeecT/dwz8f/iUm+AZQSw=="], + "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], @@ -4909,6 +5241,8 @@ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -4919,6 +5253,8 @@ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], "react-remove-scroll": ["react-remove-scroll@2.5.5", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.3", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw=="], @@ -4979,6 +5315,10 @@ "rehype-format": ["rehype-format@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-format": "^1.0.0" } }, "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ=="], + "rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="], + + "rehype-minify-whitespace": ["rehype-minify-whitespace@6.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0" } }, "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw=="], + "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], @@ -4989,11 +5329,19 @@ "relateurl": ["relateurl@0.2.7", "", {}, "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog=="], + "remark": ["remark@15.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A=="], + "remark-directive": ["remark-directive@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^3.0.0", "unified": "^11.0.0" } }, "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A=="], + "remark-frontmatter": ["remark-frontmatter@5.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0", "unified": "^11.0.0" } }, "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ=="], + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], - "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + "remark-math": ["remark-math@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.0.0", "unified": "^11.0.0" } }, "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA=="], + + "remark-mdx": ["remark-mdx@3.1.0", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA=="], + + "remark-mdx-remove-esm": ["remark-mdx-remove-esm@1.3.2", "", { "dependencies": { "@types/mdast": "^4.0.4", "unist-util-remove": "^4.0.0" }, "peerDependencies": { "unified": "^11" } }, "sha512-BvL8VSdVXy9S7NlHP56nUJAHFc45h5E9HnHiLUGHe5tw3Yvm/3cVZvAzlkEEh2i+fkq2uKrf2xn5VmItBhMypA=="], "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -5029,6 +5377,8 @@ "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], @@ -5059,8 +5409,12 @@ "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + "run-async": ["run-async@3.0.0", "", {}, "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + "s-js": ["s-js@0.4.9", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="], "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], @@ -5117,6 +5471,8 @@ "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + "sharp-ico": ["sharp-ico@0.1.5", "", { "dependencies": { "decode-ico": "*", "ico-endec": "*", "sharp": "*" } }, "sha512-a3jODQl82NPp1d5OYb0wY+oFaPk7AvyxipIowCHk7pBsZCWgbe0yAkU2OOXdoH0ENyANhyOQbs9xkAiRHcF02Q=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -5139,6 +5495,10 @@ "sigstore": ["sigstore@4.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.1", "@sigstore/tuf": "^4.0.2", "@sigstore/verify": "^3.1.1" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], @@ -5155,6 +5515,10 @@ "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="], + "socket.io": ["socket.io@4.8.0", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA=="], + + "socket.io-adapter": ["socket.io-adapter@2.5.8", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.21.0" } }, "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw=="], + "socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], "socket.io-parser": ["socket.io-parser@4.2.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg=="], @@ -5225,6 +5589,8 @@ "sst-win32-x86": ["sst-win32-x86@4.13.1", "", { "os": "win32", "cpu": "none" }, "sha512-YPxBVdac/MsrzwlC6pF0NrrvMcmfdBLYjv7MbzHc5jNh1FQ1WPh6bdWQqgv0KD9EQTNLLEkej0beydgUvcCWJg=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], @@ -5273,6 +5639,8 @@ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="], "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], @@ -5307,6 +5675,8 @@ "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + "tar-fs": ["tar-fs@2.1.5", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw=="], + "tar-stream": ["tar-stream@3.2.0", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg=="], "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], @@ -5327,6 +5697,8 @@ "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], @@ -5353,6 +5725,8 @@ "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-data-view": ["to-data-view@1.1.0", "", {}, "sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toad-cache": ["toad-cache@3.7.1", "", {}, "sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ=="], @@ -5377,6 +5751,8 @@ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + "trim-trailing-lines": ["trim-trailing-lines@2.1.0", "", {}, "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg=="], + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], @@ -5397,6 +5773,8 @@ "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "turbo": ["turbo@2.10.2", "", { "optionalDependencies": { "@turbo/darwin-64": "2.10.2", "@turbo/darwin-arm64": "2.10.2", "@turbo/linux-64": "2.10.2", "@turbo/linux-arm64": "2.10.2", "@turbo/windows-64": "2.10.2", "@turbo/windows-arm64": "2.10.2" }, "bin": { "turbo": "bin/turbo" } }, "sha512-wTExrNrRjB8qzIcg+ZLm0A3GFNLDsWNwdS/RBXB0FPrBDyzk3i96Yx+TxWZC7a0k1SIreFB8ciUbxjmEqTH8IQ=="], "turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="], @@ -5405,6 +5783,10 @@ "tw-to-css": ["tw-to-css@0.0.12", "", { "dependencies": { "postcss": "8.4.31", "postcss-css-variables": "0.18.0", "tailwindcss": "3.3.2" } }, "sha512-rQAsQvOtV1lBkyCw+iypMygNHrShYAItES5r8fMsrhhaj5qrV2LkZyXc8ccEH+u5bFjHjQ9iuxe90I7Kykf6pw=="], + "twoslash": ["twoslash@0.3.9", "", { "dependencies": { "@typescript/vfs": "^1.6.4", "twoslash-protocol": "0.3.9" }, "peerDependencies": { "typescript": "^5.5.0 || ^6.0.0" } }, "sha512-rDclk+OtzuTX+tnea7DYLCkqGQ3eP0IyfD+kzUJ7t46X/NzlaxwrhecmEBNuSCuEn3V+n1PhcjUUQQ7gUJzX5Q=="], + + "twoslash-protocol": ["twoslash-protocol@0.3.9", "", {}, "sha512-9/iwp+CXOnjFMPQuPL5PkuRbZnDoNpBvtJCLs9t8kDYkL3YHujbvnHfZA1i5fApDftVEdBw+T/4F+dH5kIzpYQ=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], @@ -5433,6 +5815,8 @@ "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="], + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], "undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="], @@ -5449,16 +5833,22 @@ "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], + "unist-builder": ["unist-builder@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg=="], + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + "unist-util-map": ["unist-util-map@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-HJs1tpkSmRJUzj6fskQrS5oYhBYlmtcvy4SepdDEEsL04FjBrgF0Mgggvxc1/qGBGgW7hRh9+UBK1aqTEnBpIA=="], + "unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="], "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], + "unist-util-remove": ["unist-util-remove@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg=="], + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], @@ -5491,6 +5881,10 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="], + + "urlpattern-polyfill": ["urlpattern-polyfill@10.0.0", "", {}, "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], @@ -5501,6 +5895,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="], + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="], @@ -5519,6 +5915,8 @@ "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + "vfile-matter": ["vfile-matter@5.0.1", "", { "dependencies": { "vfile": "^6.0.0", "yaml": "^2.0.0" } }, "sha512-o6roP82AiX0XfkyTHyRCMXgHfltUNlXSEqCIS80f+mbAyiQBE2fxtDVMtseyytGx75sihiJFo/zR6r/4LTs2Cw=="], + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], "vite": ["vite@7.1.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw=="], @@ -5633,6 +6031,8 @@ "xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], + "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], + "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -5655,6 +6055,10 @@ "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], + + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], @@ -5759,6 +6163,10 @@ "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@alcalzone/ansi-tokenize/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "@astrojs/cloudflare/vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], @@ -5781,6 +6189,12 @@ "@astrojs/starlight/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@asyncapi/parser/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "@asyncapi/parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="], + "@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="], "@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="], @@ -5971,6 +6385,8 @@ "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "@jsx-email/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@jsx-email/cli/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], @@ -5985,8 +6401,120 @@ "@mdx-js/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "@mdx-js/mdx/remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "@mintlify/cli/chalk": ["chalk@5.2.0", "", {}, "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA=="], + + "@mintlify/cli/fs-extra": ["fs-extra@11.2.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw=="], + + "@mintlify/cli/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/cli/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "@mintlify/cli/openid-client": ["openid-client@6.8.2", "", { "dependencies": { "jose": "^6.1.3", "oauth4webapi": "^3.8.4" } }, "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA=="], + + "@mintlify/cli/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "@mintlify/cli/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "@mintlify/cli/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "@mintlify/cli/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], + + "@mintlify/cli/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "@mintlify/common/acorn": ["acorn@8.11.2", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w=="], + + "@mintlify/common/hast-util-to-html": ["hast-util-to-html@9.0.4", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="], + + "@mintlify/common/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/common/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "@mintlify/common/mdast-util-gfm": ["mdast-util-gfm@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw=="], + + "@mintlify/common/mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.1.3", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ=="], + + "@mintlify/common/postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], + + "@mintlify/common/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], + + "@mintlify/common/remark-rehype": ["remark-rehype@11.1.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="], + + "@mintlify/common/sucrase": ["sucrase@3.34.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "7.1.6", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw=="], + + "@mintlify/common/tailwindcss": ["tailwindcss@3.4.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og=="], + + "@mintlify/common/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "@mintlify/common/unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + + "@mintlify/link-rot/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + + "@mintlify/link-rot/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "@mintlify/mdx/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="], + + "@mintlify/mdx/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "@mintlify/mdx/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], + + "@mintlify/prebuild/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + + "@mintlify/prebuild/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + + "@mintlify/prebuild/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/prebuild/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "@mintlify/prebuild/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + + "@mintlify/previewing/chalk": ["chalk@5.2.0", "", {}, "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA=="], + + "@mintlify/previewing/chokidar": ["chokidar@3.5.3", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="], + + "@mintlify/previewing/express": ["express@4.22.0", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ=="], + + "@mintlify/previewing/fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="], + + "@mintlify/previewing/got": ["got@13.0.0", "", { "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", "http2-wrapper": "^2.1.10", "lowercase-keys": "^3.0.0", "p-cancelable": "^3.0.0", "responselike": "^3.0.0" } }, "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA=="], + + "@mintlify/previewing/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/previewing/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "@mintlify/previewing/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + + "@mintlify/previewing/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], + + "@mintlify/scraping/fs-extra": ["fs-extra@11.1.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ=="], + + "@mintlify/scraping/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/scraping/mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.1.3", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ=="], + + "@mintlify/scraping/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], + + "@mintlify/scraping/remark-mdx": ["remark-mdx@3.0.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA=="], + + "@mintlify/scraping/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "@mintlify/scraping/yargs": ["yargs@17.7.1", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw=="], + + "@mintlify/scraping/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], + + "@mintlify/validation/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "@mintlify/validation/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "@mintlify/validation/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + + "@mintlify/validation/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], + + "@mintlify/validation/zod-to-json-schema": ["zod-to-json-schema@3.20.4", "", { "peerDependencies": { "zod": "^3.20.0" } }, "sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg=="], + "@modelcontextprotocol/sdk/hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="], "@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], @@ -6105,6 +6633,12 @@ "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], + "@puppeteer/browsers/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "@puppeteer/browsers/tar-fs": ["tar-fs@3.1.3", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ=="], + + "@puppeteer/browsers/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], @@ -6129,6 +6663,10 @@ "@shikijs/themes/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="], + "@shikijs/twoslash/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], + + "@shikijs/twoslash/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], + "@slack/bolt/express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="], "@slack/bolt/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], @@ -6161,6 +6699,28 @@ "@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="], + "@stoplight/better-ajv-errors/leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "@stoplight/json/jsonc-parser": ["jsonc-parser@2.2.1", "", {}, "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w=="], + + "@stoplight/json/safe-stable-stringify": ["safe-stable-stringify@1.1.1", "", {}, "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw=="], + + "@stoplight/json-ref-readers/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + + "@stoplight/json-ref-resolver/immer": ["immer@9.0.21", "", {}, "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="], + + "@stoplight/spectral-core/@stoplight/types": ["@stoplight/types@13.6.0", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ=="], + + "@stoplight/spectral-core/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "@stoplight/spectral-core/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@stoplight/spectral-functions/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "@stoplight/spectral-parsers/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="], + + "@stoplight/yaml/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="], + "@storybook/csf-plugin/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], @@ -6199,6 +6759,8 @@ "@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="], + "aggregate-error/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="], "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="], @@ -6255,6 +6817,12 @@ "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], + "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + + "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "builder-util/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -6263,6 +6831,8 @@ "c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "chromium-bidi/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -6273,10 +6843,16 @@ "config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "cosmiconfig/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "cosmiconfig/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "crc/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "dir-compare/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -6319,8 +6895,14 @@ "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "engine.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "engine.io/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "engine.io-client/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + "esast-util-from-js/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -6335,6 +6917,8 @@ "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "favicons/xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], + "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], @@ -6345,6 +6929,8 @@ "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], + "gitlab-ai-provider/openai": ["openai@6.39.1", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-z3dO9fEWOXBzlXynVb/xZ/tujzUjFWQWn3C0n0mw6Vo0zJTbEkaN4b2cLWjhJ6haJQx8LlREoafHRl+Gu/Hl+A=="], "gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -6363,6 +6949,26 @@ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "ink/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "ink/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "ink/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "ink/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "ink-spinner/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "is-online/got": ["got@12.6.1", "", { "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", "http2-wrapper": "^2.1.10", "lowercase-keys": "^3.0.0", "p-cancelable": "^3.0.0", "responselike": "^3.0.0" } }, "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ=="], + + "is-online/p-timeout": ["p-timeout@5.1.0", "", {}, "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew=="], + "istanbul-reports/html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], @@ -6371,6 +6977,8 @@ "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "keytar/node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], @@ -6381,6 +6989,8 @@ "micromark-extension-mdxjs/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "micromark-extension-mdxjs/micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], @@ -6397,6 +7007,10 @@ "motion/framer-motion": ["framer-motion@12.40.0", "", { "dependencies": { "motion-dom": "^12.40.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg=="], + "next-mdx-remote-client/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "next-mdx-remote-client/serialize-error": ["serialize-error@13.0.1", "", { "dependencies": { "non-error": "^0.1.0", "type-fest": "^5.4.1" } }, "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA=="], + "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], "nitro/undici": ["undici@7.26.0", "", {}, "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg=="], @@ -6429,12 +7043,18 @@ "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "p-any/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "p-some/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "parse-json/json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -6455,6 +7075,8 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "prebuild-install/node-abi": ["node-abi@3.94.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g=="], + "pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -6463,8 +7085,24 @@ "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + + "proxy-agent/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "public-ip/got": ["got@12.6.1", "", { "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", "http2-wrapper": "^2.1.10", "lowercase-keys": "^3.0.0", "p-cancelable": "^3.0.0", "responselike": "^3.0.0" } }, "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ=="], + + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "react-reconciler/react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], @@ -6481,12 +7119,18 @@ "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + "socket.io/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], + "sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], "sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="], + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -6499,6 +7143,10 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "terser/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], @@ -6517,6 +7165,8 @@ "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], + "unbzip2-stream/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], "unplugin/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], @@ -6561,6 +7211,8 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], "yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], @@ -6645,6 +7297,8 @@ "@astrojs/starlight/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@asyncapi/parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -6727,6 +7381,10 @@ "@hey-api/json-schema-ref-parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], "@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], @@ -6789,6 +7447,160 @@ "@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + "@mintlify/cli/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/cli/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/cli/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "@mintlify/cli/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "@mintlify/cli/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + + "@mintlify/cli/openid-client/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], + + "@mintlify/cli/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@mintlify/cli/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mintlify/common/hast-util-to-html/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + + "@mintlify/common/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/common/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "@mintlify/common/mdast-util-mdx-jsx/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "@mintlify/common/remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "@mintlify/common/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "@mintlify/common/sucrase/glob": ["glob@7.1.6", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="], + + "@mintlify/common/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + + "@mintlify/common/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "@mintlify/common/tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + + "@mintlify/common/tailwindcss/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "@mintlify/common/tailwindcss/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "@mintlify/common/tailwindcss/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "@mintlify/common/tailwindcss/sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "@mintlify/common/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "@mintlify/link-rot/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/link-rot/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@mintlify/link-rot/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], + + "@mintlify/link-rot/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + + "@mintlify/mdx/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], + + "@mintlify/mdx/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@mintlify/mdx/shiki/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], + + "@mintlify/mdx/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], + + "@mintlify/mdx/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="], + + "@mintlify/mdx/shiki/@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], + + "@mintlify/mdx/shiki/@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], + + "@mintlify/mdx/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], + + "@mintlify/prebuild/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/prebuild/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/prebuild/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@mintlify/prebuild/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], + + "@mintlify/prebuild/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + + "@mintlify/previewing/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "@mintlify/previewing/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "@mintlify/previewing/express/body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="], + + "@mintlify/previewing/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + + "@mintlify/previewing/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "@mintlify/previewing/express/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + + "@mintlify/previewing/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "@mintlify/previewing/express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + + "@mintlify/previewing/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + + "@mintlify/previewing/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + + "@mintlify/previewing/express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="], + + "@mintlify/previewing/express/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + + "@mintlify/previewing/express/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + + "@mintlify/previewing/express/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + + "@mintlify/previewing/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + + "@mintlify/previewing/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/previewing/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], + + "@mintlify/previewing/got/@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="], + + "@mintlify/previewing/got/cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "@mintlify/previewing/got/cacheable-request": ["cacheable-request@10.2.14", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.3", "mimic-response": "^4.0.0", "normalize-url": "^8.0.0", "responselike": "^3.0.0" } }, "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ=="], + + "@mintlify/previewing/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="], + + "@mintlify/previewing/got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "@mintlify/previewing/got/http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + + "@mintlify/previewing/got/lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + + "@mintlify/previewing/got/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + + "@mintlify/previewing/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + + "@mintlify/previewing/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/previewing/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@mintlify/previewing/unist-util-visit/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], + + "@mintlify/previewing/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], + + "@mintlify/previewing/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@mintlify/previewing/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mintlify/scraping/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "@mintlify/scraping/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "@mintlify/scraping/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@mintlify/scraping/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mintlify/validation/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], @@ -6899,6 +7711,10 @@ "@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@puppeteer/browsers/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@puppeteer/browsers/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], @@ -6961,6 +7777,8 @@ "@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], + "@storybook/csf-plugin/unplugin/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "@storybook/csf-plugin/unplugin/webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -7035,12 +7853,20 @@ "babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "better-opn/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + + "better-opn/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + + "better-opn/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + "builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "conf/dot-prop/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], + "cosmiconfig/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], @@ -7091,6 +7917,10 @@ "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "engine.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "engine.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], @@ -7101,6 +7931,30 @@ "iconv-corefoundation/cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "inquirer/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], + + "is-online/got/@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="], + + "is-online/got/cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "is-online/got/cacheable-request": ["cacheable-request@10.2.14", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.3", "mimic-response": "^4.0.0", "normalize-url": "^8.0.0", "responselike": "^3.0.0" } }, "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ=="], + + "is-online/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="], + + "is-online/got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "is-online/got/http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + + "is-online/got/lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + + "is-online/got/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + + "is-online/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + "js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "js-beautify/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -7119,6 +7973,8 @@ "motion/framer-motion/motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], + "next-mdx-remote-client/serialize-error/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="], + "opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "opencode/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="], @@ -7133,14 +7989,44 @@ "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], + "prebuild-install/node-abi/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "public-ip/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="], + + "public-ip/got/@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="], + + "public-ip/got/cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="], + + "public-ip/got/cacheable-request": ["cacheable-request@10.2.14", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.2", "get-stream": "^6.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.3", "mimic-response": "^4.0.0", "normalize-url": "^8.0.0", "responselike": "^3.0.0" } }, "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ=="], + + "public-ip/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="], + + "public-ip/got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "public-ip/got/http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="], + + "public-ip/got/lowercase-keys": ["lowercase-keys@3.0.0", "", {}, "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="], + + "public-ip/got/p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="], + + "public-ip/got/responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], + "restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "socket.io/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "socket.io/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -7295,6 +8181,10 @@ "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -7343,6 +8233,64 @@ "@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@mintlify/cli/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/cli/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@mintlify/cli/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@mintlify/cli/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/common/remark-gfm/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "@mintlify/common/sucrase/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@mintlify/common/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "@mintlify/common/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "@mintlify/common/tailwindcss/sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "@mintlify/previewing/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "@mintlify/previewing/express/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "@mintlify/previewing/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "@mintlify/previewing/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "@mintlify/previewing/express/body-parser/qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="], + + "@mintlify/previewing/express/body-parser/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + + "@mintlify/previewing/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "@mintlify/previewing/express/send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "@mintlify/previewing/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + + "@mintlify/previewing/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "@mintlify/previewing/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + + "@mintlify/previewing/got/cacheable-request/normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], + + "@mintlify/previewing/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/previewing/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@mintlify/previewing/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@mintlify/previewing/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/scraping/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mintlify/scraping/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@mintlify/scraping/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@mintlify/scraping/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], "@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -7369,6 +8317,14 @@ "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "@puppeteer/browsers/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@puppeteer/browsers/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@puppeteer/browsers/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], "@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -7393,6 +8349,8 @@ "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], @@ -7451,6 +8409,8 @@ "electron-builder/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "engine.io/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "esbuild-plugin-copy/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -7459,6 +8419,10 @@ "iconv-corefoundation/cli-truncate/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "is-online/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + + "is-online/got/cacheable-request/normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], + "js-beautify/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "js-beautify/glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], @@ -7473,10 +8437,16 @@ "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], + "public-ip/got/cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="], + + "public-ip/got/cacheable-request/normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="], + "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], + "socket.io/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -7517,6 +8487,30 @@ "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "@mintlify/cli/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/cli/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/common/sucrase/glob/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], + + "@mintlify/common/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "@mintlify/previewing/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "@mintlify/previewing/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "@mintlify/previewing/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/previewing/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/scraping/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mintlify/scraping/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@puppeteer/browsers/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@puppeteer/browsers/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@slack/bolt/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -7557,6 +8551,8 @@ "tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "@mintlify/common/sucrase/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], diff --git a/package.json b/package.json index 8ffb18491e..e427ceb127 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "lint": "oxlint", "lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/core/src packages/server/src packages/protocol/src packages/cli/src", "test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml", - "typecheck": "bun turbo typecheck", + "typecheck": "bun turbo typecheck --concurrency=3", + "typecheck:profile": "bun script/profile-typecheck.ts", + "typecheck:profile:packages": "bun script/profile-typecheck-packages.ts", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "postinstall": "bun run --cwd packages/core fix-node-pty", "prepare": "husky", diff --git a/packages/app/src/context/server-sdk.test.ts b/packages/app/src/context/server-sdk.test.ts index 5c26815425..052faf496a 100644 --- a/packages/app/src/context/server-sdk.test.ts +++ b/packages/app/src/context/server-sdk.test.ts @@ -126,9 +126,9 @@ describe("enqueueServerEvent", () => { enqueue(partUpdated("old")) enqueue({ - id: "event", + id: "event-delete", type: "session.deleted", - properties: { sessionID: "session", info: { id: "session" } }, + properties: { sessionID: "session" }, } as Event) enqueue(partUpdated("new")) diff --git a/packages/cli/AGENTS.md b/packages/cli/AGENTS.md index 209f03d623..73bebeb94f 100644 --- a/packages/cli/AGENTS.md +++ b/packages/cli/AGENTS.md @@ -3,96 +3,5 @@ ## Migration context - The TUI is being ported from legacy APIs to the new V2 APIs. New and migrated TUI behavior should use `sdk.client.v2` and the location-scoped data in `packages/tui/src/context/data.tsx` instead of adding dependencies on legacy sync state. -- Preserve established TUI behavior unless the task intentionally changes it. When behavior, copy, keyboard interaction, or layout is unclear, compare the local V2 TUI with the latest released legacy TUI. -- Run both versions in separate Terminal Control sessions and save PNG-only captures at equivalent states: - -```bash -# From packages/cli: local V2 TUI -termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev - -# Released legacy TUI behavior reference -termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest - -termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2.png -termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png -``` - -- Use the same viewport and send equivalent inputs to both sessions before comparing screenshots. The released CLI is a behavioral reference, not a source of V2 API design; keep the local implementation on V2 endpoints. -- Stop both sessions after comparison: `termctrl stop opencode-v2-dev` and `termctrl stop opencode-legacy`. - -## Interactive debugging - -- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI. -- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server. -- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots. -- Use a dedicated session name and do not reuse or kill an unrelated session. - -```bash -termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev -termctrl wait opencode-v2-dev "Ask anything" --timeout 20000 -termctrl show opencode-v2-dev -``` - -- Wait for visible text before interacting instead of relying on fixed sleeps. Use the text expected from the screen under test, such as `Ask anything` or `Connect a provider`. -- Drive the running TUI with `termctrl send`. Prefix typed input with `text:` and send control keys separately so the interaction matches real terminal input. - -```bash -termctrl send opencode-v2-dev 'text:example prompt' enter -termctrl send opencode-v2-dev ctrl-c -``` - -- Use `termctrl show` after each meaningful interaction and inspect the full visible screen for rendering errors, stale state, error toasts, and unexpected exits. -- Save PNG evidence for every user-visible bug and fix. Do not save text captures; inspect the rendered PNG. Write temporary captures outside the repository unless the artifact is intended to be committed. - -```bash -termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2-tui.png -``` - -- For resize-sensitive changes, resize the viewport, wait for the expected content, and capture the screen again: - -```bash -termctrl resize opencode-v2-dev --cols 100 --rows 30 -termctrl show opencode-v2-dev -``` - -- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change. -- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`. -- Always clean up the Terminal Control session when the check is complete: - -```bash -termctrl stop opencode-v2-dev -``` - -## Server/API debugging - -- Use `bun dev api --help` from `packages/cli` to inspect the API debugging command. It sends one request to the V2 server using the same daemon discovery/auth path as the CLI. -- Use `bun dev api` to introspect the server-side data backing the TUI. This is useful when debugging UI bugs: compare what the screen renders with the raw session, message, event, agent, or health data returned by the API to determine whether the bug is in the server state, the client data layer, or the TUI rendering. -- `bun dev api` accepts either an OpenAPI operation ID or a raw HTTP method plus path: - -```bash -bun dev api get /health -bun dev api get /openapi.json -bun dev api --param key=value -``` - -- Pass JSON request bodies with `--data`/`-d`; the command sets `content-type: application/json` automatically unless you provide a header. Add extra headers with `--header`/`-H name:value`. -- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control. -- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters. - -## Debugger - -- To debug the V2 CLI or TUI with Bun's inspector, launch the CLI entrypoint through Terminal Control with an inspector URL, then attach a debugger to that URL: - -```bash -termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \ - bun run --inspect=ws://localhost:6499/ src/index.ts -``` - -- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches. -- Use `termctrl logs opencode-v2-debug` for inspector output or startup failures emitted before the TUI renderer starts. Use `termctrl show` for the visible full-screen TUI. - -## Verification - -- Run `bun typecheck` from `packages/cli` after CLI adapter changes. -- Run `bun typecheck` and `bun test` from `packages/tui` after shared TUI changes. Do not run tests from the repository root. -- Treat automated checks and Terminal Control smoke tests as complementary. For user-visible changes, verify initial render, the changed interaction, Ctrl-C exit behavior, and save a screenshot of the corrected state. +- Preserve established TUI behavior unless the task intentionally changes it. +- Load the `opencode-dev` skill before interactively running, debugging, or verifying opencode's V2 CLI, TUI, or server. diff --git a/packages/cli/script/build.ts b/packages/cli/script/build.ts index 9c8e1e69b5..aa498c8141 100755 --- a/packages/cli/script/build.ts +++ b/packages/cli/script/build.ts @@ -18,7 +18,6 @@ await rm("dist", { recursive: true, force: true }) const singleFlag = process.argv.includes("--single") const baselineFlag = process.argv.includes("--baseline") const skipInstall = process.argv.includes("--skip-install") -const sourcemapsFlag = process.argv.includes("--sourcemaps") const plugin = createSolidTransformPlugin() const allTargets: { @@ -74,7 +73,7 @@ for (const item of targets) { external: ["node-gyp"], format: "esm", minify: true, - sourcemap: sourcemapsFlag ? "linked" : "none", + sourcemap: "inline", splitting: true, compile: { autoloadBunfig: false, diff --git a/packages/cli/src/mini/noninteractive.ts b/packages/cli/src/mini/noninteractive.ts index 97d2fd91b6..8549b36d37 100644 --- a/packages/cli/src/mini/noninteractive.ts +++ b/packages/cli/src/mini/noninteractive.ts @@ -1,14 +1,5 @@ -import type { - EventSubscribeOutput, - OpenCodeClient, -} from "@opencode-ai/client/promise" -import type { - ReasoningPart, - StepFinishPart, - StepStartPart, - TextPart, - ToolPart, -} from "@opencode-ai/sdk/v2" +import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise" +import type { ReasoningPart, StepFinishPart, StepStartPart, TextPart, ToolPart } from "@opencode-ai/sdk/v2" import { SessionMessage } from "@opencode-ai/schema/session-message" import { EOL } from "node:os" import { UI } from "./ui" @@ -169,8 +160,8 @@ export async function runNonInteractivePrompt(input: Input) { } } if ( - event.type === "session.execution.settled" && - event.data.outcome === "interrupted" && + event.type === "session.execution.interrupted" && + event.data.reason === "user" && (interrupted || permissionRejected || questionRejected || formCancelled) ) { return @@ -194,11 +185,12 @@ export async function runNonInteractivePrompt(input: Input) { } if (event.type === "session.text.started") { - starts.set(event.data.textID, { id: partID(event.id), timestamp: time }) + starts.set("text", { id: partID(event.id), timestamp: time }) continue } if (event.type === "session.text.ended") { - const started = starts.get(event.data.textID) + const started = starts.get("text") + starts.delete("text") const part: TextPart = { id: started?.id ?? partID(event.id), sessionID: input.sessionID, @@ -212,18 +204,19 @@ export async function runNonInteractivePrompt(input: Input) { } if (event.type === "session.reasoning.started") { - starts.set(event.data.reasoningID, { id: partID(event.id), timestamp: time }) + starts.set("reasoning", { id: partID(event.id), timestamp: time }) continue } if (event.type === "session.reasoning.ended" && input.thinking) { - const started = starts.get(event.data.reasoningID) + const started = starts.get("reasoning") + starts.delete("reasoning") const part: ReasoningPart = { id: started?.id ?? partID(event.id), sessionID: input.sessionID, messageID: event.data.assistantMessageID, type: "reasoning", text: event.data.text, - metadata: event.data.providerMetadata, + metadata: event.data.state, time: { start: started?.timestamp ?? time, end: time }, } if (emit("reasoning", time, { part })) continue @@ -261,10 +254,10 @@ export async function runNonInteractivePrompt(input: Input) { id: current?.id ?? partID(event.id), timestamp: current?.timestamp ?? time, assistantMessageID: event.data.assistantMessageID, - tool: event.data.tool, + tool: current?.tool ?? "tool", input: event.data.input, raw: current?.raw, - provider: event.data.provider, + provider: { executed: event.data.executed, state: event.data.state }, }) continue } @@ -291,7 +284,7 @@ export async function runNonInteractivePrompt(input: Input) { outputPaths: event.data.outputPaths, result: event.data.result, providerCall: current.provider, - providerResult: event.data.provider, + providerResult: { executed: event.data.executed, state: event.data.resultState }, rawInput: current.raw, }, time: { start: current.timestamp, end: time }, @@ -318,7 +311,7 @@ export async function runNonInteractivePrompt(input: Input) { metadata: { result: event.data.result, providerCall: current.provider, - providerResult: event.data.provider, + providerResult: { executed: event.data.executed, state: event.data.resultState }, rawInput: current.raw, }, time: { start: current.timestamp, end: time }, @@ -353,16 +346,25 @@ export async function runNonInteractivePrompt(input: Input) { if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message) continue } - if (event.type === "session.execution.settled") { - if (event.data.outcome === "failure" && !emittedError && !questionRejected && !formCancelled) { + if (event.type === "session.execution.failed") { + if (!emittedError && !questionRejected && !formCancelled) { emittedError = true process.exitCode = 1 - const error = event.data.error ?? { type: "unknown", message: "Session execution failed" } - if (!emit("error", time, { error })) UI.error(error.message) + if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message) } - if (event.data.outcome === "interrupted" && interrupted) process.exitCode = 130 return } + if (event.type === "session.execution.interrupted") { + if (event.data.reason === "user" && interrupted) process.exitCode = 130 + if (event.data.reason !== "user" && !emittedError) { + emittedError = true + process.exitCode = 1 + const error = { type: "aborted" as const, message: `Session interrupted: ${event.data.reason}` } + if (!emit("error", time, { error })) UI.error(error.message) + } + return + } + if (event.type === "session.execution.succeeded") return } } diff --git a/packages/cli/src/mini/stream-v2.subagent.ts b/packages/cli/src/mini/stream-v2.subagent.ts index ad34f6ecff..4aae8e1278 100644 --- a/packages/cli/src/mini/stream-v2.subagent.ts +++ b/packages/cli/src/mini/stream-v2.subagent.ts @@ -35,54 +35,60 @@ export function outputText(content: ReadonlyArray<{ type: string; text?: string export function legacyTool(input: { sessionID: string messageID: string - callID: string - name: string - state: SessionMessageAssistantTool["state"] - time: SessionMessageAssistantTool["time"] - provider?: SessionMessageAssistantTool["provider"] + tool: SessionMessageAssistantTool }): ToolPart { + const tool = input.tool + const providerCall = + tool.executed === undefined && tool.providerState === undefined + ? undefined + : { executed: tool.executed, state: tool.providerState } + const providerResult = + tool.executed === undefined && tool.providerResultState === undefined + ? undefined + : { executed: tool.executed, state: tool.providerResultState } const base = { - id: `prt_${input.callID}`, + id: `prt_${tool.id}`, sessionID: input.sessionID, messageID: input.messageID, type: "tool" as const, - callID: input.callID, - tool: input.name, + callID: tool.id, + tool: tool.name, } - if (input.state.status === "pending") { + if (tool.state.status === "pending") { return { ...base, - state: { status: "pending", input: {}, raw: input.state.input }, + state: { status: "pending", input: {}, raw: tool.state.input }, } } - if (input.state.status === "running") { + if (tool.state.status === "running") { return { ...base, state: { status: "running", - input: input.state.input, - title: input.name, - metadata: { structured: input.state.structured, content: input.state.content, providerCall: input.provider }, - time: { start: input.time.ran ?? input.time.created }, + input: tool.state.input, + title: tool.name, + metadata: { structured: tool.state.structured, content: tool.state.content, providerCall }, + time: { start: tool.time.ran ?? tool.time.created }, }, } } - if (input.state.status === "completed") { + if (tool.state.status === "completed") { return { ...base, state: { status: "completed", - input: input.state.input, - output: outputText(input.state.content), - title: input.name, + input: tool.state.input, + output: outputText(tool.state.content), + title: tool.name, metadata: { - structured: input.state.structured, - content: input.state.content, - outputPaths: input.state.outputPaths, - result: input.state.result, - providerCall: input.provider, + structured: tool.state.structured, + content: tool.state.content, + outputPaths: tool.state.outputPaths, + result: tool.state.result, + providerCall, + providerResult, }, - time: { start: input.time.ran ?? input.time.created, end: input.time.completed ?? input.time.created }, + time: { start: tool.time.ran ?? tool.time.created, end: tool.time.completed ?? tool.time.created }, }, } } @@ -90,15 +96,16 @@ export function legacyTool(input: { ...base, state: { status: "error", - input: input.state.input, - error: input.state.error.message, + input: tool.state.input, + error: tool.state.error.message, metadata: { - structured: input.state.structured, - content: input.state.content, - result: input.state.result, - providerCall: input.provider, + structured: tool.state.structured, + content: tool.state.content, + result: tool.state.result, + providerCall, + providerResult, }, - time: { start: input.time.ran ?? input.time.created, end: input.time.completed ?? input.time.created }, + time: { start: tool.time.ran ?? tool.time.created, end: tool.time.completed ?? tool.time.created }, }, } } @@ -138,6 +145,7 @@ type ToolTrack = { name: string input: Record started: number + providerState?: Record } type ChildState = { @@ -225,6 +233,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac const hydrationOverflow = new Set() const hydrations = new Map>() let selected: string | undefined + const fragmentKey = (messageID: string, partID: string) => `${messageID}\u0000${partID}` const ensureChild = (sessionID: string): ChildState => { const existing = children.get(sessionID) @@ -305,11 +314,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac const part = legacyTool({ sessionID: child.sessionID, messageID, - callID: item.id, - name: item.name, - state: item.state, - time: item.time, - provider: item.provider, + tool: item, }) if (item.state.status === "pending") return child.callIDs.add(item.id) @@ -339,31 +344,37 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac } if (message.type !== "assistant") continue child.messageIDs.add(message.id) + let textOrdinal = 0 + let reasoningOrdinal = 0 for (const item of message.content) { if (item.type === "text") { - child.text.set(item.id, item.text) - child.projectedText.set(item.id, item.text) - setFrame(child, `text:${item.id}`, { + const id = `text:${textOrdinal++}` + const key = fragmentKey(message.id, id) + child.text.set(key, item.text) + child.projectedText.set(key, item.text) + setFrame(child, key, { kind: "assistant", source: "assistant", text: item.text, phase: "progress", messageID: message.id, - partID: item.id, + partID: id, }) continue } if (item.type === "reasoning") { - child.reasoning.set(item.id, item.text) - child.projectedReasoning.set(item.id, item.text) + const id = `reasoning:${reasoningOrdinal++}` + const key = fragmentKey(message.id, id) + child.reasoning.set(key, item.text) + child.projectedReasoning.set(key, item.text) if (input.thinking) - setFrame(child, `reasoning:${item.id}`, { + setFrame(child, key, { kind: "reasoning", source: "reasoning", text: `Thinking: ${item.text}`, phase: "progress", messageID: message.id, - partID: item.id, + partID: id, }) continue } @@ -467,74 +478,88 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac input.emit() return } + if (event.type === "session.text.started") { + return + } if (event.type === "session.text.delta") { - const projected = child.projectedText.get(event.data.textID) + const id = `text:${event.data.ordinal}` + const key = fragmentKey(event.data.assistantMessageID, id) + const projected = child.projectedText.get(key) const covered = projected?.indexOf(event.data.delta) ?? -1 if (projected && covered >= 0) { - child.projectedText.set(event.data.textID, projected.slice(covered + event.data.delta.length)) + child.projectedText.set(key, projected.slice(covered + event.data.delta.length)) return } - const next = (child.text.get(event.data.textID) ?? "") + event.data.delta - child.text.set(event.data.textID, next) - setFrame(child, `text:${event.data.textID}`, { + const next = (child.text.get(key) ?? "") + event.data.delta + child.text.set(key, next) + setFrame(child, key, { kind: "assistant", source: "assistant", text: next, phase: "progress", messageID: event.data.assistantMessageID, - partID: event.data.textID, + partID: id, }) touch(child, event.created) notifyDetail(child) return } if (event.type === "session.text.ended") { - child.text.set(event.data.textID, event.data.text) - child.projectedText.delete(event.data.textID) - setFrame(child, `text:${event.data.textID}`, { + const id = `text:${event.data.ordinal}` + const key = fragmentKey(event.data.assistantMessageID, id) + child.text.set(key, event.data.text) + child.projectedText.delete(key) + setFrame(child, key, { kind: "assistant", source: "assistant", text: event.data.text, phase: "progress", messageID: event.data.assistantMessageID, - partID: event.data.textID, + partID: id, }) touch(child, event.created) notifyDetail(child) return } + if (event.type === "session.reasoning.started") { + return + } if (event.type === "session.reasoning.delta") { - const projected = child.projectedReasoning.get(event.data.reasoningID) + const id = `reasoning:${event.data.ordinal}` + const key = fragmentKey(event.data.assistantMessageID, id) + const projected = child.projectedReasoning.get(key) const covered = projected?.indexOf(event.data.delta) ?? -1 if (projected && covered >= 0) { - child.projectedReasoning.set(event.data.reasoningID, projected.slice(covered + event.data.delta.length)) + child.projectedReasoning.set(key, projected.slice(covered + event.data.delta.length)) return } - const next = (child.reasoning.get(event.data.reasoningID) ?? "") + event.data.delta - child.reasoning.set(event.data.reasoningID, next) + const next = (child.reasoning.get(key) ?? "") + event.data.delta + child.reasoning.set(key, next) if (!input.thinking) return - setFrame(child, `reasoning:${event.data.reasoningID}`, { + setFrame(child, key, { kind: "reasoning", source: "reasoning", text: `Thinking: ${next}`, phase: "progress", messageID: event.data.assistantMessageID, - partID: event.data.reasoningID, + partID: id, }) notifyDetail(child) return } if (event.type === "session.reasoning.ended") { - child.reasoning.set(event.data.reasoningID, event.data.text) - child.projectedReasoning.delete(event.data.reasoningID) + const id = `reasoning:${event.data.ordinal}` + const key = fragmentKey(event.data.assistantMessageID, id) + child.reasoning.set(key, event.data.text) + child.projectedReasoning.delete(key) if (!input.thinking) return - setFrame(child, `reasoning:${event.data.reasoningID}`, { + setFrame(child, key, { kind: "reasoning", source: "reasoning", text: `Thinking: ${event.data.text}`, phase: "progress", messageID: event.data.assistantMessageID, - partID: event.data.reasoningID, + partID: id, }) notifyDetail(child) return @@ -548,17 +573,19 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac if (child.finishedTools.has(event.data.callID)) return const current = child.tools.get(event.data.callID) child.tools.set(event.data.callID, { - name: event.data.tool, + name: current?.name ?? "tool", input: event.data.input, started: current?.started ?? event.created, + providerState: event.data.state, }) childTool( child, structuredClone({ type: "tool", id: event.data.callID, - name: event.data.tool, - provider: event.data.provider, + name: current?.name ?? "tool", + executed: event.data.executed, + providerState: event.data.state, state: { status: "running", input: event.data.input, structured: {}, content: [] }, time: { created: current?.started ?? event.created, ran: event.created }, }) as SessionMessageAssistantTool, @@ -578,7 +605,9 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac type: "tool", id: event.data.callID, name: current?.name ?? "tool", - provider: event.data.provider, + executed: event.data.executed, + providerState: current?.providerState, + providerResultState: event.data.resultState, state: failed ? { status: "error", @@ -608,6 +637,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac notifyDetail(child) return } + if (event.type === "session.step.ended") return if (event.type === "session.step.failed") { setFrame(child, `error:step:${event.data.assistantMessageID}`, { kind: "error", @@ -620,9 +650,23 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac notifyDetail(child) return } - if (event.type === "session.execution.settled") { + if (event.type === "session.execution.started") { + child.status = "running" + touch(child, event.created) + input.emit() + return + } + if ( + event.type === "session.execution.succeeded" || + event.type === "session.execution.failed" || + event.type === "session.execution.interrupted" + ) { child.status = - event.data.outcome === "success" ? "completed" : event.data.outcome === "interrupted" ? "cancelled" : "error" + event.type === "session.execution.succeeded" + ? "completed" + : event.type === "session.execution.interrupted" + ? "cancelled" + : "error" touch(child, event.created) input.emit() } @@ -644,8 +688,12 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac return { main(event) { + if (event.type === "session.tool.input.started") { + if (event.data.name === "subagent") pendingCalls.set(event.data.callID, {}) + return + } if (event.type === "session.tool.called") { - if (event.data.tool === "subagent") pendingCalls.set(event.data.callID, event.data.input) + if (pendingCalls.has(event.data.callID)) pendingCalls.set(event.data.callID, event.data.input) return } if (event.type === "session.tool.failed") { diff --git a/packages/cli/src/mini/stream-v2.transport.ts b/packages/cli/src/mini/stream-v2.transport.ts index 44e413ce52..a82fea2e58 100644 --- a/packages/cli/src/mini/stream-v2.transport.ts +++ b/packages/cli/src/mini/stream-v2.transport.ts @@ -1,12 +1,8 @@ -import type { - EventSubscribeOutput, - OpenCodeClient, -} from "@opencode-ai/client/promise" +import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise" import type { PermissionRequest, QuestionRequest, SessionMessage, - SessionMessageAssistant, SessionMessageAssistantTool, } from "@opencode-ai/sdk/v2" import { Event } from "@opencode-ai/schema/event" @@ -101,6 +97,7 @@ type ToolState = { input: Record started: number running: boolean + providerState?: Record } type State = { @@ -264,8 +261,7 @@ function shellTerminal( : shell.status === "exited" ? `Shell exited with code ${shell.exit ?? "unknown"}` : `Shell ${shell.status}` - if (!error) - return [shellCommit(callID, command, { text, phase: "progress", toolState: "completed" })] + if (!error) return [shellCommit(callID, command, { text, phase: "progress", toolState: "completed" })] return [ ...(text ? [shellCommit(callID, command, { text, phase: "progress", toolState: "running" })] : []), shellCommit(callID, command, { text: error, phase: "final", toolState: "error", toolError: error }), @@ -310,9 +306,7 @@ async function resolveSelectedModel(input: StreamInput, next: Pick response.model) if (session) return { ...session, variant: next.variant } - const fallback = await input.sdk.model - .default(undefined, { signal: next.signal }) - .then((response) => response.data) + const fallback = await input.sdk.model.default(undefined, { signal: next.signal }).then((response) => response.data) if (!fallback) return return { providerID: fallback.providerID, id: fallback.id, variant: next.variant } } @@ -393,11 +387,7 @@ export async function createSessionTransport(input: StreamInput): Promise= 0) { @@ -643,13 +642,14 @@ export async function createSessionTransport(input: StreamInput): Promise previous.length) write([ @@ -659,15 +659,19 @@ export async function createSessionTransport(input: StreamInput): Promise= 0) { @@ -684,13 +688,14 @@ export async function createSessionTransport(input: StreamInput): Promise previous.length) write([ @@ -700,7 +705,7 @@ export async function createSessionTransport(input: StreamInput): Promise 0 ? total.toLocaleString() : "" write([], { - phase: event.data.finish === "tool-calls" ? "running" : "idle", usage: event.data.cost ? `${usage} · ${money.format(event.data.cost)}` : usage, }) return @@ -802,21 +809,33 @@ export async function createSessionTransport(input: StreamInput): Promise>["data"] export type SessionGetOperation = (input: Endpoint4_3Input) => Effect.Effect -type Endpoint4_4Request = Parameters[0] -export type Endpoint4_4Input = { - readonly sessionID: Endpoint4_4Request["params"]["sessionID"] - readonly messageID?: Endpoint4_4Request["payload"]["messageID"] -} -export type Endpoint4_4Output = EffectValue>["data"] -export type SessionForkOperation = (input: Endpoint4_4Input) => Effect.Effect +type Endpoint4_4Request = Parameters[0] +export type Endpoint4_4Input = { readonly sessionID: Endpoint4_4Request["params"]["sessionID"] } +export type Endpoint4_4Output = EffectValue> +export type SessionRemoveOperation = (input: Endpoint4_4Input) => Effect.Effect -type Endpoint4_5Request = Parameters[0] +type Endpoint4_5Request = Parameters[0] export type Endpoint4_5Input = { readonly sessionID: Endpoint4_5Request["params"]["sessionID"] - readonly agent: Endpoint4_5Request["payload"]["agent"] + readonly messageID?: Endpoint4_5Request["payload"]["messageID"] } -export type Endpoint4_5Output = EffectValue> -export type SessionSwitchAgentOperation = (input: Endpoint4_5Input) => Effect.Effect +export type Endpoint4_5Output = EffectValue>["data"] +export type SessionForkOperation = (input: Endpoint4_5Input) => Effect.Effect -type Endpoint4_6Request = Parameters[0] +type Endpoint4_6Request = Parameters[0] export type Endpoint4_6Input = { readonly sessionID: Endpoint4_6Request["params"]["sessionID"] - readonly model: Endpoint4_6Request["payload"]["model"] + readonly agent: Endpoint4_6Request["payload"]["agent"] } -export type Endpoint4_6Output = EffectValue> -export type SessionSwitchModelOperation = (input: Endpoint4_6Input) => Effect.Effect +export type Endpoint4_6Output = EffectValue> +export type SessionSwitchAgentOperation = (input: Endpoint4_6Input) => Effect.Effect -type Endpoint4_7Request = Parameters[0] +type Endpoint4_7Request = Parameters[0] export type Endpoint4_7Input = { readonly sessionID: Endpoint4_7Request["params"]["sessionID"] - readonly title: Endpoint4_7Request["payload"]["title"] + readonly model: Endpoint4_7Request["payload"]["model"] } -export type Endpoint4_7Output = EffectValue> -export type SessionRenameOperation = (input: Endpoint4_7Input) => Effect.Effect +export type Endpoint4_7Output = EffectValue> +export type SessionSwitchModelOperation = (input: Endpoint4_7Input) => Effect.Effect -type Endpoint4_8Request = Parameters[0] +type Endpoint4_8Request = Parameters[0] export type Endpoint4_8Input = { readonly sessionID: Endpoint4_8Request["params"]["sessionID"] - readonly id?: Endpoint4_8Request["payload"]["id"] - readonly prompt: Endpoint4_8Request["payload"]["prompt"] - readonly delivery?: Endpoint4_8Request["payload"]["delivery"] - readonly resume?: Endpoint4_8Request["payload"]["resume"] + readonly title: Endpoint4_8Request["payload"]["title"] } -export type Endpoint4_8Output = EffectValue>["data"] -export type SessionPromptOperation = (input: Endpoint4_8Input) => Effect.Effect +export type Endpoint4_8Output = EffectValue> +export type SessionRenameOperation = (input: Endpoint4_8Input) => Effect.Effect -type Endpoint4_9Request = Parameters[0] +type Endpoint4_9Request = Parameters[0] export type Endpoint4_9Input = { readonly sessionID: Endpoint4_9Request["params"]["sessionID"] readonly id?: Endpoint4_9Request["payload"]["id"] - readonly command: Endpoint4_9Request["payload"]["command"] - readonly arguments?: Endpoint4_9Request["payload"]["arguments"] - readonly agent?: Endpoint4_9Request["payload"]["agent"] - readonly model?: Endpoint4_9Request["payload"]["model"] - readonly files?: Endpoint4_9Request["payload"]["files"] - readonly agents?: Endpoint4_9Request["payload"]["agents"] + readonly prompt: Endpoint4_9Request["payload"]["prompt"] readonly delivery?: Endpoint4_9Request["payload"]["delivery"] readonly resume?: Endpoint4_9Request["payload"]["resume"] } -export type Endpoint4_9Output = EffectValue>["data"] -export type SessionCommandOperation = (input: Endpoint4_9Input) => Effect.Effect +export type Endpoint4_9Output = EffectValue>["data"] +export type SessionPromptOperation = (input: Endpoint4_9Input) => Effect.Effect -type Endpoint4_10Request = Parameters[0] +type Endpoint4_10Request = Parameters[0] export type Endpoint4_10Input = { readonly sessionID: Endpoint4_10Request["params"]["sessionID"] readonly id?: Endpoint4_10Request["payload"]["id"] - readonly skill: Endpoint4_10Request["payload"]["skill"] + readonly command: Endpoint4_10Request["payload"]["command"] + readonly arguments?: Endpoint4_10Request["payload"]["arguments"] + readonly agent?: Endpoint4_10Request["payload"]["agent"] + readonly model?: Endpoint4_10Request["payload"]["model"] + readonly files?: Endpoint4_10Request["payload"]["files"] + readonly agents?: Endpoint4_10Request["payload"]["agents"] + readonly delivery?: Endpoint4_10Request["payload"]["delivery"] readonly resume?: Endpoint4_10Request["payload"]["resume"] } -export type Endpoint4_10Output = EffectValue> -export type SessionSkillOperation = (input: Endpoint4_10Input) => Effect.Effect +export type Endpoint4_10Output = EffectValue>["data"] +export type SessionCommandOperation = (input: Endpoint4_10Input) => Effect.Effect -type Endpoint4_11Request = Parameters[0] +type Endpoint4_11Request = Parameters[0] export type Endpoint4_11Input = { readonly sessionID: Endpoint4_11Request["params"]["sessionID"] - readonly text: Endpoint4_11Request["payload"]["text"] - readonly description?: Endpoint4_11Request["payload"]["description"] - readonly metadata?: Endpoint4_11Request["payload"]["metadata"] + readonly id?: Endpoint4_11Request["payload"]["id"] + readonly skill: Endpoint4_11Request["payload"]["skill"] + readonly resume?: Endpoint4_11Request["payload"]["resume"] } -export type Endpoint4_11Output = EffectValue> -export type SessionSyntheticOperation = (input: Endpoint4_11Input) => Effect.Effect +export type Endpoint4_11Output = EffectValue> +export type SessionSkillOperation = (input: Endpoint4_11Input) => Effect.Effect -type Endpoint4_12Request = Parameters[0] +type Endpoint4_12Request = Parameters[0] export type Endpoint4_12Input = { readonly sessionID: Endpoint4_12Request["params"]["sessionID"] - readonly id?: Endpoint4_12Request["payload"]["id"] - readonly command: Endpoint4_12Request["payload"]["command"] + readonly text: Endpoint4_12Request["payload"]["text"] + readonly description?: Endpoint4_12Request["payload"]["description"] + readonly metadata?: Endpoint4_12Request["payload"]["metadata"] } -export type Endpoint4_12Output = EffectValue> -export type SessionShellOperation = (input: Endpoint4_12Input) => Effect.Effect +export type Endpoint4_12Output = EffectValue> +export type SessionSyntheticOperation = (input: Endpoint4_12Input) => Effect.Effect -type Endpoint4_13Request = Parameters[0] -export type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] } -export type Endpoint4_13Output = EffectValue> -export type SessionCompactOperation = (input: Endpoint4_13Input) => Effect.Effect - -type Endpoint4_14Request = Parameters[0] -export type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] } -export type Endpoint4_14Output = EffectValue> -export type SessionWaitOperation = (input: Endpoint4_14Input) => Effect.Effect - -type Endpoint4_15Request = Parameters[0] -export type Endpoint4_15Input = { - readonly sessionID: Endpoint4_15Request["params"]["sessionID"] - readonly messageID: Endpoint4_15Request["payload"]["messageID"] - readonly files?: Endpoint4_15Request["payload"]["files"] +type Endpoint4_13Request = Parameters[0] +export type Endpoint4_13Input = { + readonly sessionID: Endpoint4_13Request["params"]["sessionID"] + readonly id?: Endpoint4_13Request["payload"]["id"] + readonly command: Endpoint4_13Request["payload"]["command"] } -export type Endpoint4_15Output = EffectValue>["data"] -export type SessionRevertStageOperation = (input: Endpoint4_15Input) => Effect.Effect +export type Endpoint4_13Output = EffectValue> +export type SessionShellOperation = (input: Endpoint4_13Input) => Effect.Effect -type Endpoint4_16Request = Parameters[0] -export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] } -export type Endpoint4_16Output = EffectValue> -export type SessionRevertClearOperation = (input: Endpoint4_16Input) => Effect.Effect +type Endpoint4_14Request = Parameters[0] +export type Endpoint4_14Input = { + readonly sessionID: Endpoint4_14Request["params"]["sessionID"] + readonly id?: Endpoint4_14Request["payload"]["id"] +} +export type Endpoint4_14Output = EffectValue>["data"] +export type SessionCompactOperation = (input: Endpoint4_14Input) => Effect.Effect -type Endpoint4_17Request = Parameters[0] +type Endpoint4_15Request = Parameters[0] +export type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] } +export type Endpoint4_15Output = EffectValue> +export type SessionWaitOperation = (input: Endpoint4_15Input) => Effect.Effect + +type Endpoint4_16Request = Parameters[0] +export type Endpoint4_16Input = { + readonly sessionID: Endpoint4_16Request["params"]["sessionID"] + readonly messageID: Endpoint4_16Request["payload"]["messageID"] + readonly files?: Endpoint4_16Request["payload"]["files"] +} +export type Endpoint4_16Output = EffectValue>["data"] +export type SessionRevertStageOperation = (input: Endpoint4_16Input) => Effect.Effect + +type Endpoint4_17Request = Parameters[0] export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] } -export type Endpoint4_17Output = EffectValue> -export type SessionRevertCommitOperation = (input: Endpoint4_17Input) => Effect.Effect +export type Endpoint4_17Output = EffectValue> +export type SessionRevertClearOperation = (input: Endpoint4_17Input) => Effect.Effect -type Endpoint4_18Request = Parameters[0] +type Endpoint4_18Request = Parameters[0] export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } -export type Endpoint4_18Output = EffectValue>["data"] -export type SessionContextOperation = (input: Endpoint4_18Input) => Effect.Effect +export type Endpoint4_18Output = EffectValue> +export type SessionRevertCommitOperation = (input: Endpoint4_18Input) => Effect.Effect -type Endpoint4_19Request = Parameters[0] +type Endpoint4_19Request = Parameters[0] export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } -export type Endpoint4_19Output = EffectValue< +export type Endpoint4_19Output = EffectValue>["data"] +export type SessionContextOperation = (input: Endpoint4_19Input) => Effect.Effect + +type Endpoint4_20Request = Parameters[0] +export type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } +export type Endpoint4_20Output = EffectValue< ReturnType >["data"] export type SessionInstructionsEntryListOperation = ( - input: Endpoint4_19Input, -) => Effect.Effect - -type Endpoint4_20Request = Parameters[0] -export type Endpoint4_20Input = { - readonly sessionID: Endpoint4_20Request["params"]["sessionID"] - readonly key: Endpoint4_20Request["params"]["key"] - readonly value: Endpoint4_20Request["payload"]["value"] -} -export type Endpoint4_20Output = EffectValue> -export type SessionInstructionsEntryPutOperation = ( input: Endpoint4_20Input, ) => Effect.Effect -type Endpoint4_21Request = Parameters[0] +type Endpoint4_21Request = Parameters[0] export type Endpoint4_21Input = { readonly sessionID: Endpoint4_21Request["params"]["sessionID"] readonly key: Endpoint4_21Request["params"]["key"] + readonly value: Endpoint4_21Request["payload"]["value"] } -export type Endpoint4_21Output = EffectValue< - ReturnType -> -export type SessionInstructionsEntryRemoveOperation = ( +export type Endpoint4_21Output = EffectValue> +export type SessionInstructionsEntryPutOperation = ( input: Endpoint4_21Input, ) => Effect.Effect -type Endpoint4_22Request = Parameters[0] +type Endpoint4_22Request = Parameters[0] export type Endpoint4_22Input = { readonly sessionID: Endpoint4_22Request["params"]["sessionID"] - readonly after?: Endpoint4_22Request["query"]["after"] - readonly follow?: Endpoint4_22Request["query"]["follow"] + readonly key: Endpoint4_22Request["params"]["key"] } -export type Endpoint4_22Output = StreamValue>> -export type SessionLogOperation = (input: Endpoint4_22Input) => Stream.Stream +export type Endpoint4_22Output = EffectValue< + ReturnType +> +export type SessionInstructionsEntryRemoveOperation = ( + input: Endpoint4_22Input, +) => Effect.Effect -type Endpoint4_23Request = Parameters[0] -export type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] } -export type Endpoint4_23Output = EffectValue> -export type SessionInterruptOperation = (input: Endpoint4_23Input) => Effect.Effect +type Endpoint4_23Request = Parameters[0] +export type Endpoint4_23Input = { + readonly sessionID: Endpoint4_23Request["params"]["sessionID"] + readonly after?: Endpoint4_23Request["query"]["after"] + readonly follow?: Endpoint4_23Request["query"]["follow"] +} +export type Endpoint4_23Output = StreamValue>> +export type SessionLogOperation = (input: Endpoint4_23Input) => Stream.Stream -type Endpoint4_24Request = Parameters[0] +type Endpoint4_24Request = Parameters[0] export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] } -export type Endpoint4_24Output = EffectValue> -export type SessionBackgroundOperation = (input: Endpoint4_24Input) => Effect.Effect +export type Endpoint4_24Output = EffectValue> +export type SessionInterruptOperation = (input: Endpoint4_24Input) => Effect.Effect -type Endpoint4_25Request = Parameters[0] -export type Endpoint4_25Input = { - readonly sessionID: Endpoint4_25Request["params"]["sessionID"] - readonly messageID: Endpoint4_25Request["params"]["messageID"] +type Endpoint4_25Request = Parameters[0] +export type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } +export type Endpoint4_25Output = EffectValue> +export type SessionBackgroundOperation = (input: Endpoint4_25Input) => Effect.Effect + +type Endpoint4_26Request = Parameters[0] +export type Endpoint4_26Input = { + readonly sessionID: Endpoint4_26Request["params"]["sessionID"] + readonly messageID: Endpoint4_26Request["params"]["messageID"] } -export type Endpoint4_25Output = EffectValue>["data"] -export type SessionMessageOperation = (input: Endpoint4_25Input) => Effect.Effect +export type Endpoint4_26Output = EffectValue>["data"] +export type SessionMessageOperation = (input: Endpoint4_26Input) => Effect.Effect export interface SessionApi { readonly list: SessionListOperation readonly create: SessionCreateOperation readonly active: SessionActiveOperation readonly get: SessionGetOperation + readonly remove: SessionRemoveOperation readonly fork: SessionForkOperation readonly switchAgent: SessionSwitchAgentOperation readonly switchModel: SessionSwitchModelOperation @@ -729,7 +738,7 @@ export type Endpoint20_1Input = { readonly location?: Endpoint20_1Request["query"]["location"] readonly command: Endpoint20_1Request["payload"]["command"] readonly cwd?: Endpoint20_1Request["payload"]["cwd"] - readonly timeout?: Endpoint20_1Request["payload"]["timeout"] + readonly timeout: Endpoint20_1Request["payload"]["timeout"] readonly metadata?: Endpoint20_1Request["payload"]["metadata"] } export type Endpoint20_1Output = EffectValue> @@ -743,28 +752,38 @@ export type Endpoint20_2Input = { export type Endpoint20_2Output = EffectValue> export type ShellGetOperation = (input: Endpoint20_2Input) => Effect.Effect -type Endpoint20_3Request = Parameters[0] +type Endpoint20_3Request = Parameters[0] export type Endpoint20_3Input = { readonly id: Endpoint20_3Request["params"]["id"] readonly location?: Endpoint20_3Request["query"]["location"] - readonly cursor?: Endpoint20_3Request["query"]["cursor"] - readonly limit?: Endpoint20_3Request["query"]["limit"] + readonly timeout: Endpoint20_3Request["payload"]["timeout"] } -export type Endpoint20_3Output = EffectValue> -export type ShellOutputOperation = (input: Endpoint20_3Input) => Effect.Effect +export type Endpoint20_3Output = EffectValue> +export type ShellTimeoutOperation = (input: Endpoint20_3Input) => Effect.Effect -type Endpoint20_4Request = Parameters[0] +type Endpoint20_4Request = Parameters[0] export type Endpoint20_4Input = { readonly id: Endpoint20_4Request["params"]["id"] readonly location?: Endpoint20_4Request["query"]["location"] + readonly cursor?: Endpoint20_4Request["query"]["cursor"] + readonly limit?: Endpoint20_4Request["query"]["limit"] } -export type Endpoint20_4Output = EffectValue> -export type ShellRemoveOperation = (input: Endpoint20_4Input) => Effect.Effect +export type Endpoint20_4Output = EffectValue> +export type ShellOutputOperation = (input: Endpoint20_4Input) => Effect.Effect + +type Endpoint20_5Request = Parameters[0] +export type Endpoint20_5Input = { + readonly id: Endpoint20_5Request["params"]["id"] + readonly location?: Endpoint20_5Request["query"]["location"] +} +export type Endpoint20_5Output = EffectValue> +export type ShellRemoveOperation = (input: Endpoint20_5Input) => Effect.Effect export interface ShellApi { readonly list: ShellListOperation readonly create: ShellCreateOperation readonly get: ShellGetOperation + readonly timeout: ShellTimeoutOperation readonly output: ShellOutputOperation readonly remove: ShellRemoveOperation } diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index 514f70a923..945bd0ffc0 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -95,56 +95,61 @@ const Endpoint4_3 = (raw: RawClient["server.session"]) => (input: Endpoint4_3Inp Effect.map((value) => value.data), ) -type Endpoint4_4Request = Parameters[0] -type Endpoint4_4Input = { - readonly sessionID: Endpoint4_4Request["params"]["sessionID"] - readonly messageID?: Endpoint4_4Request["payload"]["messageID"] -} +type Endpoint4_4Request = Parameters[0] +type Endpoint4_4Input = { readonly sessionID: Endpoint4_4Request["params"]["sessionID"] } const Endpoint4_4 = (raw: RawClient["server.session"]) => (input: Endpoint4_4Input) => + raw["session.remove"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_5Request = Parameters[0] +type Endpoint4_5Input = { + readonly sessionID: Endpoint4_5Request["params"]["sessionID"] + readonly messageID?: Endpoint4_5Request["payload"]["messageID"] +} +const Endpoint4_5 = (raw: RawClient["server.session"]) => (input: Endpoint4_5Input) => raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint4_5Request = Parameters[0] -type Endpoint4_5Input = { - readonly sessionID: Endpoint4_5Request["params"]["sessionID"] - readonly agent: Endpoint4_5Request["payload"]["agent"] +type Endpoint4_6Request = Parameters[0] +type Endpoint4_6Input = { + readonly sessionID: Endpoint4_6Request["params"]["sessionID"] + readonly agent: Endpoint4_6Request["payload"]["agent"] } -const Endpoint4_5 = (raw: RawClient["server.session"]) => (input: Endpoint4_5Input) => +const Endpoint4_6 = (raw: RawClient["server.session"]) => (input: Endpoint4_6Input) => raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_6Request = Parameters[0] -type Endpoint4_6Input = { - readonly sessionID: Endpoint4_6Request["params"]["sessionID"] - readonly model: Endpoint4_6Request["payload"]["model"] +type Endpoint4_7Request = Parameters[0] +type Endpoint4_7Input = { + readonly sessionID: Endpoint4_7Request["params"]["sessionID"] + readonly model: Endpoint4_7Request["payload"]["model"] } -const Endpoint4_6 = (raw: RawClient["server.session"]) => (input: Endpoint4_6Input) => +const Endpoint4_7 = (raw: RawClient["server.session"]) => (input: Endpoint4_7Input) => raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_7Request = Parameters[0] -type Endpoint4_7Input = { - readonly sessionID: Endpoint4_7Request["params"]["sessionID"] - readonly title: Endpoint4_7Request["payload"]["title"] +type Endpoint4_8Request = Parameters[0] +type Endpoint4_8Input = { + readonly sessionID: Endpoint4_8Request["params"]["sessionID"] + readonly title: Endpoint4_8Request["payload"]["title"] } -const Endpoint4_7 = (raw: RawClient["server.session"]) => (input: Endpoint4_7Input) => +const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Input) => raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_8Request = Parameters[0] -type Endpoint4_8Input = { - readonly sessionID: Endpoint4_8Request["params"]["sessionID"] - readonly id?: Endpoint4_8Request["payload"]["id"] - readonly prompt: Endpoint4_8Request["payload"]["prompt"] - readonly delivery?: Endpoint4_8Request["payload"]["delivery"] - readonly resume?: Endpoint4_8Request["payload"]["resume"] +type Endpoint4_9Request = Parameters[0] +type Endpoint4_9Input = { + readonly sessionID: Endpoint4_9Request["params"]["sessionID"] + readonly id?: Endpoint4_9Request["payload"]["id"] + readonly prompt: Endpoint4_9Request["payload"]["prompt"] + readonly delivery?: Endpoint4_9Request["payload"]["delivery"] + readonly resume?: Endpoint4_9Request["payload"]["resume"] } -const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Input) => +const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) => raw["session.prompt"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] }, @@ -153,20 +158,20 @@ const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Inp Effect.map((value) => value.data), ) -type Endpoint4_9Request = Parameters[0] -type Endpoint4_9Input = { - readonly sessionID: Endpoint4_9Request["params"]["sessionID"] - readonly id?: Endpoint4_9Request["payload"]["id"] - readonly command: Endpoint4_9Request["payload"]["command"] - readonly arguments?: Endpoint4_9Request["payload"]["arguments"] - readonly agent?: Endpoint4_9Request["payload"]["agent"] - readonly model?: Endpoint4_9Request["payload"]["model"] - readonly files?: Endpoint4_9Request["payload"]["files"] - readonly agents?: Endpoint4_9Request["payload"]["agents"] - readonly delivery?: Endpoint4_9Request["payload"]["delivery"] - readonly resume?: Endpoint4_9Request["payload"]["resume"] +type Endpoint4_10Request = Parameters[0] +type Endpoint4_10Input = { + readonly sessionID: Endpoint4_10Request["params"]["sessionID"] + readonly id?: Endpoint4_10Request["payload"]["id"] + readonly command: Endpoint4_10Request["payload"]["command"] + readonly arguments?: Endpoint4_10Request["payload"]["arguments"] + readonly agent?: Endpoint4_10Request["payload"]["agent"] + readonly model?: Endpoint4_10Request["payload"]["model"] + readonly files?: Endpoint4_10Request["payload"]["files"] + readonly agents?: Endpoint4_10Request["payload"]["agents"] + readonly delivery?: Endpoint4_10Request["payload"]["delivery"] + readonly resume?: Endpoint4_10Request["payload"]["resume"] } -const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) => +const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) => raw["session.command"]({ params: { sessionID: input["sessionID"] }, payload: { @@ -185,61 +190,67 @@ const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Inp Effect.map((value) => value.data), ) -type Endpoint4_10Request = Parameters[0] -type Endpoint4_10Input = { - readonly sessionID: Endpoint4_10Request["params"]["sessionID"] - readonly id?: Endpoint4_10Request["payload"]["id"] - readonly skill: Endpoint4_10Request["payload"]["skill"] - readonly resume?: Endpoint4_10Request["payload"]["resume"] +type Endpoint4_11Request = Parameters[0] +type Endpoint4_11Input = { + readonly sessionID: Endpoint4_11Request["params"]["sessionID"] + readonly id?: Endpoint4_11Request["payload"]["id"] + readonly skill: Endpoint4_11Request["payload"]["skill"] + readonly resume?: Endpoint4_11Request["payload"]["resume"] } -const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) => +const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) => raw["session.skill"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], skill: input["skill"], resume: input["resume"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_11Request = Parameters[0] -type Endpoint4_11Input = { - readonly sessionID: Endpoint4_11Request["params"]["sessionID"] - readonly text: Endpoint4_11Request["payload"]["text"] - readonly description?: Endpoint4_11Request["payload"]["description"] - readonly metadata?: Endpoint4_11Request["payload"]["metadata"] +type Endpoint4_12Request = Parameters[0] +type Endpoint4_12Input = { + readonly sessionID: Endpoint4_12Request["params"]["sessionID"] + readonly text: Endpoint4_12Request["payload"]["text"] + readonly description?: Endpoint4_12Request["payload"]["description"] + readonly metadata?: Endpoint4_12Request["payload"]["metadata"] } -const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) => +const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) => raw["session.synthetic"]({ params: { sessionID: input["sessionID"] }, payload: { text: input["text"], description: input["description"], metadata: input["metadata"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_12Request = Parameters[0] -type Endpoint4_12Input = { - readonly sessionID: Endpoint4_12Request["params"]["sessionID"] - readonly id?: Endpoint4_12Request["payload"]["id"] - readonly command: Endpoint4_12Request["payload"]["command"] +type Endpoint4_13Request = Parameters[0] +type Endpoint4_13Input = { + readonly sessionID: Endpoint4_13Request["params"]["sessionID"] + readonly id?: Endpoint4_13Request["payload"]["id"] + readonly command: Endpoint4_13Request["payload"]["command"] } -const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) => +const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) => raw["session.shell"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], command: input["command"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_13Request = Parameters[0] -type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] } -const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) => - raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) - -type Endpoint4_14Request = Parameters[0] -type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] } +type Endpoint4_14Request = Parameters[0] +type Endpoint4_14Input = { + readonly sessionID: Endpoint4_14Request["params"]["sessionID"] + readonly id?: Endpoint4_14Request["payload"]["id"] +} const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) => + raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe( + Effect.mapError(mapClientError), + Effect.map((value) => value.data), + ) + +type Endpoint4_15Request = Parameters[0] +type Endpoint4_15Input = { readonly sessionID: Endpoint4_15Request["params"]["sessionID"] } +const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) => raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_15Request = Parameters[0] -type Endpoint4_15Input = { - readonly sessionID: Endpoint4_15Request["params"]["sessionID"] - readonly messageID: Endpoint4_15Request["payload"]["messageID"] - readonly files?: Endpoint4_15Request["payload"]["files"] +type Endpoint4_16Request = Parameters[0] +type Endpoint4_16Input = { + readonly sessionID: Endpoint4_16Request["params"]["sessionID"] + readonly messageID: Endpoint4_16Request["payload"]["messageID"] + readonly files?: Endpoint4_16Request["payload"]["files"] } -const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) => +const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) => raw["session.revert.stage"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"], files: input["files"] }, @@ -248,61 +259,61 @@ const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15I Effect.map((value) => value.data), ) -type Endpoint4_16Request = Parameters[0] -type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] } -const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) => - raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) - -type Endpoint4_17Request = Parameters[0] +type Endpoint4_17Request = Parameters[0] type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] } const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) => - raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_18Request = Parameters[0] +type Endpoint4_18Request = Parameters[0] type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] } const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) => + raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_19Request = Parameters[0] +type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } +const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) => raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint4_19Request = Parameters[0] -type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] } -const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) => +type Endpoint4_20Request = Parameters[0] +type Endpoint4_20Input = { readonly sessionID: Endpoint4_20Request["params"]["sessionID"] } +const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) => raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint4_20Request = Parameters[0] -type Endpoint4_20Input = { - readonly sessionID: Endpoint4_20Request["params"]["sessionID"] - readonly key: Endpoint4_20Request["params"]["key"] - readonly value: Endpoint4_20Request["payload"]["value"] +type Endpoint4_21Request = Parameters[0] +type Endpoint4_21Input = { + readonly sessionID: Endpoint4_21Request["params"]["sessionID"] + readonly key: Endpoint4_21Request["params"]["key"] + readonly value: Endpoint4_21Request["payload"]["value"] } -const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) => +const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) => raw["session.instructions.entry.put"]({ params: { sessionID: input["sessionID"], key: input["key"] }, payload: { value: input["value"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_21Request = Parameters[0] -type Endpoint4_21Input = { - readonly sessionID: Endpoint4_21Request["params"]["sessionID"] - readonly key: Endpoint4_21Request["params"]["key"] +type Endpoint4_22Request = Parameters[0] +type Endpoint4_22Input = { + readonly sessionID: Endpoint4_22Request["params"]["sessionID"] + readonly key: Endpoint4_22Request["params"]["key"] } -const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) => +const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) => raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint4_22Request = Parameters[0] -type Endpoint4_22Input = { - readonly sessionID: Endpoint4_22Request["params"]["sessionID"] - readonly after?: Endpoint4_22Request["query"]["after"] - readonly follow?: Endpoint4_22Request["query"]["follow"] +type Endpoint4_23Request = Parameters[0] +type Endpoint4_23Input = { + readonly sessionID: Endpoint4_23Request["params"]["sessionID"] + readonly after?: Endpoint4_23Request["query"]["after"] + readonly follow?: Endpoint4_23Request["query"]["follow"] } -const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) => +const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) => Stream.unwrap( raw["session.log"]({ params: { sessionID: input["sessionID"] }, @@ -313,22 +324,22 @@ const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22I ), ) -type Endpoint4_23Request = Parameters[0] -type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] } -const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) => - raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) - -type Endpoint4_24Request = Parameters[0] +type Endpoint4_24Request = Parameters[0] type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] } const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) => + raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) + +type Endpoint4_25Request = Parameters[0] +type Endpoint4_25Input = { readonly sessionID: Endpoint4_25Request["params"]["sessionID"] } +const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) => raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint4_25Request = Parameters[0] -type Endpoint4_25Input = { - readonly sessionID: Endpoint4_25Request["params"]["sessionID"] - readonly messageID: Endpoint4_25Request["params"]["messageID"] +type Endpoint4_26Request = Parameters[0] +type Endpoint4_26Input = { + readonly sessionID: Endpoint4_26Request["params"]["sessionID"] + readonly messageID: Endpoint4_26Request["params"]["messageID"] } -const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) => +const Endpoint4_26 = (raw: RawClient["server.session"]) => (input: Endpoint4_26Input) => raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), @@ -339,26 +350,27 @@ const adaptGroup4 = (raw: RawClient["server.session"]) => ({ create: Endpoint4_1(raw), active: Endpoint4_2(raw), get: Endpoint4_3(raw), - fork: Endpoint4_4(raw), - switchAgent: Endpoint4_5(raw), - switchModel: Endpoint4_6(raw), - rename: Endpoint4_7(raw), - prompt: Endpoint4_8(raw), - command: Endpoint4_9(raw), - skill: Endpoint4_10(raw), - synthetic: Endpoint4_11(raw), - shell: Endpoint4_12(raw), - compact: Endpoint4_13(raw), - wait: Endpoint4_14(raw), - revertStage: Endpoint4_15(raw), - revertClear: Endpoint4_16(raw), - revertCommit: Endpoint4_17(raw), - context: Endpoint4_18(raw), - instructions: { entry: { list: Endpoint4_19(raw), put: Endpoint4_20(raw), remove: Endpoint4_21(raw) } }, - log: Endpoint4_22(raw), - interrupt: Endpoint4_23(raw), - background: Endpoint4_24(raw), - message: Endpoint4_25(raw), + remove: Endpoint4_4(raw), + fork: Endpoint4_5(raw), + switchAgent: Endpoint4_6(raw), + switchModel: Endpoint4_7(raw), + rename: Endpoint4_8(raw), + prompt: Endpoint4_9(raw), + command: Endpoint4_10(raw), + skill: Endpoint4_11(raw), + synthetic: Endpoint4_12(raw), + shell: Endpoint4_13(raw), + compact: Endpoint4_14(raw), + wait: Endpoint4_15(raw), + revertStage: Endpoint4_16(raw), + revertClear: Endpoint4_17(raw), + revertCommit: Endpoint4_18(raw), + context: Endpoint4_19(raw), + instructions: { entry: { list: Endpoint4_20(raw), put: Endpoint4_21(raw), remove: Endpoint4_22(raw) } }, + log: Endpoint4_23(raw), + interrupt: Endpoint4_24(raw), + background: Endpoint4_25(raw), + message: Endpoint4_26(raw), }) type Endpoint5_0Request = Parameters[0] @@ -877,7 +889,7 @@ type Endpoint20_1Input = { readonly location?: Endpoint20_1Request["query"]["location"] readonly command: Endpoint20_1Request["payload"]["command"] readonly cwd?: Endpoint20_1Request["payload"]["cwd"] - readonly timeout?: Endpoint20_1Request["payload"]["timeout"] + readonly timeout: Endpoint20_1Request["payload"]["timeout"] readonly metadata?: Endpoint20_1Request["payload"]["metadata"] } const Endpoint20_1 = (raw: RawClient["server.shell"]) => (input: Endpoint20_1Input) => @@ -896,25 +908,38 @@ const Endpoint20_2 = (raw: RawClient["server.shell"]) => (input: Endpoint20_2Inp Effect.mapError(mapClientError), ) -type Endpoint20_3Request = Parameters[0] +type Endpoint20_3Request = Parameters[0] type Endpoint20_3Input = { readonly id: Endpoint20_3Request["params"]["id"] readonly location?: Endpoint20_3Request["query"]["location"] - readonly cursor?: Endpoint20_3Request["query"]["cursor"] - readonly limit?: Endpoint20_3Request["query"]["limit"] + readonly timeout: Endpoint20_3Request["payload"]["timeout"] } const Endpoint20_3 = (raw: RawClient["server.shell"]) => (input: Endpoint20_3Input) => + raw["shell.timeout"]({ + params: { id: input["id"] }, + query: { location: input["location"] }, + payload: { timeout: input["timeout"] }, + }).pipe(Effect.mapError(mapClientError)) + +type Endpoint20_4Request = Parameters[0] +type Endpoint20_4Input = { + readonly id: Endpoint20_4Request["params"]["id"] + readonly location?: Endpoint20_4Request["query"]["location"] + readonly cursor?: Endpoint20_4Request["query"]["cursor"] + readonly limit?: Endpoint20_4Request["query"]["limit"] +} +const Endpoint20_4 = (raw: RawClient["server.shell"]) => (input: Endpoint20_4Input) => raw["shell.output"]({ params: { id: input["id"] }, query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint20_4Request = Parameters[0] -type Endpoint20_4Input = { - readonly id: Endpoint20_4Request["params"]["id"] - readonly location?: Endpoint20_4Request["query"]["location"] +type Endpoint20_5Request = Parameters[0] +type Endpoint20_5Input = { + readonly id: Endpoint20_5Request["params"]["id"] + readonly location?: Endpoint20_5Request["query"]["location"] } -const Endpoint20_4 = (raw: RawClient["server.shell"]) => (input: Endpoint20_4Input) => +const Endpoint20_5 = (raw: RawClient["server.shell"]) => (input: Endpoint20_5Input) => raw["shell.remove"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe( Effect.mapError(mapClientError), ) @@ -923,8 +948,9 @@ const adaptGroup20 = (raw: RawClient["server.shell"]) => ({ list: Endpoint20_0(raw), create: Endpoint20_1(raw), get: Endpoint20_2(raw), - output: Endpoint20_3(raw), - remove: Endpoint20_4(raw), + timeout: Endpoint20_3(raw), + output: Endpoint20_4(raw), + remove: Endpoint20_5(raw), }) type Endpoint21_0Request = Parameters[0] diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index e042859b91..697d54d732 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -13,6 +13,8 @@ import type { SessionActiveOutput, SessionGetInput, SessionGetOutput, + SessionRemoveInput, + SessionRemoveOutput, SessionForkInput, SessionForkOutput, SessionSwitchAgentInput, @@ -149,6 +151,8 @@ import type { ShellCreateOutput, ShellGetInput, ShellGetOutput, + ShellTimeoutInput, + ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, @@ -422,6 +426,17 @@ export function make(options: ClientOptions) { }, requestOptions, ).then((value) => value.data), + remove: (input: SessionRemoveInput, requestOptions?: RequestOptions) => + request( + { + method: "DELETE", + path: `/api/session/${encodeURIComponent(input.sessionID)}`, + successStatus: 204, + declaredStatuses: [404, 400, 401], + empty: true, + }, + requestOptions, + ), fork: (input: SessionForkInput, requestOptions?: RequestOptions) => request<{ readonly data: SessionForkOutput }>( { @@ -541,16 +556,17 @@ export function make(options: ClientOptions) { requestOptions, ), compact: (input: SessionCompactInput, requestOptions?: RequestOptions) => - request( + request<{ readonly data: SessionCompactOutput }>( { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`, - successStatus: 204, - declaredStatuses: [404, 409, 503, 500, 400, 401], - empty: true, + body: { id: input["id"] }, + successStatus: 200, + declaredStatuses: [409, 404, 400, 401], + empty: false, }, requestOptions, - ), + ).then((value) => value.data), wait: (input: SessionWaitInput, requestOptions?: RequestOptions) => request( { @@ -1300,6 +1316,19 @@ export function make(options: ClientOptions) { }, requestOptions, ), + timeout: (input: ShellTimeoutInput, requestOptions?: RequestOptions) => + request( + { + method: "PATCH", + path: `/api/shell/${encodeURIComponent(input.id)}/timeout`, + query: { location: input["location"] }, + body: { timeout: input["timeout"] }, + successStatus: 200, + declaredStatuses: [404, 401, 400], + empty: false, + }, + requestOptions, + ), output: (input: ShellOutputInput, requestOptions?: RequestOptions) => request( { diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 5b3d6c165f..7482d23725 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -74,14 +74,6 @@ export type SkillNotFoundError = { export const isSkillNotFoundError = (value: unknown): value is SkillNotFoundError => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SkillNotFoundError" -export type SessionBusyError = { - readonly _tag: "SessionBusyError" - readonly sessionID: string - readonly message: string -} -export const isSessionBusyError = (value: unknown): value is SessionBusyError => - typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionBusyError" - export type ServiceUnavailableError = { readonly _tag: "ServiceUnavailableError" readonly message: string @@ -90,6 +82,14 @@ export type ServiceUnavailableError = { export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError" +export type SessionBusyError = { + readonly _tag: "SessionBusyError" + readonly sessionID: string + readonly message: string +} +export const isSessionBusyError = (value: unknown): value is SessionBusyError => + typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionBusyError" + export type UnknownError = { readonly _tag: "UnknownError" readonly message: string @@ -326,6 +326,7 @@ export type SessionListOutput = { readonly data: ReadonlyArray<{ readonly id: string readonly parentID?: string + readonly fork?: { readonly sessionID: string; readonly messageID?: string } readonly projectID: string readonly agent?: string readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } @@ -388,6 +389,7 @@ export type SessionCreateOutput = { readonly data: { readonly id: string readonly parentID?: string + readonly fork?: { readonly sessionID: string; readonly messageID?: string } readonly projectID: string readonly agent?: string readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } @@ -426,6 +428,7 @@ export type SessionGetOutput = { readonly data: { readonly id: string readonly parentID?: string + readonly fork?: { readonly sessionID: string; readonly messageID?: string } readonly projectID: string readonly agent?: string readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } @@ -456,6 +459,10 @@ export type SessionGetOutput = { } }["data"] +export type SessionRemoveInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] } + +export type SessionRemoveOutput = void + export type SessionForkInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] readonly messageID?: { readonly messageID?: string | undefined }["messageID"] @@ -465,6 +472,7 @@ export type SessionForkOutput = { readonly data: { readonly id: string readonly parentID?: string + readonly fork?: { readonly sessionID: string; readonly messageID?: string } readonly projectID: string readonly agent?: string readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } @@ -871,9 +879,21 @@ export type SessionShellInput = { export type SessionShellOutput = void -export type SessionCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] } +export type SessionCompactInput = { + readonly sessionID: { readonly sessionID: string }["sessionID"] + readonly id?: { readonly id?: string | undefined }["id"] +} -export type SessionCompactOutput = void +export type SessionCompactOutput = { + readonly data: { + readonly type: "compaction" + readonly admittedSeq: number + readonly id: string + readonly sessionID: string + readonly timeCreated: number + readonly handledSeq?: number + } +}["data"] export type SessionWaitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] } @@ -1006,23 +1026,20 @@ export type SessionContextOutput = { readonly agent: string readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string } readonly content: ReadonlyArray< - | { readonly type: "text"; readonly id: string; readonly text: string } + | { readonly type: "text"; readonly text: string } | { readonly type: "reasoning" - readonly id: string readonly text: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } + readonly state?: { readonly [x: string]: JsonValue } readonly time?: { readonly created: number; readonly completed?: number } } | { readonly type: "tool" readonly id: string readonly name: string - readonly provider?: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } - readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } - } + readonly executed?: boolean + readonly providerState?: { readonly [x: string]: JsonValue } + readonly providerResultState?: { readonly [x: string]: JsonValue } readonly state: | { readonly status: "pending"; readonly input: string } | { @@ -1061,7 +1078,7 @@ export type SessionContextOutput = { | { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string } > readonly structured: { readonly [x: string]: JsonValue } - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } readonly result?: JsonValue } readonly time: { @@ -1073,7 +1090,7 @@ export type SessionContextOutput = { } > readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray } - readonly finish?: string + readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" readonly cost?: number readonly tokens?: { readonly input: number @@ -1081,10 +1098,16 @@ export type SessionContextOutput = { readonly reasoning: number readonly cache: { readonly read: number; readonly write: number } } - readonly error?: { readonly type: "unknown"; readonly message: string } + readonly error?: { readonly type: string; readonly message: string } + readonly retry?: { + readonly attempt: number + readonly at: number + readonly error: { readonly type: string; readonly message: string } + } } | { readonly type: "compaction" + readonly status: "queued" | "running" | "completed" | "failed" readonly reason: "auto" | "manual" readonly summary: string readonly recent: string @@ -1167,6 +1190,15 @@ export type SessionLogOutput = readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly title: string } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.deleted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } | { readonly id: string readonly created: number @@ -1213,6 +1245,45 @@ export type SessionLogOutput = readonly delivery: "steer" | "queue" } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.started" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.succeeded" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.failed" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { + readonly sessionID: string + readonly error: { readonly type: string; readonly message: string } + } + } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.interrupted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string; readonly reason: "user" | "shutdown" | "superseded" } + } | { readonly id: string readonly created: number @@ -1322,7 +1393,7 @@ export type SessionLogOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly finish: string + readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" readonly cost: number readonly tokens: { readonly input: number @@ -1344,7 +1415,7 @@ export type SessionLogOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } } } | { @@ -1354,7 +1425,7 @@ export type SessionLogOutput = readonly type: "session.text.started" readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string } + readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly ordinal: number } } | { readonly id: string @@ -1366,7 +1437,7 @@ export type SessionLogOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly textID: string + readonly ordinal: number readonly text: string } } @@ -1380,8 +1451,8 @@ export type SessionLogOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly reasoningID: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } + readonly ordinal: number + readonly state?: { readonly [x: string]: unknown } } } | { @@ -1394,9 +1465,9 @@ export type SessionLogOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly reasoningID: string + readonly ordinal: number readonly text: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } + readonly state?: { readonly [x: string]: unknown } } } | { @@ -1438,12 +1509,9 @@ export type SessionLogOutput = readonly sessionID: string readonly assistantMessageID: string readonly callID: string - readonly tool: string readonly input: { readonly [x: string]: unknown } - readonly provider: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } + readonly executed: boolean + readonly state?: { readonly [x: string]: unknown } } } | { @@ -1482,10 +1550,8 @@ export type SessionLogOutput = > readonly outputPaths?: ReadonlyArray readonly result?: unknown - readonly provider: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } + readonly executed: boolean + readonly resultState?: { readonly [x: string]: unknown } } } | { @@ -1499,34 +1565,36 @@ export type SessionLogOutput = readonly sessionID: string readonly assistantMessageID: string readonly callID: string - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } readonly result?: unknown - readonly provider: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } + readonly executed: boolean + readonly resultState?: { readonly [x: string]: unknown } } } | { readonly id: string readonly created: number readonly metadata?: { readonly [x: string]: unknown } - readonly type: "session.retried" + readonly type: "session.retry.scheduled" readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string + readonly assistantMessageID: string readonly attempt: number - readonly error: { - readonly message: string - readonly statusCode?: number - readonly isRetryable: boolean - readonly responseHeaders?: { readonly [x: string]: string } - readonly responseBody?: string - readonly metadata?: { readonly [x: string]: string } - } + readonly at: number + readonly error: { readonly type: string; readonly message: string } } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.compaction.admitted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string; readonly inputID: string } + } | { readonly id: string readonly created: number @@ -1550,6 +1618,15 @@ export type SessionLogOutput = readonly recent: string } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.compaction.failed" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } | { readonly id: string readonly created: number @@ -1590,7 +1667,7 @@ export type SessionLogOutput = readonly type: "session.revert.committed" readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { readonly sessionID: string; readonly messageID: string } + readonly data: { readonly sessionID: string; readonly to: string } } ) | { readonly type: "log.synced"; readonly aggregateID: string; readonly seq?: number } @@ -1703,23 +1780,20 @@ export type SessionMessageOutput = { readonly agent: string readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string } readonly content: ReadonlyArray< - | { readonly type: "text"; readonly id: string; readonly text: string } + | { readonly type: "text"; readonly text: string } | { readonly type: "reasoning" - readonly id: string readonly text: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } + readonly state?: { readonly [x: string]: JsonValue } readonly time?: { readonly created: number; readonly completed?: number } } | { readonly type: "tool" readonly id: string readonly name: string - readonly provider?: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } - readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } - } + readonly executed?: boolean + readonly providerState?: { readonly [x: string]: JsonValue } + readonly providerResultState?: { readonly [x: string]: JsonValue } readonly state: | { readonly status: "pending"; readonly input: string } | { @@ -1758,7 +1832,7 @@ export type SessionMessageOutput = { | { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string } > readonly structured: { readonly [x: string]: JsonValue } - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } readonly result?: JsonValue } readonly time: { @@ -1770,7 +1844,7 @@ export type SessionMessageOutput = { } > readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray } - readonly finish?: string + readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" readonly cost?: number readonly tokens?: { readonly input: number @@ -1778,10 +1852,16 @@ export type SessionMessageOutput = { readonly reasoning: number readonly cache: { readonly read: number; readonly write: number } } - readonly error?: { readonly type: "unknown"; readonly message: string } + readonly error?: { readonly type: string; readonly message: string } + readonly retry?: { + readonly attempt: number + readonly at: number + readonly error: { readonly type: string; readonly message: string } + } } | { readonly type: "compaction" + readonly status: "queued" | "running" | "completed" | "failed" readonly reason: "auto" | "manual" readonly summary: string readonly recent: string @@ -1905,23 +1985,20 @@ export type MessageListOutput = { readonly agent: string readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string } readonly content: ReadonlyArray< - | { readonly type: "text"; readonly id: string; readonly text: string } + | { readonly type: "text"; readonly text: string } | { readonly type: "reasoning" - readonly id: string readonly text: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } + readonly state?: { readonly [x: string]: JsonValue } readonly time?: { readonly created: number; readonly completed?: number } } | { readonly type: "tool" readonly id: string readonly name: string - readonly provider?: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } - readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } - } + readonly executed?: boolean + readonly providerState?: { readonly [x: string]: JsonValue } + readonly providerResultState?: { readonly [x: string]: JsonValue } readonly state: | { readonly status: "pending"; readonly input: string } | { @@ -1960,7 +2037,7 @@ export type MessageListOutput = { | { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string } > readonly structured: { readonly [x: string]: JsonValue } - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } readonly result?: JsonValue } readonly time: { @@ -1972,7 +2049,7 @@ export type MessageListOutput = { } > readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray } - readonly finish?: string + readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" readonly cost?: number readonly tokens?: { readonly input: number @@ -1980,10 +2057,16 @@ export type MessageListOutput = { readonly reasoning: number readonly cache: { readonly read: number; readonly write: number } } - readonly error?: { readonly type: "unknown"; readonly message: string } + readonly error?: { readonly type: string; readonly message: string } + readonly retry?: { + readonly attempt: number + readonly at: number + readonly error: { readonly type: string; readonly message: string } + } } | { readonly type: "compaction" + readonly status: "queued" | "running" | "completed" | "failed" readonly reason: "auto" | "manual" readonly summary: string readonly recent: string @@ -4406,6 +4489,15 @@ export type EventSubscribeOutput = readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string; readonly title: string } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.deleted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } | { readonly id: string readonly created: number @@ -4456,13 +4548,37 @@ export type EventSubscribeOutput = readonly id: string readonly created: number readonly metadata?: { readonly [x: string]: unknown } - readonly type: "session.execution.settled" + readonly type: "session.execution.started" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { - readonly sessionID: string - readonly outcome: "success" | "failure" | "interrupted" - readonly error?: { readonly type: "unknown"; readonly message: string } - } + readonly data: { readonly sessionID: string } + } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.succeeded" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.failed" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string; readonly error: { readonly type: string; readonly message: string } } + } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.execution.interrupted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string; readonly reason: "user" | "shutdown" | "superseded" } } | { readonly id: string @@ -4573,7 +4689,7 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly finish: string + readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" readonly cost: number readonly tokens: { readonly input: number @@ -4595,7 +4711,7 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } } } | { @@ -4605,7 +4721,7 @@ export type EventSubscribeOutput = readonly type: "session.text.started" readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string } + readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly ordinal: number } } | { readonly id: string @@ -4616,7 +4732,7 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly textID: string + readonly ordinal: number readonly delta: string } } @@ -4630,7 +4746,7 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly textID: string + readonly ordinal: number readonly text: string } } @@ -4644,8 +4760,8 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly reasoningID: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } + readonly ordinal: number + readonly state?: { readonly [x: string]: unknown } } } | { @@ -4657,7 +4773,7 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly reasoningID: string + readonly ordinal: number readonly delta: string } } @@ -4671,9 +4787,9 @@ export type EventSubscribeOutput = readonly data: { readonly sessionID: string readonly assistantMessageID: string - readonly reasoningID: string + readonly ordinal: number readonly text: string - readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } } + readonly state?: { readonly [x: string]: unknown } } } | { @@ -4728,12 +4844,9 @@ export type EventSubscribeOutput = readonly sessionID: string readonly assistantMessageID: string readonly callID: string - readonly tool: string readonly input: { readonly [x: string]: unknown } - readonly provider: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } + readonly executed: boolean + readonly state?: { readonly [x: string]: unknown } } } | { @@ -4772,10 +4885,8 @@ export type EventSubscribeOutput = > readonly outputPaths?: ReadonlyArray readonly result?: unknown - readonly provider: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } + readonly executed: boolean + readonly resultState?: { readonly [x: string]: unknown } } } | { @@ -4789,34 +4900,36 @@ export type EventSubscribeOutput = readonly sessionID: string readonly assistantMessageID: string readonly callID: string - readonly error: { readonly type: "unknown"; readonly message: string } + readonly error: { readonly type: string; readonly message: string } readonly result?: unknown - readonly provider: { - readonly executed: boolean - readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } } - } + readonly executed: boolean + readonly resultState?: { readonly [x: string]: unknown } } } | { readonly id: string readonly created: number readonly metadata?: { readonly [x: string]: unknown } - readonly type: "session.retried" + readonly type: "session.retry.scheduled" readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } readonly data: { readonly sessionID: string + readonly assistantMessageID: string readonly attempt: number - readonly error: { - readonly message: string - readonly statusCode?: number - readonly isRetryable: boolean - readonly responseHeaders?: { readonly [x: string]: string } - readonly responseBody?: string - readonly metadata?: { readonly [x: string]: string } - } + readonly at: number + readonly error: { readonly type: string; readonly message: string } } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.compaction.admitted" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string; readonly inputID: string } + } | { readonly id: string readonly created: number @@ -4848,6 +4961,15 @@ export type EventSubscribeOutput = readonly recent: string } } + | { + readonly id: string + readonly created: number + readonly metadata?: { readonly [x: string]: unknown } + readonly type: "session.compaction.failed" + readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } + readonly location?: { readonly directory: string; readonly workspaceID?: string } + readonly data: { readonly sessionID: string } + } | { readonly id: string readonly created: number @@ -4888,7 +5010,7 @@ export type EventSubscribeOutput = readonly type: "session.revert.committed" readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number } readonly location?: { readonly directory: string; readonly workspaceID?: string } - readonly data: { readonly sessionID: string; readonly messageID: string } + readonly data: { readonly sessionID: string; readonly to: string } } | { readonly id: string @@ -5686,25 +5808,25 @@ export type ShellCreateInput = { readonly command: { readonly command: string readonly cwd?: string - readonly timeout?: number + readonly timeout: number readonly metadata?: { readonly [x: string]: JsonValue } }["command"] readonly cwd?: { readonly command: string readonly cwd?: string - readonly timeout?: number + readonly timeout: number readonly metadata?: { readonly [x: string]: JsonValue } }["cwd"] - readonly timeout?: { + readonly timeout: { readonly command: string readonly cwd?: string - readonly timeout?: number + readonly timeout: number readonly metadata?: { readonly [x: string]: JsonValue } }["timeout"] readonly metadata?: { readonly command: string readonly cwd?: string - readonly timeout?: number + readonly timeout: number readonly metadata?: { readonly [x: string]: JsonValue } }["metadata"] } @@ -5762,6 +5884,37 @@ export type ShellGetOutput = { } } +export type ShellTimeoutInput = { + readonly id: { readonly id: string }["id"] + readonly location?: { + readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined + }["location"] + readonly timeout: { readonly timeout: number }["timeout"] +} + +export type ShellTimeoutOutput = { + readonly location: { + readonly directory: string + readonly workspaceID?: string + readonly project: { readonly id: string; readonly directory: string } + } + readonly data: { + readonly id: string + readonly status: "running" | "exited" | "timeout" | "killed" + readonly command: string + readonly cwd: string + readonly shell: string + readonly file: string + readonly pid?: number + readonly exit?: number | "Infinity" | "-Infinity" | "NaN" + readonly metadata: { readonly [x: string]: JsonValue } + readonly time: { + readonly started: number | "Infinity" | "-Infinity" | "NaN" + readonly completed?: number | "Infinity" | "-Infinity" | "NaN" + } + } +} + export type ShellOutputInput = { readonly id: { readonly id: string }["id"] readonly location?: { diff --git a/packages/client/test/effect.test.ts b/packages/client/test/effect.test.ts index 4c5ea512d7..6247d25610 100644 --- a/packages/client/test/effect.test.ts +++ b/packages/client/test/effect.test.ts @@ -140,6 +140,9 @@ test("session methods retain decoded Effect inputs and outputs", async () => { if (url.includes("/prompt")) { return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(admission))) } + if (url.endsWith("/compact")) { + return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(compactionAdmission))) + } if (url.includes("/context")) { return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ data: [] }))) } @@ -148,10 +151,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => { } if (url.endsWith("/api/session/active")) { return Effect.succeed( - HttpClientResponse.fromWeb( - request, - Response.json({ data: { ses_test: { type: "running" } } }), - ), + HttpClientResponse.fromWeb(request, Response.json({ data: { ses_test: { type: "running" } } })), ) } if (request.method === "POST" && url.endsWith("/api/session")) { @@ -161,10 +161,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => { return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 204 }))) } return Effect.succeed( - HttpClientResponse.fromWeb( - request, - Response.json({ data: [session.data], cursor: { next: "next" } }), - ), + HttpClientResponse.fromWeb(request, Response.json({ data: [session.data], cursor: { next: "next" } })), ) }) const result = await Effect.gen(function* () { @@ -268,6 +265,16 @@ const admission = { }, } +const compactionAdmission = { + data: { + type: "compaction", + admittedSeq: 1, + id: "msg_compaction", + sessionID: "ses_test", + timeCreated: 1_717_171_717_000, + }, +} + const modelSwitchedMessage = { id: "msg_model", type: "model-switched", diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index 21b191fd2a..4f2419075f 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -46,7 +46,7 @@ test("exposes every standard HTTP API group", () => { expect(Object.keys(client.file)).toEqual(["read", "list", "find"]) expect(Object.keys(client.vcs)).toEqual(["status", "diff"]) expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"]) - expect(Object.keys(client.shell)).toEqual(["list", "create", "get", "output", "remove"]) + expect(Object.keys(client.shell)).toEqual(["list", "create", "get", "timeout", "output", "remove"]) expect(Object.keys(client.project)).toEqual(["list", "current", "directories"]) }) @@ -240,10 +240,10 @@ test("session methods use the public HTTP contract", async () => { }) } if (url.includes("/prompt")) return Response.json(admission) + if (url.endsWith("/compact")) return Response.json(compactionAdmission) if (url.includes("/context")) return Response.json({ data: [] }) if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage }) - if (url.endsWith("/api/session/active")) - return Response.json({ data: { ses_test: { type: "running" } } }) + if (url.endsWith("/api/session/active")) return Response.json({ data: { ses_test: { type: "running" } } }) if (init?.method === "POST" && url.endsWith("/api/session")) return Response.json(session) if (init?.method === "POST") return new Response(null, { status: 204 }) return Response.json({ data: [session.data], cursor: { next: "next" } }) @@ -364,6 +364,16 @@ const admission = { }, } +const compactionAdmission = { + data: { + type: "compaction", + admittedSeq: 1, + id: "msg_compaction", + sessionID: "ses_test", + timeCreated: 1_717_171_717_000, + }, +} + const modelSwitchedMessage = { id: "msg_model", type: "model-switched", diff --git a/packages/codemode/README.md b/packages/codemode/README.md index b80247e889..b48a58a816 100644 --- a/packages/codemode/README.md +++ b/packages/codemode/README.md @@ -237,8 +237,8 @@ A host cannot define its own `$codemode` top-level namespace. CodeMode executes a deliberately bounded JavaScript subset. It supports: -- Plain data literals, property access, assignment, and destructuring. -- `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references - anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`. +- Plain data literals, property access, assignment, destructuring, and sequence expressions (the comma operator, evaluated left to right with the final value returned). +- `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets, including assignment-form destructuring such as `for ([key, value] of entries)`), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references - anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`. - Arrow functions and function declarations with closures, defaults, rest parameters, and destructuring. - Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`. - Common array, string, number, `Object`, `Math`, and `JSON` operations. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces, including `$codemode`, and `Object.keys(tools.ns)` lists the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`. diff --git a/packages/codemode/src/interpreter/runtime.ts b/packages/codemode/src/interpreter/runtime.ts index 369df4e2fc..4e1d473475 100644 --- a/packages/codemode/src/interpreter/runtime.ts +++ b/packages/codemode/src/interpreter/runtime.ts @@ -1135,7 +1135,7 @@ class Interpreter { } let declaration: { readonly pattern: AstNode; readonly mutable: boolean } | undefined - let assignmentName: string | undefined + let assignment: AstNode | undefined if (left.type === "VariableDeclaration") { const declarations = getArray(left, "declarations") @@ -1145,8 +1145,13 @@ class Interpreter { const declarator = asNode(declarations[0], "declarations[0]") declaration = { pattern: getNode(declarator, "id"), mutable: getString(left, "kind") !== "const" } - } else if (left.type === "Identifier") { - assignmentName = getString(left, "name") + } else if ( + left.type === "Identifier" || + left.type === "MemberExpression" || + left.type === "ArrayPattern" || + left.type === "ObjectPattern" + ) { + assignment = left } else { throw new InterpreterRuntimeError("Unsupported for...of binding.", left) } @@ -1155,8 +1160,8 @@ class Interpreter { if (declaration) { self.pushScope() yield* self.declarePattern(declaration.pattern, value, declaration.mutable, left) - } else if (assignmentName) { - self.setIdentifierValue(assignmentName, value, left) + } else if (assignment) { + yield* self.assignPattern(assignment, value, left) } const result = yield* self.evaluateStatement(body).pipe( @@ -1554,6 +1559,16 @@ class Interpreter { return this.evaluateUnaryExpression(node) case "AssignmentExpression": return this.evaluateAssignmentExpression(node) + case "SequenceExpression": { + const self = this + return Effect.gen(function* () { + let result: unknown + for (const expression of getArray(node, "expressions")) { + result = yield* self.evaluateExpression(asNode(expression, "expressions")) + } + return result + }) + } case "CallExpression": return this.evaluateCallExpression(node) case "ArrowFunctionExpression": diff --git a/packages/codemode/test/parity.test.ts b/packages/codemode/test/parity.test.ts index 1831a7222d..5593831127 100644 --- a/packages/codemode/test/parity.test.ts +++ b/packages/codemode/test/parity.test.ts @@ -464,6 +464,54 @@ describe("H5: builtin coercion functions work as array callbacks", () => { }) }) +describe("for...of assignment destructuring", () => { + test("assigns entry pairs into predeclared variables", async () => { + expect( + await value(` + let key + let item + const out = [] + for ([key, item] of Object.entries({ a: 1, b: 2 })) out.push(key + item) + return { key, item, out } + `), + ).toEqual({ key: "b", item: 2, out: ["a1", "b2"] }) + }) + + test("assigns object patterns and defaults", async () => { + expect( + await value(` + let id + let label + const labels = [] + for ({ id, label = "unknown" } of [{ id: 1 }, { id: 2, label: "two" }]) labels.push(label) + return { id, label, labels } + `), + ).toEqual({ id: 2, label: "two", labels: ["unknown", "two"] }) + }) +}) + +describe("sequence expressions", () => { + test("evaluate left to right and return the final value", async () => { + expect(await value(`let x = 0; const result = (x += 1, x *= 3, x + 2); return { x, result }`)).toEqual({ + x: 3, + result: 5, + }) + }) + + test("support comma-separated for-loop updates", async () => { + expect( + await value(` + const pairs = [] + for (let left = 0, right = 3; left < right; left++, right--) pairs.push([left, right]) + return pairs + `), + ).toEqual([ + [0, 3], + [1, 2], + ]) + }) +}) + describe("destructuring assignment", () => { test("assigns object and array patterns to existing bindings", async () => { expect( diff --git a/packages/core/schema.json b/packages/core/schema.json index 222cc7b399..d419909444 100644 --- a/packages/core/schema.json +++ b/packages/core/schema.json @@ -1,10 +1,8 @@ { "version": "7", "dialect": "sqlite", - "id": "992b24b9-f3e9-41f5-87a5-4917d1423169", - "prevIds": [ - "96e9fe64-660f-4a73-9414-b38bb7eac290" - ], + "id": "b0355fd9-bf41-42e3-9dca-76107de27ecd", + "prevIds": ["95328a41-789d-44de-9643-6ac6ecd6b4ec"], "ddl": [ { "name": "workspace", @@ -1012,13 +1010,23 @@ "autoincrement": false, "default": null, "generated": null, + "name": "type", + "entityType": "columns", + "table": "session_input" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, "name": "prompt", "entityType": "columns", "table": "session_input" }, { "type": "text", - "notNull": true, + "notNull": false, "autoincrement": false, "default": null, "generated": null, @@ -1166,6 +1174,26 @@ "entityType": "columns", "table": "session" }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "fork_session_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "fork_message_id", + "entityType": "columns", + "table": "session" + }, { "type": "text", "notNull": true, @@ -1547,13 +1575,9 @@ "table": "session_share" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1562,13 +1586,9 @@ "table": "workspace" }, { - "columns": [ - "active_account_id" - ], + "columns": ["active_account_id"], "tableTo": "account", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "SET NULL", "nameExplicit": false, @@ -1577,13 +1597,9 @@ "table": "account_state" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "tableTo": "event_sequence", - "columnsTo": [ - "aggregate_id" - ], + "columnsTo": ["aggregate_id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1592,13 +1608,9 @@ "table": "event" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1607,13 +1619,9 @@ "table": "permission" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1622,13 +1630,9 @@ "table": "project_directory" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1637,13 +1641,9 @@ "table": "instruction_checkpoint" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1652,13 +1652,9 @@ "table": "instruction_entry" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1667,13 +1663,9 @@ "table": "message" }, { - "columns": [ - "message_id" - ], + "columns": ["message_id"], "tableTo": "message", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1682,13 +1674,9 @@ "table": "part" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1697,13 +1685,9 @@ "table": "session_input" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1712,13 +1696,9 @@ "table": "session_message" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1727,13 +1707,9 @@ "table": "session" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1742,13 +1718,9 @@ "table": "todo" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1757,184 +1729,140 @@ "table": "session_share" }, { - "columns": [ - "email", - "url" - ], + "columns": ["email", "url"], "nameExplicit": false, "name": "control_account_pk", "entityType": "pks", "table": "control_account" }, { - "columns": [ - "project_id", - "directory" - ], + "columns": ["project_id", "directory"], "nameExplicit": false, "name": "project_directory_pk", "entityType": "pks", "table": "project_directory" }, { - "columns": [ - "session_id", - "key" - ], + "columns": ["session_id", "key"], "nameExplicit": false, "name": "instruction_entry_pk", "entityType": "pks", "table": "instruction_entry" }, { - "columns": [ - "session_id", - "position" - ], + "columns": ["session_id", "position"], "nameExplicit": false, "name": "todo_pk", "entityType": "pks", "table": "todo" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "workspace_pk", "table": "workspace", "entityType": "pks" }, { - "columns": [ - "name" - ], + "columns": ["name"], "nameExplicit": false, "name": "data_migration_pk", "table": "data_migration", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_state_pk", "table": "account_state", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_pk", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "credential_pk", "table": "credential", "entityType": "pks" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "nameExplicit": false, "name": "event_sequence_pk", "table": "event_sequence", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "event_pk", "table": "event", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "permission_pk", "table": "permission", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "project_pk", "table": "project", "entityType": "pks" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "nameExplicit": false, "name": "instruction_checkpoint_pk", "table": "instruction_checkpoint", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "message_pk", "table": "message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "part_pk", "table": "part", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_input_pk", "table": "session_input", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_message_pk", "table": "session_message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_pk", "table": "session", "entityType": "pks" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "nameExplicit": false, "name": "session_share_pk", "table": "session_share", @@ -2066,6 +1994,10 @@ "value": "promoted_seq", "isExpression": false }, + { + "value": "type", + "isExpression": false + }, { "value": "delivery", "isExpression": false @@ -2078,7 +2010,21 @@ "isUnique": false, "where": null, "origin": "manual", - "name": "session_input_session_pending_delivery_seq_idx", + "name": "session_input_session_pending_type_delivery_seq_idx", + "entityType": "indexes", + "table": "session_input" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": true, + "where": "\"session_input\".\"type\" = 'compaction' and \"session_input\".\"promoted_seq\" is null", + "origin": "manual", + "name": "session_input_session_pending_compaction_idx", "entityType": "indexes", "table": "session_input" }, diff --git a/packages/core/src/config/mcp.ts b/packages/core/src/config/mcp.ts index 53e23d6013..9e86fbbe82 100644 --- a/packages/core/src/config/mcp.ts +++ b/packages/core/src/config/mcp.ts @@ -7,8 +7,11 @@ export class Timeout extends Schema.Class("ConfigV2.MCP.Timeout")({ startup: PositiveInt.pipe(Schema.optional).annotate({ description: "Maximum time in milliseconds to establish and initialize the MCP server.", }), - request: PositiveInt.pipe(Schema.optional).annotate({ - description: "Maximum time in milliseconds to wait for MCP catalog/list requests after initialization.", + catalog: PositiveInt.pipe(Schema.optional).annotate({ + description: "Maximum time in milliseconds to wait for MCP discovery requests such as tools/list and prompts/list.", + }), + execution: PositiveInt.pipe(Schema.optional).annotate({ + description: "Maximum time in milliseconds to wait for MCP tool and prompt execution.", }), }) {} diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts index 9d3e4e673f..99ca206c1f 100644 --- a/packages/core/src/database/migration.gen.ts +++ b/packages/core/src/database/migration.gen.ts @@ -44,6 +44,9 @@ export const migrations = ( import("./migration/20260703090000_reset_v2_event_rename_sweep"), import("./migration/20260703181610_event_created_column"), import("./migration/20260703190000_reset_v2_shell_event_payloads"), + import("./migration/20260703200000_reset_v2_session_events"), import("./migration/20260705180000_rename_instructions"), + import("./migration/20260706223930_add-session-fork"), + import("./migration/20260707010146_durable_session_inbox"), ]) ).map((module) => module.default) satisfies DatabaseMigration.Migration[] diff --git a/packages/core/src/database/migration/20260703200000_reset_v2_session_events.ts b/packages/core/src/database/migration/20260703200000_reset_v2_session_events.ts new file mode 100644 index 0000000000..75108b3f11 --- /dev/null +++ b/packages/core/src/database/migration/20260703200000_reset_v2_session_events.ts @@ -0,0 +1,14 @@ +import { Effect } from "effect" +import type { DatabaseMigration } from "../migration" + +export default { + id: "20260703200000_reset_v2_session_events", + up(tx) { + return Effect.gen(function* () { + yield* tx.run(`DELETE FROM \`session_input\`;`) + yield* tx.run(`DELETE FROM \`session_message\`;`) + yield* tx.run(`DELETE FROM \`event\`;`) + yield* tx.run(`DELETE FROM \`event_sequence\`;`) + }) + }, +} satisfies DatabaseMigration.Migration diff --git a/packages/core/src/database/migration/20260706223930_add-session-fork.ts b/packages/core/src/database/migration/20260706223930_add-session-fork.ts new file mode 100644 index 0000000000..2d7b09b34a --- /dev/null +++ b/packages/core/src/database/migration/20260706223930_add-session-fork.ts @@ -0,0 +1,39 @@ +import { Effect } from "effect" +import type { DatabaseMigration } from "../migration" + +export default { + id: "20260706223930_add-session-fork", + up(tx) { + return Effect.gen(function* () { + yield* tx.run(`ALTER TABLE \`session\` ADD \`fork_session_id\` text;`) + yield* tx.run(`ALTER TABLE \`session\` ADD \`fork_message_id\` text;`) + yield* tx.run(` + UPDATE \`session\` + SET + \`parent_id\` = NULL, + \`fork_session_id\` = ( + SELECT json_extract(\`event\`.\`data\`, '$.parentID') + FROM \`event\` + WHERE \`event\`.\`aggregate_id\` = \`session\`.\`id\` + AND \`event\`.\`type\` = 'session.forked' + ORDER BY \`event\`.\`seq\` + LIMIT 1 + ), + \`fork_message_id\` = ( + SELECT json_extract(\`event\`.\`data\`, '$.from') + FROM \`event\` + WHERE \`event\`.\`aggregate_id\` = \`session\`.\`id\` + AND \`event\`.\`type\` = 'session.forked' + ORDER BY \`event\`.\`seq\` + LIMIT 1 + ) + WHERE EXISTS ( + SELECT 1 + FROM \`event\` + WHERE \`event\`.\`aggregate_id\` = \`session\`.\`id\` + AND \`event\`.\`type\` = 'session.forked' + ); + `) + }) + }, +} satisfies DatabaseMigration.Migration diff --git a/packages/core/src/database/migration/20260707010146_durable_session_inbox.ts b/packages/core/src/database/migration/20260707010146_durable_session_inbox.ts new file mode 100644 index 0000000000..e490992620 --- /dev/null +++ b/packages/core/src/database/migration/20260707010146_durable_session_inbox.ts @@ -0,0 +1,43 @@ +import { Effect } from "effect" +import type { DatabaseMigration } from "../migration" + +export default { + id: "20260707010146_durable_session_inbox", + up(tx) { + return Effect.gen(function* () { + yield* tx.run(`PRAGMA foreign_keys=OFF;`) + yield* tx.run(` + CREATE TABLE \`__new_session_input\` ( + \`id\` text PRIMARY KEY, + \`session_id\` text NOT NULL, + \`type\` text NOT NULL, + \`prompt\` text, + \`delivery\` text, + \`admitted_seq\` integer NOT NULL, + \`promoted_seq\` integer, + \`time_created\` integer NOT NULL, + CONSTRAINT \`fk_session_input_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE + ); + `) + yield* tx.run( + `INSERT INTO \`__new_session_input\`(\`id\`, \`session_id\`, \`type\`, \`prompt\`, \`delivery\`, \`admitted_seq\`, \`promoted_seq\`, \`time_created\`) SELECT \`id\`, \`session_id\`, 'prompt', \`prompt\`, \`delivery\`, \`admitted_seq\`, \`promoted_seq\`, \`time_created\` FROM \`session_input\`;`, + ) + yield* tx.run(`DROP TABLE \`session_input\`;`) + yield* tx.run(`ALTER TABLE \`__new_session_input\` RENAME TO \`session_input\`;`) + yield* tx.run(`PRAGMA foreign_keys=ON;`) + yield* tx.run(`DROP INDEX IF EXISTS \`session_input_session_pending_delivery_seq_idx\`;`) + yield* tx.run( + `CREATE INDEX \`session_input_session_pending_type_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`type\`,\`delivery\`,\`admitted_seq\`);`, + ) + yield* tx.run( + `CREATE UNIQUE INDEX \`session_input_session_pending_compaction_idx\` ON \`session_input\` (\`session_id\`) WHERE "session_input"."type" = 'compaction' and "session_input"."promoted_seq" is null;`, + ) + yield* tx.run( + `CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`, + ) + yield* tx.run( + `CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`);`, + ) + }) + }, +} satisfies DatabaseMigration.Migration diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts index dfa24f9b94..0bb6050bf6 100644 --- a/packages/core/src/database/schema.gen.ts +++ b/packages/core/src/database/schema.gen.ts @@ -170,8 +170,9 @@ export default { CREATE TABLE \`session_input\` ( \`id\` text PRIMARY KEY, \`session_id\` text NOT NULL, - \`prompt\` text NOT NULL, - \`delivery\` text NOT NULL, + \`type\` text NOT NULL, + \`prompt\` text, + \`delivery\` text, \`admitted_seq\` integer NOT NULL, \`promoted_seq\` integer, \`time_created\` integer NOT NULL, @@ -196,6 +197,8 @@ export default { \`project_id\` text NOT NULL, \`workspace_id\` text, \`parent_id\` text, + \`fork_session_id\` text, + \`fork_message_id\` text, \`slug\` text NOT NULL, \`directory\` text NOT NULL, \`path\` text, @@ -259,7 +262,10 @@ export default { yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`) yield* tx.run(`CREATE INDEX \`part_session_idx\` ON \`part\` (\`session_id\`);`) yield* tx.run( - `CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`, + `CREATE INDEX \`session_input_session_pending_type_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`type\`,\`delivery\`,\`admitted_seq\`);`, + ) + yield* tx.run( + `CREATE UNIQUE INDEX \`session_input_session_pending_compaction_idx\` ON \`session_input\` (\`session_id\`) WHERE "session_input"."type" = 'compaction' and "session_input"."promoted_seq" is null;`, ) yield* tx.run( `CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`, diff --git a/packages/core/src/mcp/client.ts b/packages/core/src/mcp/client.ts index 02362f6428..0b10ecd7e4 100644 --- a/packages/core/src/mcp/client.ts +++ b/packages/core/src/mcp/client.ts @@ -31,7 +31,8 @@ import { ConfigMCP } from "../config/mcp" import { InstallationVersion } from "../installation/version" const DEFAULT_STARTUP_TIMEOUT = 30_000 -const DEFAULT_REQUEST_TIMEOUT = 30_000 +const DEFAULT_CATALOG_TIMEOUT = 30_000 +const DEFAULT_EXECUTION_TIMEOUT = 12 * 60 * 60 * 1_000 // 12 hours type Transport = StdioClientTransport | StreamableHTTPClientTransport @@ -206,7 +207,8 @@ export const connect = Effect.fnUntraced(function* ( Effect.ignore, ), ) - const requestTimeout = config.timeout?.request ?? DEFAULT_REQUEST_TIMEOUT + const catalogTimeout = config.timeout?.catalog ?? DEFAULT_CATALOG_TIMEOUT + const executionTimeout = config.timeout?.execution ?? DEFAULT_EXECUTION_TIMEOUT return { instructions: client.getInstructions()?.trim() || undefined, tools: () => @@ -218,11 +220,11 @@ export const connect = Effect.fnUntraced(function* ( async (cursor) => { const params = cursor === undefined ? undefined : { cursor } try { - return await client.listTools(params, { timeout: requestTimeout }) + return await client.listTools(params, { timeout: catalogTimeout }) } catch (error) { if (!(error instanceof Error) || !isOutputSchemaError(error)) throw error return client.request({ method: "tools/list", params }, TolerantListToolsResult, { - timeout: requestTimeout, + timeout: catalogTimeout, }) } }, @@ -248,7 +250,7 @@ export const connect = Effect.fnUntraced(function* ( async (cursor) => { const params = cursor === undefined ? undefined : { cursor } return client.request({ method: "prompts/list", params }, TolerantListPromptsResult, { - timeout: requestTimeout, + timeout: catalogTimeout, }) }, (result) => result.prompts, @@ -273,7 +275,7 @@ export const connect = Effect.fnUntraced(function* ( client.request( { method: "prompts/get", params: { name: input.name, arguments: input.args ?? {} } }, GetPromptResultSchema, - { signal }, + { signal, timeout: executionTimeout }, ), catch: (error) => (error instanceof Error ? error : new Error(String(error))), }).pipe( @@ -287,8 +289,8 @@ export const connect = Effect.fnUntraced(function* ( client.callTool( { name: input.name, arguments: input.args ?? {} }, CallToolResultSchema, - // Keep progress tokens available without imposing a client timeout on tool execution. - { signal, resetTimeoutOnProgress: true, onprogress: () => {} }, + // Keep progress tokens available while enforcing a hard wall-clock execution timeout. + { signal, timeout: executionTimeout, onprogress: () => {} }, ), catch: (error) => (error instanceof Error ? error : new Error(String(error))), }).pipe( diff --git a/packages/core/src/permission.ts b/packages/core/src/permission.ts index 15d9047c4f..99743abd2f 100644 --- a/packages/core/src/permission.ts +++ b/packages/core/src/permission.ts @@ -67,7 +67,13 @@ export class CorrectedError extends Schema.TaggedErrorClass()("P export class BlockedError extends Schema.TaggedErrorClass()("PermissionV2.BlockedError", { rules: Permission.Ruleset, -}) {} + permission: Schema.String, + resources: Schema.Array(Schema.String), +}) { + override get message() { + return `Permission denied: ${this.permission}` + } +} export class NotFoundError extends Schema.TaggedErrorClass()("PermissionV2.NotFoundError", { requestID: ID, @@ -201,6 +207,8 @@ const layer = Layer.effect( if (result.effect === "deny") { return yield* new BlockedError({ rules: relevant(input, result.rules), + permission: input.action, + resources: input.resources, }) } if (result.effect === "allow") return diff --git a/packages/core/src/plugin/skill.ts b/packages/core/src/plugin/skill.ts index f6ca2ef4af..91c5297c0e 100644 --- a/packages/core/src/plugin/skill.ts +++ b/packages/core/src/plugin/skill.ts @@ -13,14 +13,14 @@ import { FSUtil } from "../fs-util" import os from "os" import path from "path" import { fileURLToPath } from "url" -import customizeOpencodeContent from "./skill/customize-opencode.md" with { type: "text" } +import opencodeContent from "./skill/opencode.md" with { type: "text" } import reportContent from "./skill/report.md" with { type: "text" } -export const CustomizeOpencodeContent = customizeOpencodeContent +export const OpencodeContent = opencodeContent export const ReportContent = reportContent -const CUSTOMIZE_OPENCODE_DESCRIPTION = - "Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself." +export const OpencodeDescription = + "Use this skill for any question about OpenCode itself, including how OpenCode works, using or configuring it, troubleshooting it, developing plugins or integrations, using the OpenCode SDK, clients, server, or API, and contributing to the OpenCode codebase. Also use it for OpenCode agents, commands, skills, tools, permissions, MCP servers, providers, models, themes, keybinds, formatters, the CLI, TUI, desktop app, and web app." const REPORT_DESCRIPTION = "Use when the user wants to report an opencode issue or bug. Collect standard diagnostics, add user-specific reproduction context, and publish the issue with GitHub CLI." @@ -33,10 +33,10 @@ export const Plugin = define({ SkillV2.EmbeddedSource.make({ type: "embedded", skill: SkillV2.Info.make({ - name: "customize-opencode", - description: CUSTOMIZE_OPENCODE_DESCRIPTION, - location: AbsolutePath.make("/builtin/customize-opencode.md"), - content: CustomizeOpencodeContent, + name: "opencode", + description: OpencodeDescription, + location: AbsolutePath.make("/builtin/opencode.md"), + content: OpencodeContent, }), }), ) diff --git a/packages/core/src/plugin/skill/customize-opencode.md b/packages/core/src/plugin/skill/customize-opencode.md deleted file mode 100644 index 6932dbfd54..0000000000 --- a/packages/core/src/plugin/skill/customize-opencode.md +++ /dev/null @@ -1,452 +0,0 @@ - - -# Customizing opencode - -opencode validates its own config strictly and refuses to start when a field -is wrong. The shapes below cover the common surface area, but they are a -**summary, not the source of truth**. - -## Full schema reference - -The authoritative list of every config option — with field types, enums, -defaults, and descriptions — lives in the published JSON Schema: - -**** - -If a field is not documented in this skill, or you need to confirm an exact -shape before writing config, **fetch that URL and read the schema directly** -rather than guessing. opencode hard-fails on invalid config, so the cost of a -wrong shape is a broken startup. - -Independently, every `opencode.json` should declare -`"$schema": "https://opencode.ai/config.json"` so the user's editor catches -mistakes as they type. - -## Applying changes - -Config is loaded once when opencode starts and is not hot-reloaded. After -saving changes to `opencode.json`, an agent file, a skill, a plugin, or any -other config-time file, **tell the user to quit and restart opencode** for -the changes to take effect. The running session will keep using the -already-loaded config until then. - -## Where files live - -| Scope | Path | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| Project config | `./opencode.json`, `./opencode.jsonc`, or `.opencode/opencode.json` (opencode walks up from the cwd to the worktree root) | -| Global config | `~/.config/opencode/opencode.json` (NOT `~/.opencode/`) | -| Project agents | `.opencode/agent/.md` or `.opencode/agents/.md` | -| Global agents | `~/.config/opencode/agent(s)/.md` | -| Project commands | `.opencode/command/.md` or `.opencode/commands/.md` | -| Global commands | `~/.config/opencode/command(s)/.md` | -| Project skills | `.opencode/skill(s)//SKILL.md` | -| Global skills | `~/.config/opencode/skill(s)//SKILL.md` | -| External skills (auto-loaded) | `~/.claude/skills//SKILL.md`, `~/.agents/skills//SKILL.md` | - -Configs from each scope are deep-merged. Project overrides global. Unknown -top-level keys in `opencode.json` are rejected with `ConfigInvalidError`. - -## opencode.json - -Every field is optional. - -```json -{ - "$schema": "https://opencode.ai/config.json", - "username": "string", - "model": "provider/model-id", - "small_model": "provider/model-id", - "default_agent": "agent-name", - "shell": "/bin/zsh", - "logLevel": "DEBUG" | "INFO" | "WARN" | "ERROR", - "share": "manual" | "auto" | "disabled", - "autoupdate": true | false | "notify", - "snapshot": true, - "instructions": ["AGENTS.md", "docs/style.md"], - - "skills": { - "paths": [".opencode/skills", "/abs/path/to/skills"], - "urls": ["https://example.com/.well-known/skills/"] - }, - - "references": { - "docs": { - "path": "../docs", - "description": "Use for product behavior and documentation conventions" - }, - "sdk": { - "repository": "owner/sdk", - "branch": "main", - "description": "Use for SDK implementation details", - "hidden": true - } - }, - - "agent": { - "my-agent": { - "model": "anthropic/claude-sonnet-4-6", - "mode": "subagent", - "description": "...", - "permission": { "edit": "deny" } - } - }, - - "command": { - "deploy": { "description": "...", "template": "..." } - }, - - "provider": { - "anthropic": { "options": { "apiKey": "..." } } - }, - "disabled_providers": ["openai"], - "enabled_providers": ["anthropic"], - - "mcp": { - "playwright": { - "type": "local", - "command": ["npx", "-y", "@playwright/mcp"], - "enabled": true, - "env": {} - }, - "remote-thing": { - "type": "remote", - "url": "https://...", - "headers": { "Authorization": "Bearer ..." } - } - }, - - "plugin": [ - "opencode-gemini-auth", - "opencode-foo@1.2.3", - "./local-plugin.ts", - ["opencode-bar", { "option": "value" }] - ], - - "permission": { - "edit": "deny", - "bash": { "git *": "allow", "*": "ask" } - }, - - "formatter": false, - "lsp": false, - - "experimental": { - "primary_tools": ["edit"], - "mcp_timeout": 30000 - }, - - "tool_output": { "max_lines": 200, "max_bytes": 8192 }, - - "compaction": { "auto": true, "tail_turns": 15 } -} -``` - -Shape notes worth being explicit about: - -- `model` always carries a provider prefix: `"anthropic/claude-sonnet-4-6"`. -- `skills` is an object with `paths` and/or `urls`, not an array. -- `references` is an object keyed by alias. Each value is a local path, Git repository, or string shorthand. -- `agent` is an object keyed by agent name, not an array. -- `command` is an object keyed by command name, not an array. -- `plugin` is an array of strings or `[name, options]` tuples, not an object. -- `mcp[name].command` is an array of strings, never a single string. `type` is required. -- `permission` is either a string action or an object keyed by tool name. - -## Skills - -opencode's skill loader scans for `**/SKILL.md` inside skill directories. The -file is named `SKILL.md` exactly, and lives in its own folder named after the -skill: - -``` -.opencode/skills/my-skill/SKILL.md -``` - -Frontmatter: - -```markdown ---- -name: my-skill -description: One sentence covering what this skill does AND when to trigger it. Front-load the literal keywords or filenames the user is likely to say. ---- - -# My Skill - -(skill body in markdown: instructions, examples, references) -``` - -- `name` is required, lowercase hyphen-separated, up to 64 chars, and matches the folder name. -- `description` is effectively required: skills without one are filtered out and never surfaced to the model. Cover both _what_ the skill does and _when_ to use it. Write in third person ("Use when...", not "I help with..."). Front-load concrete trigger keywords and filenames; gate with "Use ONLY when..." if the skill should stay quiet on adjacent topics. -- Optional: `license`, `compatibility`, `metadata` (string-string map). - -Register skills from non-default locations via `skills.paths` (scanned -recursively for `**/SKILL.md`) and `skills.urls` (each URL serves a list of -skills). - -## References - -References make local directories and Git repositories outside the active -project available as supporting context. Configure them under `references`, -keyed by the alias used in `@` autocomplete: - -```json -{ - "references": { - "docs": { - "path": "../product-docs", - "description": "Use for product behavior and terminology" - }, - "effect": { - "repository": "Effect-TS/effect", - "branch": "main", - "description": "Use for Effect implementation details" - } - } -} -``` - -Local `path` values may be relative to the declaring config, absolute, or use -`~/`. Git `repository` values accept Git URLs, host/path references, and GitHub -`owner/repo` shorthand; `branch` is optional. Both forms support optional -`description` and `hidden` fields. - -- Only references with a `description` are advertised to agents in system context. -- `hidden: true` removes a reference from TUI `@` autocomplete only. It remains available to agents and by direct path. -- Reference directories are automatically allowed through the external-directory boundary; normal read/edit/tool permissions still apply. -- String shorthand is supported: use `"docs": "../docs"` for local paths or `"effect": "Effect-TS/effect"` for Git repositories. - -## Agents - -Two ways to define an agent. Use the file form for anything non-trivial. - -### Inline (in `opencode.json`) - -```json -{ - "agent": { - "my-reviewer": { - "description": "Reviews PRs for style violations.", - "mode": "subagent", - "model": "anthropic/claude-sonnet-4-6", - "permission": { "edit": "deny", "bash": "ask" }, - "prompt": "You are a strict PR reviewer..." - } - } -} -``` - -### File - -``` -.opencode/agent/my-reviewer.md OR .opencode/agents/my-reviewer.md -``` - -```markdown ---- -description: Reviews PRs for style violations. -mode: subagent -model: anthropic/claude-sonnet-4-6 -permission: - edit: deny - bash: ask ---- - -You are a strict PR reviewer. Focus on... -``` - -The file body becomes the agent's `prompt`. Do not also put `prompt:` in the -frontmatter. - -`mode` is one of `"primary"`, `"subagent"`, `"all"`. - -Allowed top-level frontmatter fields: `name, model, variant, description, mode, -hidden, color, steps, options, permission, disable, temperature, top_p`. Any -unknown field is silently routed into `options`. - -To disable a built-in agent: `agent: { build: { disable: true } }`, or in a -file, `disable: true` in frontmatter. - -`default_agent` must point to a non-hidden, primary-mode agent. - -### Built-in agents - -opencode ships with `build`, `plan`, `general`, `explore`. Hidden internal agents: -`compaction`, `title`, `summary`. To override a built-in's fields, define the -same key in `agent: { : { ... } }`. - -## Commands - -opencode's command loader scans for `**/*.md` inside command directories. The -file is named after the command, and lives directly inside the `command` folder: - -``` -.opencode/command/deploy.md -``` - -Frontmatter: - -```markdown ---- -description: One sentence describing what the command does. -agent: build -model: anthropic/claude-sonnet-4-6 ---- - -(command body in markdown: the prompt opencode runs, with $ARGUMENTS for the user's input) -``` - -- `template` is the command body — everything below the frontmatter — and is required: it is the prompt opencode runs when the command is invoked. Do not also put a `template:` key in the frontmatter. -- `$ARGUMENTS` is replaced with everything the user typed after the command; `$1`, `$2`, … pull individual positional arguments. -- Optional: `description`, `agent`, `model`, `variant`, `subtask`. - -## Plugins - -`plugin:` is an array. Each entry is one of: - -```json -"plugin": [ - "opencode-gemini-auth", // npm spec, latest - "opencode-foo@1.2.3", // npm spec, pinned - "./local-plugin.ts", // file path, relative to the declaring config - "file:///abs/path/plugin.js", // file URL - ["opencode-bar", { "key": "val" }] // tuple form with options -] -``` - -Auto-discovered plugins (no config entry needed): any `*.ts` or `*.js` file in -`.opencode/plugin/` or `.opencode/plugins/`. - -A plugin module exports `default` (or any named export) of type -`Plugin = (input: PluginInput, options?) => Promise`. The export is a -function, not a plain object literal, and the function returns an object -(return `{}` if there is nothing to register). - -```ts -import type { Plugin } from "@opencode-ai/plugin" - -export default (async ({ client, project, directory, $ }) => { - return { - config: (cfg) => { - // cfg is the live merged config; mutate fields here. - }, - "tool.execute.before": async (input, output) => { - // mutate output.args before the tool runs - }, - } -}) satisfies Plugin -``` - -Hook surface (mutate `output` in place; return `void`): - -- `event(input)`: every bus event -- `config(cfg)`: once on init with the merged config -- `chat.message`, `chat.params`, `chat.headers` -- `tool.execute.before`, `tool.execute.after` -- `tool.definition` -- `command.execute.before` -- `shell.env` -- `permission.ask` -- `experimental.chat.messages.transform`, `experimental.chat.system.transform`, - `experimental.session.compacting`, `experimental.compaction.autocontinue`, - `experimental.text.complete` - -Special object-shaped (not callbacks): `tool: { my_tool: { ... } }`, -`auth: { ... }`, `provider: { ... }`. - -## MCP servers - -`mcp:` is an object keyed by server name. Each server is discriminated by -`type`: - -```json -{ - "mcp": { - "playwright": { - "type": "local", - "command": ["npx", "-y", "@playwright/mcp"], - "enabled": true, - "env": { "BROWSER": "chromium" } - }, - "github": { - "type": "remote", - "url": "https://...", - "enabled": true, - "headers": { "Authorization": "Bearer {env:GITHUB_TOKEN}" } - }, - "old-server": { "enabled": false } - } -} -``` - -`command` is an array of strings. `type` is required. Use `enabled: false` to -disable a server inherited from a parent config. String values such as header -tokens support `{env:VAR}` interpolation (and `{file:path}`); the shell-style -`${VAR}` is not substituted. - -## Permissions - -```json -"permission": { - "edit": "deny", - "bash": { "git *": "allow", "rm *": "deny", "*": "ask" }, - "external_directory": { "~/secrets/**": "deny", "*": "allow" } -} -``` - -Actions: `"allow"`, `"ask"`, `"deny"`. - -Per-tool value forms: `"allow"` shorthand (treated as `{"*": "allow"}`), or an -object `{ pattern: action }`. Within an object, **insertion order matters**. -opencode evaluates the LAST matching rule, so put broad rules first and narrow -rules last. - -`permission: "allow"` (a string at the top level) is shorthand for "allow -everything" and is rarely what the user wants. - -Known permission keys: `read, edit, glob, grep, list, bash, task, -external_directory, todowrite, question, webfetch, websearch, lsp, doom_loop, -skill`. Some of these (`todowrite, -question, webfetch, websearch, doom_loop`) only accept a flat -action, not a per-pattern object. - -`external_directory` patterns are filesystem paths (use `~/`, absolute paths, -or globs like `~/projects/**`). - -Per-agent `permission:` overrides top-level `permission:`. Plan Mode lives on -the `plan` agent's permission ruleset (`edit: deny *`). - -## Escape hatches - -When a user's config is broken and opencode won't start, these env vars help: - -- `OPENCODE_DISABLE_PROJECT_CONFIG=1`: skip the project's local `opencode.json` - and start from globals only. Run from the project directory, opencode loads, - the user edits the broken file, then they restart without the flag. -- `OPENCODE_CONFIG=/path/to/file.json`: load an additional explicit config. -- `OPENCODE_CONFIG_CONTENT='{"$schema":"https://opencode.ai/config.json"}'`: - inject inline JSON as a final local-scope merge. -- `OPENCODE_DISABLE_DEFAULT_PLUGINS=1`: skip default plugins. -- `OPENCODE_PURE=1`: skip external plugins entirely. -- `OPENCODE_DISABLE_EXTERNAL_SKILLS=1`, - `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1`: skip the external skill scans under - `~/.claude/` and `~/.agents/`. - -## When proposing edits - -- Validate against the schema before writing. If you are unsure of a field's - exact shape, or the field is not covered in this skill, fetch - `https://opencode.ai/config.json` and read the schema rather than guessing. -- Preserve `$schema` and any existing fields the user did not ask to change. -- For agent, command, skill, and plugin definitions, prefer creating new files - in the correct location over inlining everything in `opencode.json`. -- If the user's existing config is malformed, point them at the env-var escape - hatches above so they can edit from inside opencode without breaking their - session. -- After saving any config change, remind the user to quit and restart opencode - — running sessions keep using the already-loaded config. diff --git a/packages/core/src/plugin/skill/opencode.md b/packages/core/src/plugin/skill/opencode.md new file mode 100644 index 0000000000..fd58be201e --- /dev/null +++ b/packages/core/src/plugin/skill/opencode.md @@ -0,0 +1,112 @@ +# OpenCode + +Use this guide as the starting point for work involving OpenCode itself. It +covers the core concepts needed to configure and customize OpenCode, extend it +with plugins, and build integrations with the OpenCode SDK, clients, and API. + +Full documentation is available at . Consult +it when this overview does not contain enough detail for the task. + +## Configuration + +OpenCode configuration uses JSON or JSONC. Include the published schema so the +user's editor can validate fields and provide autocomplete: + +```jsonc +{ + "$schema": "https://opencode.ai/config.json" +} +``` + +Global configuration lives at `~/.config/opencode/opencode.json(c)` and applies +to every project for that user. Project configuration can live in any directory +as `opencode.json(c)` or `.opencode/opencode.json(c)`, including nested packages +in a monorepo. + +When OpenCode starts, it searches upward from the current directory for project +configuration and merges the files it finds with the global configuration. + +Common configuration fields include `model`, `default_agent`, `permissions`, +`agents`, `commands`, `plugins`, `providers`, `mcp`, `skills`, `instructions`, +`references`, `formatter`, and `lsp`. + +Do not guess field names or shapes. Use + as the source of truth and preserve unrelated +settings when editing an existing file. + +See the [full configuration guide](https://opencode.mintlify.site/config) for +every field, examples, config locations, and links to dedicated feature guides. + +## Service + +OpenCode uses a client-server architecture. Interfaces such as the TUI connect +to a background OpenCode service, which owns sessions, configuration, plugins, +permissions, and tool execution. + +Configuration and related files are typically watched and reloaded while the +service is running. If a change does not appear, restart the service: + +```sh +opencode2 service restart +``` + +Check its status after restarting: + +```sh +opencode2 service status +``` + +## API + +OpenCode exposes an HTTP API from its server. The API is described by an +OpenAPI document available from the running server at `/openapi.json`. + +Use OpenCode's built-in `api` command for local requests. It discovers the same +background server used by the TUI, starts it when necessary, and applies the +server's authentication headers automatically. + +Call an endpoint with an HTTP method and path: + +```sh +opencode2 api get /api/health +``` + +Pass a request body with `--data` or `-d`, and additional headers with +`--header` or `-H`: + +```sh +opencode2 api post /api/example --data '{"key":"value"}' +opencode2 api get /api/example --header 'X-Example:value' +``` + +Request bodies default to `Content-Type: application/json`. When OpenCode is +connected to an explicit server instead of its managed background service, use +the same configured server and authentication context rather than constructing +an unauthenticated request separately. + +See the [full API reference](https://opencode.mintlify.site/api) for available +endpoints, parameters, request bodies, and response schemas. The +raw [OpenAPI specification](https://opencode.mintlify.site/openapi.json) is also +available for code generation and other tooling. + +## Troubleshooting + +OpenCode runs a client and a background server. Start by determining whether a +problem belongs to the client, the shared server, or one project. + +- Check the service with `opencode2 service status` and verify the API with + `opencode2 api get /api/health`. +- Inspect `~/.local/share/opencode/log/opencode.log`. Filter `role=cli` for + client startup and `role=server` for sessions, providers, plugins, + permissions, and tools. +- Run one reproduction with `OPENCODE_LOG_LEVEL=DEBUG` when normal logs are not + sufficient. +- Do not delete or edit the database, service registration, or service config + while diagnosing a problem. Back up persistent data before inspecting it + with external tools. +- Redact API keys, authorization headers, prompts, file contents, and other + sensitive data before sharing diagnostics. + +See the [full troubleshooting guide](https://opencode.mintlify.site/troubleshooting) +for service lifecycle commands, API inspection, log locations, explicit server +connections, issue-reporting details, and local development paths. diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index b26b072dcf..b5d28c4612 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -33,7 +33,6 @@ import { MessageDecodeError } from "./session/error" import { SessionEvent } from "./session/event" import { SessionInput } from "./session/input" import { Snapshot } from "./snapshot" -import { SessionCompaction } from "./session/compaction" import { SessionRevert } from "./session/revert" import { Revert } from "@opencode-ai/schema/revert" import { FSUtil } from "./fs-util" @@ -96,6 +95,7 @@ type CreateInput = CreateBaseInput & ({ location: Location.Ref; parentID?: never } | { parentID: SessionSchema.ID; location?: never }) type CompactInput = { + id?: SessionMessage.ID sessionID: SessionSchema.ID } @@ -125,6 +125,13 @@ export class AttachmentError extends Schema.TaggedErrorClass()( uri: Schema.String, message: Schema.String, }) {} +export class CompactionConflictError extends Schema.TaggedErrorClass()( + "Session.CompactionConflictError", + { + sessionID: SessionSchema.ID, + inputID: SessionMessage.ID, + }, +) {} export class BusyError extends Schema.TaggedErrorClass()("Session.BusyError", { sessionID: SessionSchema.ID, }) {} @@ -140,6 +147,7 @@ export type Error = | OperationUnavailableError | PromptConflictError | AttachmentError + | CompactionConflictError | BusyError | SkillNotFoundError | CommandV2.NotFoundError @@ -153,6 +161,7 @@ export interface Interface { readonly create: (input: CreateInput) => Effect.Effect readonly fork: (input: ForkInput) => Effect.Effect readonly get: (sessionID: SessionSchema.ID) => Effect.Effect + readonly remove: (sessionID: SessionSchema.ID) => Effect.Effect readonly messages: (input: { sessionID: SessionSchema.ID limit?: number @@ -223,7 +232,7 @@ export interface Interface { }) => Effect.Effect readonly compact: ( input: CompactInput, - ) => Effect.Effect + ) => Effect.Effect readonly wait: (id: SessionSchema.ID) => Effect.Effect readonly active: Effect.Effect> readonly background: (sessionID: SessionSchema.ID) => Effect.Effect @@ -363,6 +372,15 @@ const layer = Layer.effect( if (!session) return yield* new NotFoundError({ sessionID }) return session }), + remove: Effect.fn("V2Session.remove")(function* (sessionID) { + yield* result.get(sessionID) + yield* execution.interrupt(sessionID) + yield* execution.awaitIdle(sessionID) + const children = yield* result.list({ parentID: sessionID }) + yield* Effect.forEach(children.data, (child) => result.remove(child.id), { concurrency: 1, discard: true }) + yield* events.publish(SessionEvent.Deleted, { sessionID }) + yield* events.remove(sessionID) + }), list: Effect.fn("V2Session.list")(function* (input = {}) { const direction = input.anchor?.direction ?? "next" const requestedOrder = input.order ?? "desc" @@ -529,7 +547,7 @@ const layer = Layer.effect( if ((yield* execution.active).has(input.sessionID)) yield* execution.awaitIdle(input.sessionID) const started = yield* Effect.gen(function* () { const shell = yield* Shell.Service - return yield* shell.create({ command: input.command, cwd: session.location.directory }) + return yield* shell.create({ command: input.command, cwd: session.location.directory, timeout: 0 }) }).pipe(Effect.provide(locations.get(session.location))) yield* events.publish( SessionEvent.Shell.Started, @@ -616,19 +634,20 @@ const layer = Layer.effect( }) }), compact: Effect.fn("V2Session.compact")(function* (input) { - const session = yield* result.get(input.sessionID) - // TODO: admit manual compaction as durable pending work, like prompt input, instead of rejecting active sessions. - if ((yield* execution.active).has(input.sessionID)) return yield* new BusyError({ sessionID: input.sessionID }) - const context = yield* store.context(input.sessionID) - const compacted = yield* Effect.gen(function* () { - const compaction = yield* SessionCompaction.Service - return yield* compaction.compactManual({ session, messages: context }) + yield* result.get(input.sessionID) + const inputID = input.id ?? SessionMessage.ID.create() + const admitted = yield* SessionInput.admitCompaction(db, events, { + id: inputID, + sessionID: input.sessionID, }).pipe( - Effect.provide(locations.get(session.location)), - Effect.catch(() => Effect.succeed(false)), + Effect.catchDefect((defect) => + defect instanceof SessionInput.LifecycleConflict + ? new CompactionConflictError({ sessionID: input.sessionID, inputID }) + : Effect.die(defect), + ), ) - if (!compacted) return yield* new OperationUnavailableError({ operation: "compact" }) - return undefined + yield* execution.wake(input.sessionID) + return admitted }), wait: Effect.fn("V2Session.wait")(function* (sessionID) { yield* result.get(sessionID) @@ -724,11 +743,7 @@ function synthesizeTerminalShellInfo(started: ShellSchema.Info): ShellSchema.Inf const resolvePrompt = Effect.fn("V2Session.resolvePrompt")(function* (input: PromptInput.Prompt) { const fs = yield* FSUtil.Service const files = input.files - ? yield* Effect.forEach( - input.files, - (file) => materializeAttachment(fs, file), - { concurrency: 8 }, - ) + ? yield* Effect.forEach(input.files, (file) => materializeAttachment(fs, file), { concurrency: 8 }) : undefined return Prompt.make({ text: input.text, agents: input.agents, files }) }) @@ -746,6 +761,7 @@ const materializeAttachment = Effect.fn("V2Session.materializeAttachment")(funct start: undefined, end: undefined, name: undefined, + mime: undefined, } : yield* readFileAttachment(fs, input.uri) if (resolved.bytes.byteLength > MAX_ATTACHMENT_BYTES) @@ -754,11 +770,15 @@ const materializeAttachment = Effect.fn("V2Session.materializeAttachment")(funct message: `Attachment exceeds the ${MAX_ATTACHMENT_BYTES} byte limit: ${input.uri}`, }) - const mime = Mime.detect(resolved.bytes) + const mime = resolved.mime ?? Mime.detect(resolved.bytes) const content = mime === "text/plain" && resolved.start !== undefined ? Buffer.from( - Buffer.from(resolved.bytes).toString("utf8").split("\n").slice(resolved.start - 1, resolved.end).join("\n"), + Buffer.from(resolved.bytes) + .toString("utf8") + .split("\n") + .slice(resolved.start - 1, resolved.end) + .join("\n"), ) : resolved.bytes return FileAttachment.create({ @@ -788,19 +808,38 @@ const readFileAttachment = Effect.fn("V2Session.readFileAttachment")(function* ( }, catch: () => new AttachmentError({ uri, message: `Invalid file URI: ${uri}` }), }) - const info = yield* fs.stat(target).pipe( - Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` })), - ) + const info = yield* fs + .stat(target) + .pipe(Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` }))) + if (info.type === "Directory") { + const entries = yield* fs + .readDirectoryEntries(target) + .pipe(Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` }))) + return { + bytes: Buffer.from( + entries + .filter((entry) => entry.type === "file" || entry.type === "directory") + .sort((a, b) => (a.type === b.type ? a.name.localeCompare(b.name) : a.type === "directory" ? -1 : 1)) + .map((entry) => entry.name + (entry.type === "directory" ? path.sep : "")) + .join("\n"), + ), + source: { type: "uri" as const, uri }, + start: undefined, + end: undefined, + name: path.basename(target), + mime: "application/x-directory", + } + } if (info.type !== "File") return yield* new AttachmentError({ uri, message: `Attachment is not a file: ${uri}` }) if (Number(info.size) > MAX_ATTACHMENT_BYTES) return yield* new AttachmentError({ uri, message: `Attachment exceeds the ${MAX_ATTACHMENT_BYTES} byte limit: ${uri}`, }) - const bytes = yield* fs.readFile(target).pipe( - Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` })), - ) - return { bytes, source: { type: "uri" as const, uri }, start, end, name: path.basename(target) } + const bytes = yield* fs + .readFile(target) + .pipe(Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` }))) + return { bytes, source: { type: "uri" as const, uri }, start, end, name: path.basename(target), mime: undefined } }) function decodeDataURL(uri: string) { diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index 375d6e5266..e90b6455dc 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -217,7 +217,13 @@ const make = (dependencies: Dependencies) => { .pipe( Stream.runForEach((event) => { if (LLMEvent.is.providerError(event)) failed = true - if (LLMEvent.is.textDelta(event)) chunks.push(event.text) + if (LLMEvent.is.textDelta(event)) { + chunks.push(event.text) + return dependencies.events.publish(SessionEvent.Compaction.Delta, { + sessionID: input.sessionID, + text: event.text, + }) + } return Effect.void }), Effect.as(true), @@ -254,7 +260,9 @@ const make = (dependencies: Dependencies) => { if (context === undefined || context <= 0) return false const selected = select(input.messages, config.tokens) if (!selected) return false - const previousSummary = input.messages.find((message) => message.type === "compaction") + const previousSummary = input.messages.find( + (message) => message.type === "compaction" && message.status === "completed", + ) const hasHead = selected.head.length > 0 if (!hasHead && previousSummary?.type !== "compaction" && !input.force) return false const forcedShortContext = input.force && !hasHead diff --git a/packages/core/src/session/error.ts b/packages/core/src/session/error.ts index 68f4fd32a4..7842390f3b 100644 --- a/packages/core/src/session/error.ts +++ b/packages/core/src/session/error.ts @@ -1,6 +1,7 @@ import { Schema } from "effect" import { SessionMessage } from "./message" import { SessionSchema } from "./schema" +import { SessionError } from "@opencode-ai/schema/session-error" export class MessageDecodeError extends Schema.TaggedErrorClass()("Session.MessageDecodeError", { sessionID: SessionSchema.ID, @@ -10,3 +11,20 @@ export class MessageDecodeError extends Schema.TaggedErrorClass()("Session.StepFailedError", { + error: SessionError.Error, +}) { + override get message() { + return this.error.message + } +} + +export class UserInterruptedError extends Schema.TaggedErrorClass()( + "Session.UserInterruptedError", + {}, +) { + override get message() { + return "Session interrupted by user" + } +} diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts index 4e68e6c3d9..c925ee655c 100644 --- a/packages/core/src/session/execution/local.ts +++ b/packages/core/src/session/execution/local.ts @@ -1,4 +1,4 @@ -import { Cause, DateTime, Effect, Exit, Layer } from "effect" +import { Cause, Effect, Exit, Layer } from "effect" import { EventV2 } from "../../event" import { LocationServiceMap } from "../../location-service-map" import { makeGlobalNode } from "../../effect/app-node" @@ -8,6 +8,16 @@ import { SessionRunner } from "../runner" import { SessionSchema } from "../schema" import { SessionStore } from "../store" import { SessionExecution } from "../execution" +import { toSessionError } from "../to-session-error" +import { UserInterruptedError } from "../error" + +export function terminal(exit: Exit.Exit, reason?: "user" | "shutdown" | "superseded") { + if (Exit.isSuccess(exit)) return { type: "succeeded" as const } + if (Cause.hasInterrupts(exit.cause)) return { type: "interrupted" as const, reason: reason ?? "shutdown" } + const failure = Cause.squash(exit.cause) + if (failure instanceof UserInterruptedError) return { type: "interrupted" as const, reason: "user" as const } + return { type: "failed" as const, error: toSessionError(failure) } +} /** Current-process routing for implicit-local Locations. Future remote placement belongs here. */ const layer = Layer.effect( @@ -16,7 +26,23 @@ const layer = Layer.effect( const store = yield* SessionStore.Service const locations = yield* LocationServiceMap.Service const events = yield* EventV2.Service - const coordinator = yield* SessionRunCoordinator.make({ + const reportLifecycle = (sessionID: SessionSchema.ID, effect: Effect.Effect) => + effect.pipe( + Effect.tapCause((cause) => + Cause.hasInterruptsOnly(cause) + ? Effect.void + : Effect.logError("Failed to publish Session execution lifecycle", cause).pipe( + Effect.annotateLogs({ sessionID }), + ), + ), + Effect.asVoid, + ) + const coordinator = yield* SessionRunCoordinator.make< + SessionSchema.ID, + SessionRunner.RunError, + "user" | "shutdown" | "superseded" + >({ + started: (sessionID) => reportLifecycle(sessionID, events.publish(SessionEvent.Execution.Started, { sessionID })), drain: Effect.fnUntraced(function* (sessionID: SessionSchema.ID, force) { const session = yield* store.get(sessionID) if (!session) return yield* Effect.die(new Error(`Session not found: ${sessionID}`)) @@ -29,28 +55,31 @@ const layer = Layer.effect( ), ) }), - // One ExecutionSettled per execution (busy period), covering every coalesced drain. - settled: (sessionID, exit) => - Effect.gen(function* () { - const failure = - Exit.isFailure(exit) && !Cause.hasInterrupts(exit.cause) ? Cause.squash(exit.cause) : undefined - yield* events.publish(SessionEvent.ExecutionSettled, { - sessionID, - outcome: Exit.isSuccess(exit) ? "success" : Cause.hasInterrupts(exit.cause) ? "interrupted" : "failure", - error: - failure !== undefined - ? { type: "unknown", message: failure instanceof Error ? failure.message : String(failure) } - : undefined, - }) - }).pipe( - Effect.catchCause(() => Effect.void), - Effect.asVoid, + // One terminal observation per busy period, covering every coalesced drain. + settled: (sessionID, exit, reason) => + reportLifecycle( + sessionID, + Effect.gen(function* () { + const outcome = terminal(exit, reason) + if (outcome.type === "succeeded") { + yield* events.publish(SessionEvent.Execution.Succeeded, { sessionID }) + return + } + if (outcome.type === "interrupted") { + yield* events.publish(SessionEvent.Execution.Interrupted, { sessionID, reason: outcome.reason }) + return + } + yield* events.publish(SessionEvent.Execution.Failed, { + sessionID, + error: outcome.error, + }) + }), ), }) return SessionExecution.Service.of({ active: coordinator.active, - interrupt: coordinator.interrupt, + interrupt: (sessionID) => coordinator.interrupt(sessionID, "user"), resume: coordinator.run, wake: coordinator.wake, awaitIdle: coordinator.awaitIdle, diff --git a/packages/core/src/session/history.ts b/packages/core/src/session/history.ts index b3f0b6c741..e719366940 100644 --- a/packages/core/src/session/history.ts +++ b/packages/core/src/session/history.ts @@ -1,4 +1,4 @@ -import { and, asc, desc, eq, gt, gte, ne, or } from "drizzle-orm" +import { and, asc, desc, eq, gt, gte, ne, or, sql } from "drizzle-orm" import { Effect, Schema } from "effect" import { Database } from "../database/database" import { MessageDecodeError } from "./error" @@ -14,7 +14,13 @@ export const latestCompaction = Effect.fnUntraced(function* (db: DatabaseService return yield* db .select({ seq: SessionMessageTable.seq }) .from(SessionMessageTable) - .where(and(eq(SessionMessageTable.session_id, sessionID), eq(SessionMessageTable.type, "compaction"))) + .where( + and( + eq(SessionMessageTable.session_id, sessionID), + eq(SessionMessageTable.type, "compaction"), + sql`json_extract(${SessionMessageTable.data}, '$.status') = 'completed'`, + ), + ) .orderBy(desc(SessionMessageTable.seq)) .limit(1) .get() diff --git a/packages/core/src/session/info.ts b/packages/core/src/session/info.ts index 66832750fd..1c8ddf153d 100644 --- a/packages/core/src/session/info.ts +++ b/packages/core/src/session/info.ts @@ -17,6 +17,12 @@ export function fromRow(row: typeof SessionTable.$inferSelect): SessionSchema.In projectID: ProjectV2.ID.make(row.project_id), title: row.title, parentID: row.parent_id ? SessionSchema.ID.make(row.parent_id) : undefined, + fork: row.fork_session_id + ? { + sessionID: SessionSchema.ID.make(row.fork_session_id), + messageID: row.fork_message_id ? SessionMessage.ID.make(row.fork_message_id) : undefined, + } + : undefined, agent: row.agent ? AgentV2.ID.make(row.agent) : undefined, model: row.model ? { diff --git a/packages/core/src/session/input.ts b/packages/core/src/session/input.ts index 72622d32ef..c08439d0c1 100644 --- a/packages/core/src/session/input.ts +++ b/packages/core/src/session/input.ts @@ -2,9 +2,10 @@ export * as SessionInput from "./input" import { and, asc, eq, isNull } from "drizzle-orm" import { DateTime, Effect, Schema } from "effect" -import { Admitted, Delivery } from "@opencode-ai/schema/session-input" +import { Admitted, Compaction, Delivery, Entry, PromptEntry } from "@opencode-ai/schema/session-input" import type { Database } from "../database/database" import type { EventV2 } from "../event" +import { KeyedMutex } from "../effect/keyed-mutex" import { SessionEvent } from "./event" import { SessionMessage } from "./message" import { Prompt } from "@opencode-ai/schema/prompt" @@ -13,30 +14,77 @@ import { SessionInputTable, SessionMessageTable } from "./sql" type DatabaseService = Database.Interface["db"] -export { Admitted, Delivery } +export { Admitted, Compaction, Delivery, Entry, PromptEntry } const decodePrompt = Schema.decodeUnknownSync(Prompt) const encodePrompt = Schema.encodeSync(Prompt) +const inboxLocks = KeyedMutex.makeUnsafe() -const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted => - Admitted.make({ +export class LifecycleConflict extends Schema.TaggedErrorClass()("SessionInput.LifecycleConflict", { + id: SessionMessage.ID, +}) {} + +const fromRow = (row: typeof SessionInputTable.$inferSelect): Entry => { + const base = { admittedSeq: row.admitted_seq, id: SessionMessage.ID.make(row.id), sessionID: SessionSchema.ID.make(row.session_id), + timeCreated: DateTime.makeUnsafe(row.time_created), + } + if (row.type === "compaction") + return Compaction.make({ + ...base, + type: "compaction", + ...(row.promoted_seq === null ? {} : { handledSeq: row.promoted_seq }), + }) + if (!row.prompt || !row.delivery) throw new LifecycleConflict({ id: base.id }) + return PromptEntry.make({ + ...base, + type: "prompt", prompt: decodePrompt(row.prompt), delivery: row.delivery, - timeCreated: DateTime.makeUnsafe(row.time_created), ...(row.promoted_seq === null ? {} : { promotedSeq: row.promoted_seq }), }) +} + +const toAdmitted = (entry: PromptEntry): Admitted => + Admitted.make({ + admittedSeq: entry.admittedSeq, + id: entry.id, + sessionID: entry.sessionID, + prompt: entry.prompt, + delivery: entry.delivery, + timeCreated: entry.timeCreated, + ...(entry.promotedSeq === undefined ? {} : { promotedSeq: entry.promotedSeq }), + }) export const find = Effect.fn("SessionInput.find")(function* (db: DatabaseService, id: SessionMessage.ID) { const row = yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, id)).get().pipe(Effect.orDie) return row === undefined ? undefined : fromRow(row) }) -export class LifecycleConflict extends Schema.TaggedErrorClass()("SessionInput.LifecycleConflict", { - id: SessionMessage.ID, -}) {} +export const pendingCompaction = Effect.fn("SessionInput.pendingCompaction")(function* ( + db: DatabaseService, + sessionID: SessionSchema.ID, +) { + const row = yield* db + .select() + .from(SessionInputTable) + .where( + and( + eq(SessionInputTable.session_id, sessionID), + eq(SessionInputTable.type, "compaction"), + isNull(SessionInputTable.promoted_seq), + ), + ) + .orderBy(asc(SessionInputTable.admitted_seq)) + .limit(1) + .get() + .pipe(Effect.orDie) + if (!row) return + const entry = fromRow(row) + return entry.type === "compaction" ? entry : undefined +}) export const admit = Effect.fn("SessionInput.admit")(function* ( db: DatabaseService, @@ -49,7 +97,10 @@ export const admit = Effect.fn("SessionInput.admit")(function* ( }, ) { const existing = yield* find(db, input.id) - if (existing !== undefined) return existing + if (existing !== undefined) { + if (existing.type !== "prompt") return yield* Effect.die(new LifecycleConflict({ id: input.id })) + return toAdmitted(existing) + } return yield* events .publish(SessionEvent.PromptAdmitted, { inputID: input.id, @@ -73,11 +124,54 @@ export const admit = Effect.fn("SessionInput.admit")(function* ( ), ), Effect.catchDefect((defect) => - find(db, input.id).pipe(Effect.flatMap((stored) => (stored ? Effect.succeed(stored) : Effect.die(defect)))), + find(db, input.id).pipe( + Effect.flatMap((stored) => + stored?.type === "prompt" ? Effect.succeed(toAdmitted(stored)) : Effect.die(defect), + ), + ), ), ) }) +export const admitCompaction = Effect.fn("SessionInput.admitCompaction")(function* ( + db: DatabaseService, + events: EventV2.Interface, + input: { readonly id: SessionMessage.ID; readonly sessionID: SessionSchema.ID }, +) { + return yield* inboxLocks.withLock(input.sessionID)( + Effect.gen(function* () { + const exact = yield* find(db, input.id) + if (exact) { + if (exact.type === "compaction" && exact.sessionID === input.sessionID) return exact + return yield* Effect.die(new LifecycleConflict({ id: input.id })) + } + const pending = yield* pendingCompaction(db, input.sessionID) + if (pending) return pending + return yield* events + .publish(SessionEvent.Compaction.Admitted, { + inputID: input.id, + sessionID: input.sessionID, + }) + .pipe( + Effect.flatMap((event) => { + if (event.durable === undefined) + return Effect.die(new Error("Compaction admission event is missing aggregate sequence")) + return pendingCompaction(db, input.sessionID).pipe( + Effect.flatMap((stored) => + stored ? Effect.succeed(stored) : Effect.die(new LifecycleConflict({ id: input.id })), + ), + ) + }), + Effect.catchDefect((defect) => + pendingCompaction(db, input.sessionID).pipe( + Effect.flatMap((stored) => (stored ? Effect.succeed(stored) : Effect.die(defect))), + ), + ), + ) + }), + ) +}) + export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(function* ( db: DatabaseService, input: { @@ -101,6 +195,7 @@ export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(functio .values({ id: input.id, session_id: input.sessionID, + type: "prompt", admitted_seq: input.admittedSeq, prompt: encodePrompt(input.prompt), delivery: input.delivery, @@ -113,6 +208,44 @@ export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(functio if (!stored) return yield* Effect.die(new LifecycleConflict({ id: input.id })) }) +export const projectCompactionAdmitted = Effect.fn("SessionInput.projectCompactionAdmitted")(function* ( + db: DatabaseService, + input: { + readonly admittedSeq: number + readonly id: SessionMessage.ID + readonly sessionID: SessionSchema.ID + readonly timeCreated: DateTime.Utc + }, +) { + const message = yield* db + .select({ id: SessionMessageTable.id }) + .from(SessionMessageTable) + .where(eq(SessionMessageTable.id, input.id)) + .get() + .pipe(Effect.orDie) + if (message !== undefined) return yield* Effect.die(new LifecycleConflict({ id: input.id })) + const stored = yield* db + .insert(SessionInputTable) + .values({ + id: input.id, + session_id: input.sessionID, + type: "compaction", + admitted_seq: input.admittedSeq, + time_created: DateTime.toEpochMillis(input.timeCreated), + }) + .onConflictDoNothing() + .returning() + .get() + .pipe(Effect.orDie) + if (stored) { + const entry = fromRow(stored) + return entry.type === "compaction" ? entry : yield* Effect.die(new LifecycleConflict({ id: entry.id })) + } + const pending = yield* pendingCompaction(db, input.sessionID) + if (pending) return pending + return yield* Effect.die(new LifecycleConflict({ id: input.id })) +}) + export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromoted")(function* ( db: DatabaseService, input: { @@ -121,6 +254,7 @@ export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromot readonly promotedSeq: number }, ) { + if (yield* pendingCompaction(db, input.sessionID)) return yield* Effect.die(new LifecycleConflict({ id: input.id })) const updated = yield* db .update(SessionInputTable) .set({ promoted_seq: input.promotedSeq }) @@ -128,6 +262,7 @@ export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromot and( eq(SessionInputTable.id, input.id), eq(SessionInputTable.session_id, input.sessionID), + eq(SessionInputTable.type, "prompt"), isNull(SessionInputTable.promoted_seq), ), ) @@ -136,29 +271,58 @@ export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromot .pipe(Effect.orDie) if (updated) { const stored = fromRow(updated) - if (stored.sessionID !== input.sessionID) return yield* Effect.die(new LifecycleConflict({ id: input.id })) + if (stored.type !== "prompt" || stored.sessionID !== input.sessionID) + return yield* Effect.die(new LifecycleConflict({ id: input.id })) return stored } - - // Every PromptPromoted event is published from an admitted inbox row, so a missing or - // divergent row on replay is an invariant violation. const stored = yield* find(db, input.id) - if (!stored || stored.sessionID !== input.sessionID || stored.promotedSeq !== input.promotedSeq) + if ( + !stored || + stored.type !== "prompt" || + stored.sessionID !== input.sessionID || + stored.promotedSeq !== input.promotedSeq + ) return yield* Effect.die(new LifecycleConflict({ id: input.id })) return stored }) +export const settleCompaction = Effect.fn("SessionInput.settleCompaction")(function* ( + db: DatabaseService, + input: { readonly sessionID: SessionSchema.ID; readonly handledSeq: number }, +) { + const updated = yield* db + .update(SessionInputTable) + .set({ promoted_seq: input.handledSeq }) + .where( + and( + eq(SessionInputTable.session_id, input.sessionID), + eq(SessionInputTable.type, "compaction"), + isNull(SessionInputTable.promoted_seq), + ), + ) + .returning() + .get() + .pipe(Effect.orDie) + if (updated) { + const stored = fromRow(updated) + return stored.type === "compaction" ? stored : yield* Effect.die(new LifecycleConflict({ id: stored.id })) + } + return undefined +}) + export const hasPending = Effect.fn("SessionInput.hasPending")(function* ( db: DatabaseService, sessionID: SessionSchema.ID, delivery: Delivery, ) { + if (yield* pendingCompaction(db, sessionID)) return false const row = yield* db .select({ id: SessionInputTable.id }) .from(SessionInputTable) .where( and( eq(SessionInputTable.session_id, sessionID), + eq(SessionInputTable.type, "prompt"), isNull(SessionInputTable.promoted_seq), eq(SessionInputTable.delivery, delivery), ), @@ -181,42 +345,44 @@ export const equivalent = ( input.sessionID === expected.sessionID && JSON.stringify(encodePrompt(input.prompt)) === JSON.stringify(encodePrompt(expected.prompt)) -const matchesProjection = ( - input: Admitted, - expected: { - readonly sessionID: SessionSchema.ID - readonly prompt: Prompt - readonly delivery: Delivery - readonly timeCreated: DateTime.Utc - }, -) => - equivalent(input, expected) && - DateTime.toEpochMillis(input.timeCreated) === DateTime.toEpochMillis(expected.timeCreated) - const publish = Effect.fn("SessionInput.publish")(function* ( db: DatabaseService, events: EventV2.Interface, sessionID: SessionSchema.ID, rows: ReadonlyArray, ) { - for (const row of rows) { - const id = SessionMessage.ID.make(row.id) - yield* events - .publish(SessionEvent.PromptPromoted, { - sessionID, - inputID: id, - }) - .pipe( - Effect.catchDefect((defect) => - defect instanceof LifecycleConflict - ? find(db, id).pipe( - Effect.flatMap((stored) => (stored?.promotedSeq === undefined ? Effect.die(defect) : Effect.void)), - ) - : Effect.die(defect), - ), + return yield* inboxLocks.withLock(sessionID)( + Effect.gen(function* () { + if (yield* pendingCompaction(db, sessionID)) return 0 + yield* Effect.forEach( + rows, + (row) => { + const entry = fromRow(row) + if (entry.type !== "prompt") return Effect.die(new LifecycleConflict({ id: entry.id })) + return events + .publish(SessionEvent.PromptPromoted, { + sessionID, + inputID: entry.id, + }) + .pipe( + Effect.catchDefect((defect) => + defect instanceof LifecycleConflict + ? find(db, entry.id).pipe( + Effect.flatMap((stored) => + stored?.type === "prompt" && stored.promotedSeq !== undefined + ? Effect.void + : Effect.die(defect), + ), + ) + : Effect.die(defect), + ), + ) + }, + { discard: true }, ) - } - return rows.length + return rows.length + }), + ) }) export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* ( @@ -224,12 +390,14 @@ export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* ( events: EventV2.Interface, sessionID: SessionSchema.ID, ) { + if (yield* pendingCompaction(db, sessionID)) return 0 const rows = yield* db .select() .from(SessionInputTable) .where( and( eq(SessionInputTable.session_id, sessionID), + eq(SessionInputTable.type, "prompt"), isNull(SessionInputTable.promoted_seq), eq(SessionInputTable.delivery, "steer"), ), @@ -245,12 +413,14 @@ export const promoteNextQueued = Effect.fn("SessionInput.promoteNextQueued")(fun events: EventV2.Interface, sessionID: SessionSchema.ID, ) { + if (yield* pendingCompaction(db, sessionID)) return false const row = yield* db .select() .from(SessionInputTable) .where( and( eq(SessionInputTable.session_id, sessionID), + eq(SessionInputTable.type, "prompt"), isNull(SessionInputTable.promoted_seq), eq(SessionInputTable.delivery, "queue"), ), diff --git a/packages/core/src/session/message-updater.ts b/packages/core/src/session/message-updater.ts index 4bf4e43825..96881e7c69 100644 --- a/packages/core/src/session/message-updater.ts +++ b/packages/core/src/session/message-updater.ts @@ -1,5 +1,5 @@ import { castDraft, produce, type WritableDraft } from "immer" -import { Effect } from "effect" +import { DateTime, Effect } from "effect" import { SessionEvent } from "./event" import { SessionMessage } from "./message" @@ -16,8 +16,10 @@ export interface Adapter { readonly getShell: ( shellID: SessionMessage.Shell["shell"]["id"], ) => Effect.Effect + readonly getCompaction: () => Effect.Effect readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect + readonly updateCompaction: (compaction: SessionMessage.Compaction) => Effect.Effect readonly appendMessage: (message: SessionMessage.Message) => Effect.Effect } @@ -26,6 +28,10 @@ export function memory(state: MemoryState): Adapter { state.messages.findLastIndex((message) => message.id === messageID) const shellIndex = (messageID: SessionMessage.ID) => state.messages.findLastIndex((message) => message.id === messageID) + const compactionIndex = () => + state.messages.findLastIndex( + (message) => message.type === "compaction" && (message.status === "queued" || message.status === "running"), + ) // A newer step supersedes stale incomplete rows; never resume an older assistant projection. const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant") @@ -62,6 +68,13 @@ export function memory(state: MemoryState): Adapter { }) }) }, + getCompaction() { + return Effect.sync(() => { + const index = compactionIndex() + const message = state.messages[index] + return message?.type === "compaction" ? message : undefined + }) + }, updateAssistant(assistant) { return Effect.sync(() => { const index = assistantIndex(assistant.id) @@ -80,6 +93,12 @@ export function memory(state: MemoryState): Adapter { state.messages[index] = shell }) }, + updateCompaction(compaction) { + return Effect.sync(() => { + const index = state.messages.findLastIndex((message) => message.id === compaction.id) + if (index >= 0) state.messages[index] = compaction + }) + }, appendMessage(message) { return Effect.sync(() => { state.messages.push(message) @@ -99,11 +118,11 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { (item): item is DraftTool => item.type === "tool" && (callID === undefined || item.id === callID), ) - const latestText = (assistant: DraftAssistant | undefined, textID: string) => - assistant?.content.findLast((item): item is DraftText => item.type === "text" && item.id === textID) + const latestText = (assistant: DraftAssistant | undefined) => + assistant?.content.findLast((item): item is DraftText => item.type === "text") - const latestReasoning = (assistant: DraftAssistant | undefined, reasoningID: string) => - assistant?.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && item.id === reasoningID) + const latestReasoning = (assistant: DraftAssistant | undefined) => + assistant?.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && !item.time?.completed) const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) => Effect.gen(function* () { @@ -111,6 +130,17 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { if (assistant) yield* adapter.updateAssistant(produce(assistant, recipe)) }) + const clearCurrentRetry = Effect.gen(function* () { + const assistant = yield* adapter.getCurrentAssistant() + if (assistant?.retry) { + yield* adapter.updateAssistant( + produce(assistant, (draft) => { + draft.retry = undefined + }), + ) + } + }) + return Effect.gen(function* () { yield* SessionEvent.All.match(event, { "session.agent.selected": (event) => { @@ -141,10 +171,14 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { }, "session.moved": () => Effect.void, "session.renamed": () => Effect.void, + "session.deleted": () => Effect.void, "session.forked": () => Effect.void, "session.prompt.promoted": () => Effect.void, "session.prompt.admitted": () => Effect.void, - "session.execution.settled": () => Effect.void, + "session.execution.started": () => Effect.void, + "session.execution.succeeded": () => clearCurrentRetry, + "session.execution.failed": () => clearCurrentRetry, + "session.execution.interrupted": () => clearCurrentRetry, "session.instructions.updated": (event) => adapter.appendMessage( SessionMessage.System.make({ @@ -154,7 +188,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { time: { created: event.created }, }), ), - "session.instructions.discovered": () => Effect.void, "session.synthetic": (event) => { return adapter.appendMessage( SessionMessage.Synthetic.make({ @@ -206,10 +239,26 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { }, "session.step.started": (event) => { return Effect.gen(function* () { + const existing = yield* adapter.getAssistant(event.data.assistantMessageID) + if (existing) { + yield* adapter.updateAssistant( + produce(existing, (draft) => { + draft.agent = event.data.agent + draft.model = castDraft(event.data.model) + draft.retry = undefined + draft.error = undefined + draft.finish = undefined + draft.time.completed = undefined + if (event.data.snapshot) draft.snapshot = { ...draft.snapshot, start: event.data.snapshot } + }), + ) + return + } const currentAssistant = yield* adapter.getCurrentAssistant() if (currentAssistant) { yield* adapter.updateAssistant( produce(currentAssistant, (draft) => { + draft.retry = undefined draft.time.completed = event.created }), ) @@ -245,25 +294,24 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { draft.time.completed = event.created draft.finish = "error" - draft.error = event.data.error + draft.error = castDraft(event.data.error) + draft.retry = undefined }) }, "session.text.started": (event) => { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { - draft.content.push( - castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })), - ) + draft.content.push(castDraft(SessionMessage.AssistantText.make({ type: "text", text: "" }))) }) }, "session.text.delta": (event) => { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { - const match = latestText(draft, event.data.textID) + const match = latestText(draft) if (match) match.text += event.data.delta }) }, "session.text.ended": (event) => { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { - const match = latestText(draft, event.data.textID) + const match = latestText(draft) if (match) match.text = event.data.text }) }, @@ -293,7 +341,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { const match = latestTool(draft, event.data.callID) if (match) { - match.provider = event.data.provider + match.executed = event.data.executed + match.providerState = event.data.state match.time.ran = event.created match.state = castDraft( SessionMessage.ToolStateRunning.make({ @@ -319,11 +368,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { const match = latestTool(draft, event.data.callID) if (match && match.state.status === "running") { - match.provider = { - executed: event.data.provider.executed || match.provider?.executed === true, - metadata: match.provider?.metadata, - resultMetadata: event.data.provider.metadata, - } + match.executed = event.data.executed || match.executed === true + match.providerResultState = event.data.resultState match.time.completed = event.created match.state = castDraft( SessionMessage.ToolStateCompleted.make({ @@ -342,11 +388,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { const match = latestTool(draft, event.data.callID) if (match && (match.state.status === "pending" || match.state.status === "running")) { - match.provider = { - executed: event.data.provider.executed || match.provider?.executed === true, - metadata: match.provider?.metadata, - resultMetadata: event.data.provider.metadata, - } + match.executed = event.data.executed || match.executed === true + match.providerResultState = event.data.resultState match.time.completed = event.created match.state = castDraft( SessionMessage.ToolStateError.make({ @@ -367,9 +410,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { castDraft( SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: event.data.reasoningID, text: "", - providerMetadata: event.data.providerMetadata, + state: event.data.state, time: { created: event.created }, }), ), @@ -378,36 +420,83 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { }, "session.reasoning.delta": (event) => { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { - const match = latestReasoning(draft, event.data.reasoningID) + const match = latestReasoning(draft) if (match) match.text += event.data.delta }) }, "session.reasoning.ended": (event) => { return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { - const match = latestReasoning(draft, event.data.reasoningID) + const match = latestReasoning(draft) if (match) { match.text = event.data.text match.time = { created: match.time?.created ?? event.created, completed: event.created } - if (event.data.providerMetadata !== undefined) match.providerMetadata = event.data.providerMetadata + if (event.data.state !== undefined) match.state = event.data.state } }) }, - "session.retried": () => Effect.void, - "session.compaction.started": () => Effect.void, - "session.compaction.delta": () => Effect.void, - "session.compaction.ended": (event) => { - return adapter.appendMessage( + "session.retry.scheduled": (event) => { + return updateOwnedAssistant(event.data.assistantMessageID, (draft) => { + draft.retry = { + attempt: event.data.attempt, + at: DateTime.makeUnsafe(event.data.at), + error: castDraft(event.data.error), + } + }) + }, + "session.compaction.admitted": (event) => + adapter.appendMessage( SessionMessage.Compaction.make({ - id: SessionMessage.ID.fromEvent(event.id), + id: event.data.inputID, type: "compaction", + status: "queued", metadata: event.metadata, - reason: event.data.reason, - summary: event.data.text, - recent: event.data.recent, + reason: "manual", + summary: "", + recent: "", time: { created: event.created }, }), - ) + ), + "session.compaction.started": (event) => + Effect.gen(function* () { + if (event.data.reason !== "manual") return + const current = yield* adapter.getCompaction() + if (!current) return + yield* adapter.updateCompaction({ ...current, status: "running" }) + }), + "session.compaction.delta": () => Effect.void, + "session.compaction.ended": (event) => { + return Effect.gen(function* () { + const current = event.data.reason === "manual" ? yield* adapter.getCompaction() : undefined + if (current) { + yield* adapter.updateCompaction({ + ...current, + status: "completed", + reason: event.data.reason, + summary: event.data.text, + recent: event.data.recent, + }) + return + } + yield* adapter.appendMessage( + SessionMessage.Compaction.make({ + id: SessionMessage.ID.fromEvent(event.id), + type: "compaction", + status: "completed", + metadata: event.metadata, + reason: event.data.reason, + summary: event.data.text, + recent: event.data.recent, + time: { created: event.created }, + }), + ) + }) }, + "session.compaction.failed": () => + Effect.gen(function* () { + const current = yield* adapter.getCompaction() + if (!current) return + yield* adapter.updateCompaction({ ...current, status: "failed" }) + }), "session.revert.staged": () => Effect.void, "session.revert.cleared": () => Effect.void, "session.revert.committed": () => Effect.void, diff --git a/packages/core/src/session/projector.ts b/packages/core/src/session/projector.ts index bbe9ce53b1..625b6f1553 100644 --- a/packages/core/src/session/projector.ts +++ b/packages/core/src/session/projector.ts @@ -26,7 +26,10 @@ import type { DeepMutable } from "../schema" import { Slug } from "../util/slug" type DatabaseService = Database.Interface["db"] -type MessageEvent = Exclude +type MessageEvent = Exclude< + SessionEvent.DurableEvent, + typeof SessionEvent.Forked.Type | typeof SessionEvent.Deleted.Type +> const decodeMessage = Schema.decodeUnknownSync(SessionMessage.Message) const encodeMessage = Schema.encodeSync(SessionMessage.Message) @@ -190,7 +193,9 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* ( .insert(SessionTable) .values({ id: event.data.sessionID, - parent_id: event.data.parentID, + parent_id: null, + fork_session_id: event.data.parentID, + fork_message_id: event.data.from, project_id: parent.project_id, workspace_id: parent.workspace_id, slug: Slug.create(), @@ -243,6 +248,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* ( eq(SessionMessageTable.session_id, event.data.parentID), gt(SessionMessageTable.seq, cursor), copiedSeq === 0 ? undefined : lt(SessionMessageTable.seq, copiedSeq + 1), + sql`${SessionMessageTable.type} != 'compaction' or json_extract(${SessionMessageTable.data}, '$.status') not in ('queued', 'running')`, ), ) .orderBy(asc(SessionMessageTable.seq)) @@ -292,11 +298,12 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* ( .values( inputRows.flatMap((row) => { const id = idMap.get(row.id) - return id + return id && row.type === "prompt" ? [ { id, session_id: event.data.sessionID, + type: "prompt" as const, prompt: row.prompt, delivery: row.delivery, admitted_seq: row.admitted_seq, @@ -426,8 +433,30 @@ function run(db: DatabaseService, event: MessageEvent) { return message.type === "shell" ? message : undefined }) }, + getCompaction() { + return Effect.gen(function* () { + const row = yield* db + .select() + .from(SessionMessageTable) + .where( + and( + eq(SessionMessageTable.session_id, event.data.sessionID), + eq(SessionMessageTable.type, "compaction"), + sql`json_extract(${SessionMessageTable.data}, '$.status') in ('queued', 'running')`, + ), + ) + .orderBy(desc(SessionMessageTable.seq)) + .limit(1) + .get() + .pipe(Effect.orDie) + if (!row) return + const message = decodeRow(row) + return message.type === "compaction" ? message : undefined + }) + }, updateAssistant: updateMessage, updateShell: updateMessage, + updateCompaction: updateMessage, appendMessage, } yield* SessionMessageUpdater.update(adapter, event) @@ -503,6 +532,9 @@ const layer = Layer.effectDiscard( yield* events.project(SessionV1.Event.Deleted, (event) => db.delete(SessionTable).where(eq(SessionTable.id, event.data.sessionID)).run().pipe(Effect.orDie), ) + yield* events.project(SessionEvent.Deleted, (event) => + db.delete(SessionTable).where(eq(SessionTable.id, event.data.sessionID)).run().pipe(Effect.orDie), + ) yield* events.project(SessionV1.Event.MessageUpdated, (event) => Effect.gen(function* () { const time_created = event.data.info.time.created @@ -634,6 +666,23 @@ const layer = Layer.effectDiscard( }) }), ) + yield* events.project(SessionEvent.Compaction.Admitted, (event) => + Effect.gen(function* () { + if (event.durable === undefined) + return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence")) + const admitted = yield* SessionInput.projectCompactionAdmitted(db, { + admittedSeq: event.durable.seq, + id: event.data.inputID, + sessionID: event.data.sessionID, + timeCreated: event.created, + }) + if (admitted.id !== event.data.inputID) return + yield* run(db, event) + }), + ) + yield* events.project(SessionEvent.Execution.Succeeded, (event) => run(db, event)) + yield* events.project(SessionEvent.Execution.Failed, (event) => run(db, event)) + yield* events.project(SessionEvent.Execution.Interrupted, (event) => run(db, event)) yield* events.project(SessionEvent.InstructionsUpdated, (event) => run(db, event)) yield* events.project(SessionEvent.Synthetic, (event) => run(db, event)) yield* events.project(SessionEvent.Skill.Activated, (event) => @@ -660,8 +709,31 @@ const layer = Layer.effectDiscard( yield* events.project(SessionEvent.Tool.Failed, (event) => run(db, event)) yield* events.project(SessionEvent.Reasoning.Started, (event) => run(db, event)) yield* events.project(SessionEvent.Reasoning.Ended, (event) => run(db, event)) - // yield* events.project(SessionEvent.Retried, (event) => run(db, event)) - yield* events.project(SessionEvent.Compaction.Ended, (event) => run(db, event)) + yield* events.project(SessionEvent.RetryScheduled, (event) => run(db, event)) + yield* events.project(SessionEvent.Compaction.Started, (event) => run(db, event)) + yield* events.project(SessionEvent.Compaction.Ended, (event) => + Effect.gen(function* () { + yield* run(db, event) + if (event.durable === undefined) + return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence")) + if (event.data.reason === "manual") + yield* SessionInput.settleCompaction(db, { + sessionID: event.data.sessionID, + handledSeq: event.durable.seq, + }) + }), + ) + yield* events.project(SessionEvent.Compaction.Failed, (event) => + Effect.gen(function* () { + yield* run(db, event) + if (event.durable === undefined) + return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence")) + yield* SessionInput.settleCompaction(db, { + sessionID: event.data.sessionID, + handledSeq: event.durable.seq, + }) + }), + ) yield* events.project(SessionEvent.RevertEvent.Staged, (event) => db .update(SessionTable) @@ -687,14 +759,11 @@ const layer = Layer.effectDiscard( .select({ seq: SessionMessageTable.seq }) .from(SessionMessageTable) .where( - and( - eq(SessionMessageTable.session_id, event.data.sessionID), - eq(SessionMessageTable.id, event.data.messageID), - ), + and(eq(SessionMessageTable.session_id, event.data.sessionID), eq(SessionMessageTable.id, event.data.to)), ) .get() .pipe(Effect.orDie) - if (!boundary) return yield* Effect.die(new Error(`Revert boundary message not found: ${event.data.messageID}`)) + if (!boundary) return yield* Effect.die(new Error(`Revert boundary message not found: ${event.data.to}`)) yield* db .delete(SessionMessageTable) .where( diff --git a/packages/core/src/session/revert.ts b/packages/core/src/session/revert.ts index 8c0b8bbf7b..80ec187472 100644 --- a/packages/core/src/session/revert.ts +++ b/packages/core/src/session/revert.ts @@ -113,6 +113,6 @@ export const commit = Effect.fn("SessionRevert.commit")(function* (session: Sess const events = yield* EventV2.Service yield* events.publish(SessionEvent.RevertEvent.Committed, { sessionID: session.id, - messageID: session.revert.messageID, + to: session.revert.messageID, }) }) diff --git a/packages/core/src/session/run-coordinator.ts b/packages/core/src/session/run-coordinator.ts index 8524b1c1f8..1550280164 100644 --- a/packages/core/src/session/run-coordinator.ts +++ b/packages/core/src/session/run-coordinator.ts @@ -3,7 +3,7 @@ export * as SessionRunCoordinator from "./run-coordinator" import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect" /** Serializes execution for each key while allowing different keys to run concurrently. */ -export interface Coordinator { +export interface Coordinator { /** Snapshots keys with an execution owned by this coordinator. */ readonly active: Effect.Effect> /** Starts an execution while idle, or joins the active execution and returns its exit. */ @@ -11,7 +11,7 @@ export interface Coordinator { /** Rings the doorbell: an idle key starts an execution; an active one drains again before settling. */ readonly wake: (key: Key) => Effect.Effect /** Stops the active execution, clears its doorbell, and waits for cleanup. No-op when idle. */ - readonly interrupt: (key: Key) => Effect.Effect + readonly interrupt: (key: Key, reason?: Reason) => Effect.Effect /** Resolves once no execution is active for the key. Returns immediately when already idle and never starts work. */ readonly awaitIdle: (key: Key) => Effect.Effect } @@ -23,11 +23,13 @@ export interface Coordinator { * closes the gap between a drain's last eligibility check and the idle transition, since * those cannot be one atomic step. `done` resolves joiners with this execution's exit. */ -type Execution = { +type Execution = { readonly done: Deferred.Deferred owner?: Fiber.Fiber pendingWake: boolean stopping: boolean + settling: boolean + interruptionReason?: Reason } /** @@ -41,19 +43,21 @@ type Execution = { * waiters get this exit * ``` */ -export const make = (options: { +export const make = (options: { readonly drain: (key: Key, force: boolean) => Effect.Effect + /** Runs once when a process-local busy period begins, before its first drain. */ + readonly started?: (key: Key) => Effect.Effect /** * Runs in the execution fiber for every exit, including interruption, after the final * drain and before the execution settles (waiters resolve after it completes). */ - readonly settled?: (key: Key, exit: Exit.Exit) => Effect.Effect -}): Effect.Effect, never, Scope.Scope> => + readonly settled?: (key: Key, exit: Exit.Exit, reason?: Reason) => Effect.Effect +}): Effect.Effect, never, Scope.Scope> => Effect.gen(function* () { - const executions = new Map>() + const executions = new Map>() const fork = yield* FiberSet.makeRuntime() - const loop = (key: Key, execution: Execution, force: boolean): Effect.Effect => + const loop = (key: Key, execution: Execution, force: boolean): Effect.Effect => Effect.suspend(() => options.drain(key, force)).pipe( Effect.flatMap(() => Effect.suspend(() => { @@ -66,15 +70,25 @@ export const make = (options: { ) const start = (key: Key, force: boolean) => { - const execution: Execution = { done: Deferred.makeUnsafe(), pendingWake: false, stopping: false } + const execution: Execution = { + done: Deferred.makeUnsafe(), + pendingWake: false, + stopping: false, + settling: false, + } executions.set(key, execution) // The leading yield lets `owner` be assigned before the drain can settle, and keeps // failing self-waking executions from growing the stack across successor starts. // Drains start one tick after wake; callers observe progress through events or run. execution.owner = fork( Effect.yieldNow.pipe( + Effect.andThen(Effect.uninterruptible(options.started?.(key) ?? Effect.void)), Effect.andThen(loop(key, execution, force)), - Effect.onExit((exit) => options.settled?.(key, exit) ?? Effect.void), + Effect.onExit((exit) => + Effect.sync(() => { + execution.settling = true + }).pipe(Effect.andThen(options.settled?.(key, exit, execution.interruptionReason) ?? Effect.void)), + ), Effect.onExit((exit) => Effect.sync(() => settle(key, execution, exit))), Effect.exit, Effect.asVoid, @@ -85,7 +99,7 @@ export const make = (options: { // A doorbell that survives the execution loop (rung after the loop decided to end, or // during failure or interruption cleanup) starts a fresh execution for the remaining work. - const settle = (key: Key, execution: Execution, exit: Exit.Exit) => { + const settle = (key: Key, execution: Execution, exit: Exit.Exit) => { if (execution.pendingWake) start(key, false) else executions.delete(key) Deferred.doneUnsafe(execution.done, exit) @@ -112,12 +126,13 @@ export const make = (options: { start(key, false) }) - const interrupt = (key: Key): Effect.Effect => + const interrupt = (key: Key, reason?: Reason): Effect.Effect => Effect.suspend(() => { const execution = executions.get(key) - if (execution?.owner === undefined) return Effect.void + if (execution?.owner === undefined || execution.stopping || execution.settling) return Effect.void execution.stopping = true execution.pendingWake = false + execution.interruptionReason = reason return Fiber.interrupt(execution.owner) }) diff --git a/packages/core/src/session/runner/index.ts b/packages/core/src/session/runner/index.ts index 3decb42f05..d40efb7945 100644 --- a/packages/core/src/session/runner/index.ts +++ b/packages/core/src/session/runner/index.ts @@ -3,13 +3,19 @@ export * as SessionRunner from "./index" import type { LLMError } from "@opencode-ai/llm" import { Context, Effect } from "effect" import { SessionSchema } from "../schema" -import type { MessageDecodeError } from "../error" +import type { MessageDecodeError, StepFailedError, UserInterruptedError } from "../error" import { SessionRunnerModel } from "./model" import type { Instructions } from "../../instructions/index" import type { ToolOutputStore } from "../../tool-output-store" export type RunError = - LLMError | SessionRunnerModel.Error | MessageDecodeError | Instructions.InitializationBlocked | ToolOutputStore.Error + | LLMError + | SessionRunnerModel.Error + | MessageDecodeError + | StepFailedError + | UserInterruptedError + | Instructions.InitializationBlocked + | ToolOutputStore.Error /** Runs one local continuation from already-recorded Session history. */ export interface Interface { diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index d696dd7ff0..2e05d1edcf 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -10,7 +10,8 @@ import { isContextOverflowFailure, type ProviderErrorEvent, } from "@opencode-ai/llm" -import { Cause, Effect, Exit, FiberSet, Layer, Option, Semaphore, Stream } from "effect" +import { SessionError } from "@opencode-ai/schema/session-error" +import { Cause, Effect, Exit, Fiber, FiberSet, Layer, Option, Semaphore, Stream } from "effect" import { AgentV2 } from "../../agent" import { Config } from "../../config" import { Database } from "../../database/database" @@ -32,6 +33,7 @@ import { SessionCompaction } from "../compaction" import { SessionEvent } from "../event" import { SessionHistory } from "../history" import { SessionInput } from "../input" +import { SessionMessage } from "../message" import { SessionSchema } from "../schema" import { SessionStore } from "../store" import { SessionTitle } from "../title" @@ -44,6 +46,9 @@ import { SessionRunnerSystemPrompt } from "./system-prompt" import { Snapshot } from "../../snapshot" import { makeLocationNode } from "../../effect/app-node" import { llmClient } from "../../effect/app-node-platform" +import { StepFailedError, UserInterruptedError } from "../error" +import { toSessionError } from "../to-session-error" +import { SessionRunnerRetry } from "./retry" /** * Runs one durable coding-agent Session until it settles. @@ -54,10 +59,10 @@ import { llmClient } from "../../effect/app-node-platform" * - Session ownership and controls * - [x] Coordinate one local active drain per Session; explicit resumes join and prompt wakeups coalesce. * - [ ] Replace local ownership with durable multi-node ownership when clustered. - * - [ ] Mark busy, retrying, idle, interrupted, or terminal-failure status durably. + * - [x] Publish durable historical execution lifecycle and bounded retry observations. * - [ ] Honor interruption and reject stale work after runtime attachment replacement. * - [x] Honor optional agent step limits. - * - [ ] Bound provider retries and repeated identical tool calls. + * - [ ] Bound repeated identical tool calls (provider retries are bounded). * * - Runtime context assembly * - Track V1 runtime-context parity canonically in `specs/v2/session.md`. @@ -66,7 +71,7 @@ import { llmClient } from "../../effect/app-node-platform" * - [x] Translate every projected V2 Session message variant into canonical * `@opencode-ai/llm` messages. * - [ ] Resolve policy-filtered built-in, MCP, plugin, and structured-output tool definitions. - * - [x] Stream exactly one `llm.stream(request)` physical attempt. + * - [x] Stream exactly one `llm.stream(request)` call per attempt. * - [x] Persist assistant text and usage events incrementally as they arrive. * - [ ] Persist snapshots, patches, and retry notices incrementally as they arrive. * - [x] Persist reasoning, provider errors, and tool-call events incrementally as they arrive. @@ -87,7 +92,7 @@ import { llmClient } from "../../effect/app-node-platform" * - [ ] Coalesce streamed deltas and add covering projected-history indexes. * - [ ] Update title, summaries, compaction state, and cleanup in bounded background work. * - * Use `llm.stream(request)` for each physical attempt. Keep tool execution and continuation here. + * Use `llm.stream(request)` for each attempt. Keep tool execution and continuation here. * Durable continuation recovery remains a separate future slice with an explicit retry policy. * * The current slice loads V2 history, translates it, resolves a model through a core service, and persists one @@ -137,19 +142,13 @@ const layer = Layer.effect( sessionID, assistantMessageID: message.id, callID: tool.id, - error: { type: "unknown", message: "Tool execution interrupted" }, - provider: { - executed: tool.provider?.executed === true, - ...(tool.provider?.metadata === undefined ? {} : { metadata: tool.provider.metadata }), - }, + error: { type: "tool.stale", message: `Tool execution interrupted: ${tool.name}` }, + executed: tool.executed === true, }) } } }) - const awaitToolFibers = (fibers: FiberSet.FiberSet) => - Effect.raceFirst(FiberSet.join(fibers), FiberSet.awaitEmpty(fibers)) - // Declining an interactive prompt halts the drain instead of becoming model-facing tool output. const isUserDeclined = (cause: Cause.Cause) => cause.reasons.some( @@ -176,6 +175,7 @@ const layer = Layer.effect( promotion: SessionInput.Delivery | undefined, step: number, recoverOverflow?: typeof compaction.compactAfterOverflow, + assistantMessageID?: SessionMessage.ID, ) { const session = yield* getSession(sessionID) if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID) @@ -189,7 +189,8 @@ const layer = Layer.effect( loadInstructions(agent, session.id), session.id, ) - const toolFibers = yield* FiberSet.make() + const toolFibers = yield* FiberSet.make() + const ownedToolFibers: Array> = [] let needsContinuation = false let currentStep = step if (promotion) { @@ -227,7 +228,10 @@ const layer = Layer.effect( toolChoice: isLastStep ? "none" : undefined, }) // Automatic compaction completed; rebuild the request from compacted history. - if (yield* compaction.compactIfNeeded({ sessionID: session.id, messages: context, request })) + if ( + !(yield* SessionInput.pendingCompaction(db, session.id)) && + (yield* compaction.compactIfNeeded({ sessionID: session.id, messages: context, request })) + ) return { _tag: "RestartAfterCompaction", step: currentStep } as const const startSnapshot = yield* snapshots.capture() const publisher = createLLMEventPublisher(events, { @@ -236,21 +240,23 @@ const layer = Layer.effect( // The selected catalog identity, not model.id: route-level ids are provider API // model ids (for example gpt-5.5-fast resolves to api id gpt-5.5). model: resolved.ref, + provider: model.provider, snapshot: startSnapshot, + assistantMessageID, }) const publication = Semaphore.makeUnsafe(1) // Durable publishes are serialized so tool fibers and step settlement never interleave // mid-event. const serialized = (effect: Effect.Effect) => publication.withPermit(effect) - const publish = (event: LLMEvent, outputPaths: ReadonlyArray = []) => - serialized(publisher.publish(event, outputPaths)) + const publish = (event: LLMEvent, outputPaths: ReadonlyArray = [], error?: SessionError.Error) => + serialized(publisher.publish(event, outputPaths, error)) let overflowFailure: ProviderErrorEvent | undefined const providerStream = llm.stream(request).pipe( Stream.runForEach((event) => Effect.gen(function* () { if (overflowFailure || publisher.hasProviderError()) return if (LLMEvent.is.providerError(event)) { - if (isContextOverflowFailure(event) && !publisher.hasAssistantStarted()) { + if (isContextOverflowFailure(event) && !publisher.hasRetryEvidence()) { overflowFailure = event return } @@ -258,33 +264,49 @@ const layer = Layer.effect( yield* publish(event) if (event.type !== "tool-call" || event.providerExecuted) return if (!toolMaterialization) { - yield* serialized(publisher.failUnsettledTools("Tools are disabled after the maximum agent steps")) + yield* serialized( + publisher.failUnsettledTools({ + type: "tool.execution", + message: "Tools are disabled after the maximum agent steps", + }), + ) return } needsContinuation = true const assistantMessageID = yield* publisher.assistantMessageID(event.id) - yield* Effect.uninterruptibleMask((restore) => - restore( - toolMaterialization.settle({ - sessionID: session.id, - agent: agent.id, - assistantMessageID, - call: event, - }), - ).pipe( - Effect.flatMap((settlement) => - publish( - LLMEvent.toolResult({ - id: event.id, - name: event.name, - result: settlement.result, - output: settlement.output, - }), - settlement.outputPaths ?? [], + ownedToolFibers.push( + yield* Effect.uninterruptibleMask((restore) => + restore( + toolMaterialization.settle({ + sessionID: session.id, + agent: agent.id, + assistantMessageID, + call: event, + }), + ).pipe( + Effect.flatMap((settlement) => + publish( + LLMEvent.toolResult({ + id: event.id, + name: event.name, + result: settlement.result, + output: settlement.output, + }), + settlement.outputPaths ?? [], + settlement.error, + ).pipe( + Effect.andThen( + settlement.error?.type === "permission.rejected" + ? serialized(publisher.failAssistant(settlement.error)).pipe( + Effect.andThen(Effect.fail(new UserInterruptedError())), + ) + : Effect.void, + ), + ), ), ), - ), - ).pipe(FiberSet.run(toolFibers)) + ).pipe(FiberSet.run(toolFibers)), + ) }), ), Effect.ensuring(serialized(publisher.flush())), @@ -327,64 +349,118 @@ const layer = Layer.effect( // restart the step instead of surfacing the provider error. if ( recoverOverflow && - !publisher.hasAssistantStarted() && + !publisher.hasRetryEvidence() && isContextOverflowFailure(overflowFailure ?? streamFailure) && (yield* restore(recoverOverflow({ sessionID: session.id, messages: context, request }))) ) return { _tag: "RestartAfterOverflowCompaction", step: currentStep } as const // An unrecovered held-back overflow becomes the step's durable provider error. A - // thrown LLM failure fails hosted tool calls and the assistant unless a provider - // error was already recorded from the stream. + // thrown LLM failure records the assistant failure unless a provider error was + // already recorded from the stream. Terminal publication waits for owned tools. if (overflowFailure) yield* publish(overflowFailure) const llmFailure = streamFailure instanceof LLMError ? streamFailure : undefined if (llmFailure && !publisher.hasProviderError()) { - yield* serialized(publisher.failUnsettledTools("Provider did not return a tool result", true)) - yield* serialized(publisher.failAssistant(llmFailure.reason.message)) + const error = toSessionError(llmFailure) + if ( + SessionRunnerRetry.isRetryable(llmFailure) && + !publisher.hasRetryEvidence() && + (agent.info?.steps === undefined || currentStep < agent.info.steps) + ) { + return yield* new SessionRunnerRetry.RetryableFailure({ + cause: llmFailure, + assistantMessageID: yield* publisher.startAssistant(), + error, + step: currentStep, + }) + } + yield* serialized(publisher.failAssistant(error)) } // Provider error events only arrive from the stream, so the flag is final here. const providerFailed = publisher.hasProviderError() - // Settle tool fibers: an interrupted stream abandons unstarted tool work first. + // Settle every owned tool fiber. FiberSet.join returns on the first failure, so retain + // the individual fibers and await all exits before publishing the terminal step event. if (streamInterrupted) yield* FiberSet.clear(toolFibers) - const settled = yield* restore(awaitToolFibers(toolFibers)).pipe(Effect.exit) - const toolsInterrupted = settled._tag === "Failure" && Cause.hasInterrupts(settled.cause) - const userDeclined = settled._tag === "Failure" && isUserDeclined(settled.cause) + const settled = yield* restore( + Effect.forEach(ownedToolFibers, Fiber.await, { concurrency: "unbounded" }), + ).pipe(Effect.exit) + const settledCauses = + settled._tag === "Failure" + ? [settled.cause] + : settled.value.flatMap((exit) => (exit._tag === "Failure" ? [exit.cause] : [])) + const toolsInterrupted = settledCauses.some(Cause.hasInterrupts) + const userDeclined = settledCauses.some(isUserDeclined) + const permissionRejected = settledCauses.some( + (cause) => Option.getOrUndefined(Cause.findErrorOption(cause)) instanceof UserInterruptedError, + ) - if (userDeclined || streamInterrupted || toolsInterrupted) { + if (userDeclined || permissionRejected || streamInterrupted || toolsInterrupted) { yield* FiberSet.clear(toolFibers) - yield* serialized(publisher.failUnsettledTools("Tool execution interrupted")) - yield* serialized(publisher.failAssistant("Step interrupted")) - if (userDeclined) return yield* Effect.interrupt + yield* serialized(publisher.failUnsettledTools({ type: "aborted", message: "Tool execution interrupted" })) + yield* serialized(publisher.failAssistant({ type: "aborted", message: "Step interrupted" })) } // A settled tool fiber failure is one of two things. A defect from a tool // implementation becomes a failed tool call the model can read, and the step still // settles so the model may recover. A typed infrastructure failure (tool output // could not be persisted) also fails the assistant and then fails the drain. - const settledFailure = settled._tag === "Failure" && !toolsInterrupted ? settled.cause : undefined + const settledFailure = settledCauses.find( + (cause) => !Cause.hasInterrupts(cause) && !isUserDeclined(cause) && !permissionRejected, + ) const infraError = settledFailure === undefined ? undefined : Option.getOrUndefined(Cause.findErrorOption(settledFailure)) if (settledFailure !== undefined) { const failure = infraError ?? Cause.squash(settledFailure) - const message = failure instanceof Error ? failure.message : String(failure) - yield* serialized(publisher.failUnsettledTools(`Tool execution failed: ${message}`)) - if (infraError !== undefined) - yield* serialized(publisher.failAssistant(`Tool execution failed: ${message}`)) + const error = toSessionError(failure) + yield* serialized(publisher.failUnsettledTools(error)) + if (infraError !== undefined) yield* serialized(publisher.failAssistant(error)) } + // Fail unresolved calls before the terminal step event. Local calls have joined, so + // these sweeps only close calls that could not produce a truthful settlement. + if (providerFailed) + yield* serialized(publisher.failUnsettledTools({ type: "aborted", message: "Tool execution interrupted" })) + if (llmFailure && !providerFailed) + yield* serialized( + publisher.failUnsettledTools( + { + type: "tool.result-missing", + message: "Provider did not return a tool result", + }, + true, + ), + ) + const hostedResultMissing = + stream._tag === "Success" && !providerFailed + ? yield* serialized( + publisher.failUnsettledTools( + { type: "tool.result-missing", message: "Provider did not return a tool result" }, + true, + ), + ) + : false + if (hostedResultMissing && !publisher.stepSettlement()) + yield* serialized( + publisher.failAssistant({ + type: "tool.result-missing", + message: "Provider did not return a tool result", + }), + ) + + const stepFailure = publisher.stepFailure() const stepSettlement = publisher.stepSettlement() const stepEndedCleanly = - !streamInterrupted && !toolsInterrupted && infraError === undefined && !providerFailed + !streamInterrupted && !toolsInterrupted && infraError === undefined && !providerFailed && !stepFailure if (stepSettlement && stepEndedCleanly) yield* publishStepEnd(stepSettlement) - // A provider error orphans recorded local calls; a clean stream can still leave - // hosted calls without results. - if (providerFailed) yield* serialized(publisher.failUnsettledTools("Tool execution interrupted")) - if (stream._tag === "Success" && !providerFailed) - yield* serialized(publisher.failUnsettledTools("Provider did not return a tool result", true)) + if (stepFailure) yield* serialized(publisher.publishStepFailure()) if (stream._tag === "Failure") return yield* Effect.failCause(stream.cause) - if (settled._tag === "Failure" && (toolsInterrupted || infraError !== undefined)) - return yield* Effect.failCause(settled.cause) + if (userDeclined) return yield* Effect.interrupt + if (permissionRejected) return yield* new UserInterruptedError() + if ((toolsInterrupted || infraError !== undefined) && settledFailure) + return yield* Effect.failCause(settledFailure) + if (toolsInterrupted && settled._tag === "Failure") return yield* Effect.failCause(settled.cause) + if (stepFailure) return yield* new StepFailedError({ error: stepFailure }) return { _tag: "Completed", needsContinuation: !providerFailed && needsContinuation, @@ -405,8 +481,31 @@ const layer = Layer.effect( let recoverOverflow: typeof compaction.compactAfterOverflow | undefined = compaction.compactAfterOverflow let currentPromotion = promotion let currentStep = step + let assistantMessageID: SessionMessage.ID | undefined while (true) { - const attempt = yield* attemptStep(sessionID, currentPromotion, currentStep, recoverOverflow) + const attempt = yield* Effect.suspend(() => + attemptStep(sessionID, currentPromotion, currentStep, recoverOverflow, assistantMessageID), + ).pipe( + Effect.tapError((error) => + error instanceof SessionRunnerRetry.RetryableFailure + ? Effect.sync(() => { + currentStep = error.step + 1 + assistantMessageID = error.assistantMessageID + currentPromotion = undefined + }) + : Effect.void, + ), + Effect.retryOrElse(SessionRunnerRetry.schedule(events, sessionID), (error) => { + if (!(error instanceof SessionRunnerRetry.RetryableFailure)) return Effect.fail(error) + return events + .publish(SessionEvent.Step.Failed, { + sessionID, + assistantMessageID: error.assistantMessageID, + error: error.error, + }) + .pipe(Effect.andThen(Effect.fail(error.cause))) + }), + ) if (attempt._tag === "Completed") return { needsContinuation: attempt.needsContinuation, step: attempt.step } if (attempt._tag === "RestartAfterOverflowCompaction") recoverOverflow = undefined yield* Effect.yieldNow @@ -415,12 +514,36 @@ const layer = Layer.effect( } }) - // ExecutionSettled is published per execution (busy period) by SessionExecution, not per - // drain here. + const runPendingCompaction = Effect.fn("SessionRunner.runPendingCompaction")(function* ( + sessionID: SessionSchema.ID, + ) { + const pending = yield* SessionInput.pendingCompaction(db, sessionID) + if (!pending) return false + const session = yield* getSession(sessionID) + return yield* Effect.uninterruptibleMask((restore) => + Effect.gen(function* () { + const compacted = yield* restore( + Effect.gen(function* () { + return yield* compaction.compactManual({ + session, + messages: yield* store.context(sessionID), + }) + }), + ).pipe(Effect.exit) + if (Exit.isSuccess(compacted) && compacted.value) return true + yield* events.publish(SessionEvent.Compaction.Failed, { sessionID }) + if (Exit.isFailure(compacted)) return yield* Effect.failCause(compacted.cause) + return true + }), + ) + }) + + // Execution lifecycle is published per busy period by SessionExecution, not per drain here. const drain = Effect.fn("SessionRunner.drain")(function* (input: { readonly sessionID: SessionSchema.ID readonly force: boolean }) { + yield* runPendingCompaction(input.sessionID) const hasSteer = yield* SessionInput.hasPending(db, input.sessionID, "steer") const hasQueue = hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue") if (!input.force && !hasSteer && !hasQueue) return @@ -444,11 +567,19 @@ const layer = Layer.effect( } needsContinuation = result.needsContinuation step = result.step + 1 + if (needsContinuation) { + promotion = (yield* SessionInput.pendingCompaction(db, input.sessionID)) ? undefined : "steer" + continue + } + yield* runPendingCompaction(input.sessionID) promotion = "steer" - if (!needsContinuation) needsContinuation = yield* SessionInput.hasPending(db, input.sessionID, "steer") + needsContinuation = yield* SessionInput.hasPending(db, input.sessionID, "steer") } - shouldRun = yield* SessionInput.hasPending(db, input.sessionID, "queue") - promotion = shouldRun ? "queue" : undefined + yield* runPendingCompaction(input.sessionID) + const hasSteer = yield* SessionInput.hasPending(db, input.sessionID, "steer") + const hasQueue = hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue") + shouldRun = hasSteer || hasQueue + promotion = hasSteer ? "steer" : hasQueue ? "queue" : undefined } }) diff --git a/packages/core/src/session/runner/publish-llm-event.ts b/packages/core/src/session/runner/publish-llm-event.ts index 9a334a0a6d..5f4eeebc84 100644 --- a/packages/core/src/session/runner/publish-llm-event.ts +++ b/packages/core/src/session/runner/publish-llm-event.ts @@ -1,16 +1,19 @@ import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm" -import { DateTime, Effect } from "effect" +import { Effect } from "effect" import { EventV2 } from "../../event" import { ModelV2 } from "../../model" import { SessionEvent } from "../event" import { SessionMessage } from "../message" import { SessionSchema } from "../schema" +import { SessionError } from "@opencode-ai/schema/session-error" type Input = { readonly sessionID: SessionSchema.ID readonly agent: string readonly model: ModelV2.Ref + readonly provider: string readonly snapshot?: string + readonly assistantMessageID?: SessionMessage.ID } const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? 0) : 0) @@ -41,10 +44,10 @@ const message = (value: unknown) => { type SettledOutput = | { readonly structured: Record; readonly content: ToolOutput["content"] } - | { readonly error: { readonly type: "unknown"; readonly message: string } } + | { readonly error: SessionError.Error } const settledOutput = (value: ToolOutput | undefined, result: ToolResultValue): SettledOutput => { - if (result.type === "error") return { error: { type: "unknown", message: message(result.value) } } + if (result.type === "error") return { error: { type: "tool.execution", message: message(result.value) } } const settled = value ?? ToolOutput.fromResultValue(result) if (!settled) throw new Error(`Unsupported tool result: ${message(result)}`) return { structured: record(settled.structured), content: settled.content } @@ -61,20 +64,25 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) called: boolean settled: boolean providerExecuted: boolean - providerMetadata?: ProviderMetadata } >() - const timestamp = DateTime.now - let assistantMessageID: SessionMessage.ID | undefined - let assistantActive = false - let assistantFailed = false + let assistantMessageID = input.assistantMessageID + let stepStarted = false + let stepFailed = false let providerFailed = false - let stepSettlement: { readonly finish: string; readonly tokens: ReturnType } | undefined + let retryEvidence = false + let stepFailure: SessionError.Error | undefined + let stepSettlement: + | { + readonly finish: Extract["reason"] + readonly tokens: ReturnType + } + | undefined const startAssistant = Effect.fnUntraced(function* () { - if (assistantMessageID !== undefined) return assistantMessageID - assistantMessageID = SessionMessage.ID.create() - assistantActive = true + if (stepStarted && assistantMessageID !== undefined) return assistantMessageID + assistantMessageID ??= SessionMessage.ID.create() + stepStarted = true yield* events.publish(SessionEvent.Step.Started, { ...input, assistantMessageID, @@ -86,29 +94,34 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) assistantMessageID === undefined ? Effect.die(new Error("Tool event before assistant step start")) : Effect.succeed(assistantMessageID) + const providerState = (metadata: ProviderMetadata | undefined) => metadata?.[input.provider] const fragments = ( name: string, - ended: (id: string, value: string, providerMetadata?: ProviderMetadata) => Effect.Effect, + ended: (id: string, value: string, ordinal: number, state?: Record) => Effect.Effect, + single = false, ) => { - const chunks = new Map() + const chunks = new Map() + let nextOrdinal = 0 const start = (id: string) => Effect.suspend(() => { if (chunks.has(id)) return Effect.die(new Error(`Duplicate ${name} start: ${id}`)) - chunks.set(id, []) - return Effect.void + if (single && chunks.size > 0) return Effect.die(new Error(`${name} start before end: ${id}`)) + const ordinal = nextOrdinal++ + chunks.set(id, { ordinal, values: [] }) + return Effect.succeed(ordinal) }) const append = (id: string, value: string) => Effect.suspend(() => { const current = chunks.get(id) if (!current) return Effect.die(new Error(`${name} delta before start: ${id}`)) - current.push(value) - return Effect.void + current.values.push(value) + return Effect.succeed(current.ordinal) }) - const end = Effect.fnUntraced(function* (id: string, providerMetadata?: ProviderMetadata) { + const end = Effect.fnUntraced(function* (id: string, state?: Record) { const current = chunks.get(id) if (!current) return yield* Effect.die(new Error(`${name} end before start: ${id}`)) - yield* ended(id, current.join(""), providerMetadata) + yield* ended(id, current.values.join(""), current.ordinal, state) chunks.delete(id) }) const flush = Effect.fnUntraced(function* () { @@ -117,26 +130,32 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) return { start, append, end, flush } } - const text = fragments("text", (textID, value) => - Effect.gen(function* () { - yield* events.publish(SessionEvent.Text.Ended, { - sessionID: input.sessionID, - assistantMessageID: yield* currentAssistantMessageID(), - textID, - text: value, - }) - }), + const text = fragments( + "text", + (_textID, value, ordinal) => + Effect.gen(function* () { + yield* events.publish(SessionEvent.Text.Ended, { + sessionID: input.sessionID, + assistantMessageID: yield* currentAssistantMessageID(), + ordinal, + text: value, + }) + }), + true, ) - const reasoning = fragments("reasoning", (reasoningID, value, providerMetadata) => - Effect.gen(function* () { - yield* events.publish(SessionEvent.Reasoning.Ended, { - sessionID: input.sessionID, - assistantMessageID: yield* currentAssistantMessageID(), - reasoningID, - text: value, - providerMetadata, - }) - }), + const reasoning = fragments( + "reasoning", + (_reasoningID, value, ordinal, state) => + Effect.gen(function* () { + yield* events.publish(SessionEvent.Reasoning.Ended, { + sessionID: input.sessionID, + assistantMessageID: yield* currentAssistantMessageID(), + ordinal, + text: value, + state, + }) + }), + true, ) const toolInput = fragments("tool input", (callID, value) => Effect.gen(function* () { @@ -191,37 +210,41 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) yield* flushFragments() }) - const failAssistant = Effect.fnUntraced(function* (message: string) { - if (assistantFailed) return + const failAssistant = Effect.fnUntraced(function* (error: SessionError.Error, replace = false) { yield* flush() + yield* startAssistant() + if (replace || stepFailure === undefined) stepFailure = error + }) + + const publishStepFailure = Effect.fnUntraced(function* () { + if (stepFailed || stepFailure === undefined) return const assistantMessageID = yield* startAssistant() - assistantActive = false - assistantFailed = true + stepFailed = true yield* events.publish(SessionEvent.Step.Failed, { sessionID: input.sessionID, assistantMessageID, - error: { type: "unknown", message }, + error: stepFailure, }) }) const failUnsettledTools = Effect.fn("SessionRunner.failUnsettledTools")(function* ( - message: string, + error: SessionError.Error, hostedOnly = false, ) { + let failed = false for (const [callID, tool] of tools) { if (tool.settled || (hostedOnly && !tool.providerExecuted)) continue tool.settled = true + failed = true yield* events.publish(SessionEvent.Tool.Failed, { sessionID: input.sessionID, assistantMessageID: tool.assistantMessageID, callID, - error: { type: "unknown", message }, - provider: { - executed: tool.providerExecuted, - ...(tool.providerMetadata === undefined ? {} : { metadata: tool.providerMetadata }), - }, + error, + executed: tool.providerExecuted, }) } + return failed }) const assistantMessageIDForTool = (callID: string) => { @@ -232,24 +255,27 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) const publish = Effect.fn("SessionRunner.publishLLMEvent")(function* ( event: LLMEvent, outputPaths: ReadonlyArray = [], + error?: SessionError.Error, ) { switch (event.type) { case "step-start": + yield* startAssistant() return case "text-start": - yield* text.start(event.id) + retryEvidence = true + const startedTextOrdinal = yield* text.start(event.id) yield* events.publish(SessionEvent.Text.Started, { sessionID: input.sessionID, assistantMessageID: yield* startAssistant(), - textID: event.id, + ordinal: startedTextOrdinal, }) return case "text-delta": - yield* text.append(event.id, event.text) + const deltaTextOrdinal = yield* text.append(event.id, event.text) yield* events.publish(SessionEvent.Text.Delta, { sessionID: input.sessionID, assistantMessageID: yield* currentAssistantMessageID(), - textID: event.id, + ordinal: deltaTextOrdinal, delta: event.text, }) return @@ -257,27 +283,29 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) yield* text.end(event.id) return case "reasoning-start": - yield* reasoning.start(event.id) + retryEvidence = true + const startedReasoningOrdinal = yield* reasoning.start(event.id) yield* events.publish(SessionEvent.Reasoning.Started, { sessionID: input.sessionID, assistantMessageID: yield* startAssistant(), - reasoningID: event.id, - providerMetadata: event.providerMetadata, + ordinal: startedReasoningOrdinal, + state: providerState(event.providerMetadata), }) return case "reasoning-delta": - yield* reasoning.append(event.id, event.text) + const deltaReasoningOrdinal = yield* reasoning.append(event.id, event.text) yield* events.publish(SessionEvent.Reasoning.Delta, { sessionID: input.sessionID, assistantMessageID: yield* currentAssistantMessageID(), - reasoningID: event.id, + ordinal: deltaReasoningOrdinal, delta: event.text, }) return case "reasoning-end": - yield* reasoning.end(event.id, event.providerMetadata) + yield* reasoning.end(event.id, providerState(event.providerMetadata)) return case "tool-input-start": + retryEvidence = true yield* startToolInput(event) return case "tool-input-delta": { @@ -299,6 +327,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) yield* endToolInput(event) return case "tool-call": { + retryEvidence = true if (!tools.has(event.id)) yield* startToolInput(event) const tool = tools.get(event.id)! if (!tool.inputEnded) yield* endToolInput(event) @@ -307,21 +336,19 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) if (tool.called) return yield* Effect.die(new Error(`Duplicate tool call: ${event.id}`)) tool.called = true tool.providerExecuted = event.providerExecuted === true - tool.providerMetadata = event.providerMetadata + const state = providerState(event.providerMetadata) yield* events.publish(SessionEvent.Tool.Called, { sessionID: input.sessionID, assistantMessageID: tool.assistantMessageID, callID: event.id, - tool: event.name, input: record(event.input), - provider: { - executed: tool.providerExecuted, - ...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }), - }, + executed: tool.providerExecuted, + state, }) return } case "tool-result": { + retryEvidence = true const tool = tools.get(event.id) if (!tool?.called) return yield* Effect.die(new Error(`Tool result before call: ${event.id}`)) if (tool.name !== event.name) @@ -331,11 +358,9 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) return yield* Effect.die(new Error(`Duplicate tool result: ${event.id}`)) } tool.settled = true - const result = settledOutput(event.output, event.result) - const provider = { - executed: event.providerExecuted === true || tool.providerExecuted, - ...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }), - } + const result = error ? { error } : settledOutput(event.output, event.result) + const executed = event.providerExecuted === true || tool.providerExecuted + const resultState = providerState(event.providerMetadata) if ("error" in result) { yield* events.publish(SessionEvent.Tool.Failed, { sessionID: input.sessionID, @@ -343,7 +368,8 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) callID: event.id, error: result.error, result: event.result, - provider, + executed, + resultState, }) return } @@ -353,12 +379,14 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) callID: event.id, ...result, outputPaths, - ...(provider.executed ? { result: event.result } : {}), - provider, + ...(executed ? { result: event.result } : {}), + executed, + resultState, }) return } case "tool-error": { + retryEvidence = true const tool = tools.get(event.id) if (!tool?.called) return yield* Effect.die(new Error(`Tool error before call: ${event.id}`)) if (tool.name !== event.name) @@ -369,25 +397,30 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) sessionID: input.sessionID, assistantMessageID: tool.assistantMessageID, callID: event.id, - error: { type: "unknown", message: event.message }, - provider: { - executed: tool.providerExecuted, - ...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }), - }, + error: + event.message === `Unknown tool: ${event.name}` + ? { type: "tool.unknown", message: event.message } + : { type: "tool.execution", message: event.message }, + executed: tool.providerExecuted, + resultState: providerState(event.providerMetadata), }) return } case "step-finish": yield* flush() - assistantActive = false if (stepSettlement) return yield* Effect.die(new Error("Duplicate step finish")) + if (event.reason === "content-filter") { + providerFailed = true + yield* failAssistant({ type: "provider.content-filter", message: "Provider blocked the response" }, true) + return + } stepSettlement = { finish: event.reason, tokens: tokens(event.usage) } return case "finish": return case "provider-error": providerFailed = true - yield* failAssistant(event.message) + yield* failAssistant({ type: "provider.unknown", message: event.message }, true) return } }) @@ -396,10 +429,11 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) publish, flush, failAssistant, + publishStepFailure, failUnsettledTools, - hasActiveAssistant: () => assistantActive, - hasAssistantStarted: () => assistantMessageID !== undefined, hasProviderError: () => providerFailed, + hasRetryEvidence: () => retryEvidence, + stepFailure: () => stepFailure, stepSettlement: () => stepSettlement, startAssistant, assistantMessageID: assistantMessageIDForTool, diff --git a/packages/core/src/session/runner/retry.ts b/packages/core/src/session/runner/retry.ts new file mode 100644 index 0000000000..3ada83b4e4 --- /dev/null +++ b/packages/core/src/session/runner/retry.ts @@ -0,0 +1,67 @@ +export * as SessionRunnerRetry from "./retry" + +import { LLMError } from "@opencode-ai/llm" +import { SessionError } from "@opencode-ai/schema/session-error" +import { Data, Duration, Effect, Schedule } from "effect" +import { EventV2 } from "../../event" +import { SessionEvent } from "../event" +import { SessionMessage } from "../message" +import { SessionSchema } from "../schema" +import type { SessionRunner } from "./index" + +export class RetryableFailure extends Data.TaggedError("SessionRunner.RetryableFailure")<{ + readonly cause: LLMError + readonly assistantMessageID: SessionMessage.ID + readonly error: SessionError.Error + readonly step: number +}> {} + +export function isRetryable(error: LLMError) { + switch (error.reason._tag) { + case "RateLimit": + case "ProviderInternal": + case "Transport": + return true + case "Authentication": + case "QuotaExceeded": + case "ContentPolicy": + case "InvalidProviderOutput": + case "InvalidRequest": + case "NoRoute": + case "UnknownProvider": + return false + default: { + const exhaustive: never = error.reason + return exhaustive + } + } +} + +const retryAfter = (failure: RetryableFailure) => { + if (failure.cause.reason._tag === "RateLimit" || failure.cause.reason._tag === "ProviderInternal") + return failure.cause.reason.retryAfterMs + return undefined +} + +export const schedule = (events: EventV2.Interface, sessionID: SessionSchema.ID) => + Schedule.exponential("2 seconds").pipe( + Schedule.take(4), + Schedule.setInputType(), + Schedule.passthrough, + Schedule.while(({ input }) => input instanceof RetryableFailure), + Schedule.modifyDelay((failure, delay) => { + const minimum = failure instanceof RetryableFailure ? retryAfter(failure) : undefined + return Effect.succeed(minimum === undefined ? delay : Duration.max(delay, Duration.millis(minimum))) + }), + Schedule.tap((metadata) => + metadata.input instanceof RetryableFailure + ? events.publish(SessionEvent.RetryScheduled, { + sessionID, + assistantMessageID: metadata.input.assistantMessageID, + attempt: metadata.attempt + 1, + at: metadata.now + Duration.toMillis(metadata.duration), + error: metadata.input.error, + }) + : Effect.void, + ), + ) diff --git a/packages/core/src/session/runner/to-llm-message.ts b/packages/core/src/session/runner/to-llm-message.ts index 0663ac8e6d..f4e1d7ca01 100644 --- a/packages/core/src/session/runner/to-llm-message.ts +++ b/packages/core/src/session/runner/to-llm-message.ts @@ -41,8 +41,33 @@ const textAttachment = (file: FileAttachment) => }, }) +const directoryAttachment = (file: FileAttachment) => + Message.make({ + role: "user", + content: [ + `Attached directory: ${file.name ?? (file.source.type === "uri" ? file.source.uri : "directory")}`, + file.description === undefined ? undefined : `Description: ${file.description}`, + file.data.length === 0 ? undefined : "", + file.data.length === 0 ? undefined : Buffer.from(file.data, "base64").toString("utf8"), + ] + .filter((line): line is string => line !== undefined) + .join("\n"), + metadata: { + attachment: { + source: file.source, + name: file.name, + description: file.description, + }, + }, + }) + const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString) +const providerMetadata = ( + provider: string, + state: Record | undefined, +): ProviderMetadata | undefined => (state === undefined ? undefined : { [provider]: state }) + const toolInput = (tool: SessionMessage.AssistantTool) => tool.state.status === "pending" ? Option.getOrElse(decodeToolInput(tool.state.input), () => tool.state.input) @@ -53,7 +78,7 @@ const toolCall = (tool: SessionMessage.AssistantTool, providerMetadata: Provider id: tool.id, name: tool.name, input: toolInput(tool), - providerExecuted: tool.provider?.executed, + providerExecuted: tool.executed, providerMetadata, }) @@ -62,14 +87,14 @@ const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: Provid // TODO: Materialize remote and managed URIs before provider-history lowering. // ToolOutput.toResultValue rejects unresolved URIs rather than treating them as media bytes. const result = - tool.provider?.executed === true && tool.state.result !== undefined + tool.executed === true && tool.state.result !== undefined ? tool.state.result : ToolOutput.toResultValue({ structured: tool.state.structured, content: tool.state.content }) return ToolResultPart.make({ id: tool.id, name: tool.name, result, - providerExecuted: tool.provider?.executed, + providerExecuted: tool.executed, providerMetadata, }) } @@ -78,11 +103,11 @@ const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: Provid id: tool.id, name: tool.name, result: - tool.provider?.executed === true && tool.state.result !== undefined + tool.executed === true && tool.state.result !== undefined ? tool.state.result : { error: tool.state.error, content: tool.state.content, structured: tool.state.structured }, resultType: "error", - providerExecuted: tool.provider?.executed, + providerExecuted: tool.executed, providerMetadata, }) } @@ -100,17 +125,22 @@ const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => { { type: "reasoning", text: item.text, - providerMetadata: reuseProviderMetadata ? item.providerMetadata : undefined, + providerMetadata: reuseProviderMetadata ? providerMetadata(model.providerID, item.state) : undefined, }, ] : item.text.length > 0 ? [{ type: "text", text: item.text }] : [] - const call = toolCall(item, reuseProviderMetadata ? item.provider?.metadata : undefined) - if (item.provider?.executed !== true) return [call] + const call = toolCall( + item, + reuseProviderMetadata ? providerMetadata(model.providerID, item.providerState) : undefined, + ) + if (item.executed !== true) return [call] const result = toolResult( item, - reuseProviderMetadata ? (item.provider.resultMetadata ?? item.provider.metadata) : undefined, + reuseProviderMetadata + ? providerMetadata(model.providerID, item.providerResultState ?? item.providerState) + : undefined, ) return result ? [call, result] : [call] }) @@ -120,9 +150,14 @@ const assistant = (message: SessionMessage.Assistant, model: ModelV2.Ref) => { return part.text !== "" || (part.providerMetadata !== undefined && Object.keys(part.providerMetadata).length > 0) }) const results = message.content - .filter((item): item is SessionMessage.AssistantTool => item.type === "tool" && item.provider?.executed !== true) + .filter((item): item is SessionMessage.AssistantTool => item.type === "tool" && item.executed !== true) .map((item) => - toolResult(item, reuseProviderMetadata ? (item.provider?.resultMetadata ?? item.provider?.metadata) : undefined), + toolResult( + item, + reuseProviderMetadata + ? providerMetadata(model.providerID, item.providerResultState ?? item.providerState) + : undefined, + ), ) .filter((message) => message !== undefined) .map(Message.tool) @@ -141,9 +176,8 @@ function toLLMMessage(message: SessionMessage.Message, model: ModelV2.Ref): Mess case "user": const files = message.files ?? [] return [ - ...files - .filter((file) => file.mime === "text/plain") - .map(textAttachment), + ...files.filter((file) => file.mime === "text/plain").map(textAttachment), + ...files.filter((file) => file.mime === "application/x-directory").map(directoryAttachment), Message.make({ id: message.id, role: "user", @@ -175,6 +209,7 @@ function toLLMMessage(message: SessionMessage.Message, model: ModelV2.Ref): Mess case "assistant": return assistant(message, model) case "compaction": + if (message.status !== "completed") return [] return [ Message.make({ id: message.id, diff --git a/packages/core/src/session/sql.ts b/packages/core/src/session/sql.ts index 3e6f79863c..e7d3bdca58 100644 --- a/packages/core/src/session/sql.ts +++ b/packages/core/src/session/sql.ts @@ -1,4 +1,5 @@ import { sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core" +import { sql } from "drizzle-orm" import { directoryColumn, pathColumn } from "../database/path" import { ProjectTable } from "../project/sql" import type { SessionMessage } from "./message" @@ -29,6 +30,8 @@ export const SessionTable = sqliteTable( .references(() => ProjectTable.id, { onDelete: "cascade" }), workspace_id: text().$type(), parent_id: text().$type(), + fork_session_id: text().$type(), + fork_message_id: text().$type(), slug: text().notNull(), directory: directoryColumn().notNull(), path: pathColumn(), @@ -145,8 +148,9 @@ export const SessionInputTable = sqliteTable( .$type() .notNull() .references(() => SessionTable.id, { onDelete: "cascade" }), - prompt: text({ mode: "json" }).notNull().$type(), - delivery: text().$type().notNull(), + type: text().$type().notNull(), + prompt: text({ mode: "json" }).$type(), + delivery: text().$type(), admitted_seq: integer().notNull(), promoted_seq: integer(), time_created: integer() @@ -154,12 +158,16 @@ export const SessionInputTable = sqliteTable( .$default(() => Date.now()), }, (table) => [ - index("session_input_session_pending_delivery_seq_idx").on( + index("session_input_session_pending_type_delivery_seq_idx").on( table.session_id, table.promoted_seq, + table.type, table.delivery, table.admitted_seq, ), + uniqueIndex("session_input_session_pending_compaction_idx") + .on(table.session_id) + .where(sql`${table.type} = 'compaction' and ${table.promoted_seq} is null`), uniqueIndex("session_input_session_admitted_seq_idx").on(table.session_id, table.admitted_seq), uniqueIndex("session_input_session_promoted_seq_idx").on(table.session_id, table.promoted_seq), ], diff --git a/packages/core/src/session/to-session-error.ts b/packages/core/src/session/to-session-error.ts new file mode 100644 index 0000000000..df84a46668 --- /dev/null +++ b/packages/core/src/session/to-session-error.ts @@ -0,0 +1,55 @@ +import { LLMError, ToolFailure } from "@opencode-ai/llm" +import { SessionError } from "@opencode-ai/schema/session-error" +import { PermissionV2 } from "../permission" +import { QuestionV2 } from "../question" +import { Integration } from "../integration" +import { ToolOutputStore } from "../tool-output-store" +import { StepFailedError, UserInterruptedError } from "./error" +import { SessionRunnerModel } from "./runner/model" + +export function toSessionError(cause: unknown): SessionError.Error { + if (cause instanceof LLMError) { + switch (cause.reason._tag) { + case "RateLimit": + return { type: "provider.rate-limit", message: cause.reason.message } + case "Authentication": + return { type: "provider.auth", message: cause.reason.message } + case "QuotaExceeded": + return { type: "provider.quota", message: cause.reason.message } + case "ContentPolicy": + return { type: "provider.content-filter", message: cause.reason.message } + case "Transport": + return { type: "provider.transport", message: cause.reason.message } + case "ProviderInternal": + return { type: "provider.internal", message: cause.reason.message } + case "InvalidProviderOutput": + return { type: "provider.invalid-output", message: cause.reason.message } + case "InvalidRequest": + return { type: "provider.invalid-request", message: cause.reason.message } + case "NoRoute": + return { type: "provider.no-route", message: cause.reason.message } + case "UnknownProvider": + return { type: "provider.unknown", message: cause.reason.message } + default: { + const exhaustive: never = cause.reason + return exhaustive + } + } + } + if (cause instanceof PermissionV2.BlockedError) return { type: "permission.rejected", message: cause.message } + if (cause instanceof QuestionV2.RejectedError) return { type: "aborted", message: cause.message } + if (cause instanceof ToolFailure) + return cause.error === undefined ? { type: "tool.execution", message: cause.message } : toSessionError(cause.error) + if (cause instanceof StepFailedError) return cause.error + if (cause instanceof UserInterruptedError) return { type: "aborted", message: cause.message } + if ( + cause instanceof SessionRunnerModel.ModelNotSelectedError || + cause instanceof SessionRunnerModel.ModelUnavailableError || + cause instanceof SessionRunnerModel.VariantUnavailableError || + cause instanceof SessionRunnerModel.UnsupportedPackageError + ) + return { type: "provider.no-route", message: cause.message } + if (cause instanceof Integration.AuthorizationError) return { type: "provider.auth", message: cause.message } + if (cause instanceof ToolOutputStore.StorageError) return { type: "unknown", message: cause.message } + return { type: "unknown", message: cause instanceof Error ? cause.message : String(cause) } +} diff --git a/packages/core/src/shell.ts b/packages/core/src/shell.ts index 852e19abd7..a62fef8107 100644 --- a/packages/core/src/shell.ts +++ b/packages/core/src/shell.ts @@ -32,6 +32,7 @@ type Active = { // started after termination resolves immediately from the already-completed deferred. done: Deferred.Deferred timeoutFiber?: Fiber.Fiber + timeout?: (duration: number) => Effect.Effect } /** @@ -50,6 +51,8 @@ export interface Interface { // Resolves once the command reaches a terminal status, returning its final Info. Fails with // NotFoundError if the command is unknown or is removed before it terminates. readonly wait: (id: Shell.ID) => Effect.Effect + // Replaces the running command's timeout from now; zero clears it. + readonly timeout: (id: Shell.ID, duration: number) => Effect.Effect readonly output: (id: Shell.ID, input?: Shell.OutputInput) => Effect.Effect readonly remove: (id: Shell.ID) => Effect.Effect } @@ -124,6 +127,13 @@ export const layer = Layer.effect( return yield* Deferred.await((yield* require(id)).done) }) + const timeout = Effect.fn("Shell.timeout")(function* (id: Shell.ID, duration: number) { + const session = yield* require(id) + if (session.info.status !== "running" || !session.timeout) return session.info + yield* session.timeout(duration) + return session.info + }) + const output = Effect.fn("Shell.output")(function* (id: Shell.ID, input?: Shell.OutputInput) { const session = yield* require(id) const cursor = input?.cursor ?? 0 @@ -265,16 +275,22 @@ export const layer = Layer.effect( if (session.timeoutFiber) yield* Fiber.interrupt(session.timeoutFiber) }) - if (input.timeout) { - session.timeoutFiber = runFork( - Effect.sleep(Duration.millis(input.timeout)).pipe( - Effect.flatMap(() => - finish("timeout", undefined, handle.kill().pipe(Effect.catch(() => Effect.void))), + session.timeout = (duration) => + Effect.gen(function* () { + if (session.timeoutFiber) yield* Fiber.interrupt(session.timeoutFiber) + session.timeoutFiber = undefined + if (duration === 0 || session.info.status !== "running") return + session.timeoutFiber = runFork( + Effect.sleep(Duration.millis(duration)).pipe( + Effect.flatMap(() => + finish("timeout", undefined, handle.kill().pipe(Effect.catch(() => Effect.void))), + ), + Effect.catch(() => Effect.void), ), - Effect.catch(() => Effect.void), - ), - ) - } + ) + }) + + yield* session.timeout(input.timeout) runFork( handle.exitCode.pipe( @@ -296,7 +312,7 @@ export const layer = Layer.effect( return session.info }) - return Service.of({ create, list, get, wait, output, remove }) + return Service.of({ create, list, get, wait, timeout, output, remove }) }), ) diff --git a/packages/core/src/tool/apply-patch.ts b/packages/core/src/tool/apply-patch.ts index 2853a9130b..aee442169e 100644 --- a/packages/core/src/tool/apply-patch.ts +++ b/packages/core/src/tool/apply-patch.ts @@ -75,12 +75,12 @@ export const Plugin = { toModelOutput: ({ output }) => [{ type: "text", text: toModelOutput(output) }], execute: (input, context) => { const applied: Array = [] - const fail = (path: string) => { + const fail = (path: string, error?: unknown) => { const prefix = applied.length === 0 ? `Unable to apply patch at ${path}` : `Patch partially applied before failing at ${path}. Applied: ${applied.map((item) => item.resource).join(", ")}` - return new ToolFailure({ message: prefix }) + return new ToolFailure({ message: prefix, error }) } return Effect.gen(function* () { const source = { @@ -152,7 +152,7 @@ export const Plugin = { before, after: update.content, }) - }).pipe(Effect.mapError(() => fail(hunk.path))) + }).pipe(Effect.mapError((error) => fail(hunk.path, error))) } const patchFiles = prepared.map(patchFile) @@ -182,11 +182,11 @@ export const Plugin = { content: change.content, }) applied.push({ type: change.type, resource: result.resource, target: result.target }) - }).pipe(Effect.mapError(() => fail(change.path))), + }).pipe(Effect.mapError((error) => fail(change.path, error))), { discard: true }, ) return { applied, files: patchFiles } - }).pipe(Effect.mapError((error) => (error instanceof ToolFailure ? error : fail("patch")))) + }).pipe(Effect.mapError((error) => (error instanceof ToolFailure ? error : fail("patch", error)))) }, }), "edit", diff --git a/packages/core/src/tool/edit.ts b/packages/core/src/tool/edit.ts index 88feac6c34..1128243b70 100644 --- a/packages/core/src/tool/edit.ts +++ b/packages/core/src/tool/edit.ts @@ -113,8 +113,9 @@ export const Plugin = { error instanceof FileMutation.StaleContentError ? new ToolFailure({ message: "File changed after permission approval. Read it again before editing.", + error, }) - : new ToolFailure({ message: `Unable to edit ${input.path}` }), + : new ToolFailure({ message: `Unable to edit ${input.path}`, error }), ), ) diff --git a/packages/core/src/tool/glob.ts b/packages/core/src/tool/glob.ts index 80f0ca2da4..4c0938c3d2 100644 --- a/packages/core/src/tool/glob.ts +++ b/packages/core/src/tool/glob.ts @@ -102,7 +102,7 @@ export const Plugin = { Effect.mapError((error) => error instanceof ToolFailure ? error - : new ToolFailure({ message: `Unable to find files matching ${input.pattern}` }), + : new ToolFailure({ message: `Unable to find files matching ${input.pattern}`, error }), ), ), }), diff --git a/packages/core/src/tool/grep.ts b/packages/core/src/tool/grep.ts index 9efce1712a..64f2a3a9d6 100644 --- a/packages/core/src/tool/grep.ts +++ b/packages/core/src/tool/grep.ts @@ -133,7 +133,7 @@ export const Plugin = { Effect.mapError((error) => error instanceof ToolFailure ? error - : new ToolFailure({ message: `Unable to grep for ${input.pattern}` }), + : new ToolFailure({ message: `Unable to grep for ${input.pattern}`, error }), ), ), }), diff --git a/packages/core/src/tool/question.ts b/packages/core/src/tool/question.ts index e19706f4ae..a2ac9828c6 100644 --- a/packages/core/src/tool/question.ts +++ b/packages/core/src/tool/question.ts @@ -76,7 +76,7 @@ export const Plugin = { source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID }, }) .pipe( - Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })), + Effect.mapError((error) => new ToolFailure({ message: "Permission denied: question", error })), Effect.andThen( forms .ask({ diff --git a/packages/core/src/tool/read.ts b/packages/core/src/tool/read.ts index 9ee5b4c42f..f6a7c474b5 100644 --- a/packages/core/src/tool/read.ts +++ b/packages/core/src/tool/read.ts @@ -134,7 +134,7 @@ export const Plugin = { error instanceof Image.SizeError ? error.message : `Unable to read ${input.path}` - return new ToolFailure({ message }) + return new ToolFailure({ message, error }) }), ) }, diff --git a/packages/core/src/tool/registry.ts b/packages/core/src/tool/registry.ts index de11c10e6f..0275cd8d87 100644 --- a/packages/core/src/tool/registry.ts +++ b/packages/core/src/tool/registry.ts @@ -14,6 +14,8 @@ import { definition, permission, registrationEntries, RegistrationError, settle, import { Tools } from "./tools" import { ToolHooks } from "./hooks" import { makeLocationNode } from "../effect/app-node" +import { SessionError } from "@opencode-ai/schema/session-error" +import { toSessionError } from "../session/to-session-error" export type ExecuteInput = { readonly sessionID: SessionSchema.ID @@ -45,6 +47,7 @@ export interface Settlement { readonly result: ToolResultValue readonly output?: ToolOutput readonly outputPaths?: ReadonlyArray + readonly error?: SessionError.Error } export class Service extends Context.Service()("@opencode/v2/ToolRegistry") {} @@ -86,7 +89,10 @@ const registryLayer = Layer.effect( ).pipe( Effect.map((output) => ({ output })), Effect.catchTag("LLM.ToolFailure", (failure) => - Effect.succeed({ result: { type: "error" as const, value: failure.message } }), + Effect.succeed({ + result: { type: "error" as const, value: failure.message }, + error: toSessionError(failure), + }), ), ) let settlement: Settlement @@ -124,20 +130,19 @@ const registryLayer = Layer.effect( result: afterEvent.result, ...(afterEvent.output !== undefined ? { output: afterEvent.output } : {}), ...(afterEvent.outputPaths !== undefined ? { outputPaths: afterEvent.outputPaths } : {}), + ...(settlement.error !== undefined ? { error: settlement.error } : {}), } }) const settleWith = Effect.fn("ToolRegistry.settle")(function* (input: ExecuteInput, advertised: object) { const registration = local.get(input.call.name)?.at(-1)?.registration - if (!registration) + if (!registration || registration.identity !== advertised) { + const message = `Stale tool call: ${input.call.name}` return { - result: { - type: "error" as const, - value: `Stale tool call: ${input.call.name}`, - }, + result: { type: "error" as const, value: message }, + error: { type: "tool.stale" as const, message }, } - if (registration.identity !== advertised) - return { result: { type: "error" as const, value: `Stale tool call: ${input.call.name}` } } + } return yield* settleTool(input, registration.tool) }) @@ -215,7 +220,10 @@ const registryLayer = Layer.effect( if (input.call.name === "execute" && execute) return settleTool(input, execute) const registration = direct.get(input.call.name) if (registration) return settleWith(input, registration.identity) - return Effect.succeed({ result: { type: "error", value: `Unknown tool: ${input.call.name}` } }) + return Effect.succeed({ + result: { type: "error", value: `Unknown tool: ${input.call.name}` }, + error: { type: "tool.unknown", message: `Unknown tool: ${input.call.name}` }, + }) }, } }), diff --git a/packages/core/src/tool/shell.ts b/packages/core/src/tool/shell.ts index c4a2820c16..ca6b9f1969 100644 --- a/packages/core/src/tool/shell.ts +++ b/packages/core/src/tool/shell.ts @@ -8,7 +8,7 @@ import { FSUtil } from "../fs-util" import { LocationMutation } from "../location-mutation" import { PermissionV2 } from "../permission" import { PluginRuntime } from "../plugin/runtime" -import { PositiveInt } from "../schema" +import { NonNegativeInt } from "../schema" import { SessionSchema } from "../session/schema" import { Shell } from "../shell" import { Tool, type Content } from "./tool" @@ -27,10 +27,10 @@ export const Input = Schema.Struct({ workdir: Schema.String.pipe(Schema.optional).annotate({ description: "Working directory. Defaults to the active Location; relative paths resolve from that Location.", }), - timeout: PositiveInt.check(Schema.isLessThanOrEqualTo(MAX_TIMEOUT_MS)) + timeout: NonNegativeInt.check(Schema.isLessThanOrEqualTo(MAX_TIMEOUT_MS)) .pipe(Schema.optional) .annotate({ - description: `Timeout in milliseconds. Defaults to ${DEFAULT_TIMEOUT_MS} and may not exceed ${MAX_TIMEOUT_MS}.`, + description: `Optional timeout in milliseconds. Zero means unlimited. Foreground commands default to ${DEFAULT_TIMEOUT_MS}; background commands default to unlimited. May not exceed ${MAX_TIMEOUT_MS}.`, }), background: Schema.Boolean.pipe(Schema.optional).annotate({ description: @@ -143,7 +143,7 @@ export const Plugin = { draft.add( name, Tool.make({ - description: `Execute one shell command string with the host user's filesystem, process, and network authority. The active Location is the default working directory. Relative workdir values resolve from that Location. External workdir values require external_directory approval; best-effort command-argument path warnings are advisory only. Timeout values are milliseconds (default: ${DEFAULT_TIMEOUT_MS}; maximum: ${MAX_TIMEOUT_MS}). Uses the configured shell when set; otherwise uses /bin/sh on POSIX and COMSPEC or cmd.exe on Windows. Background mode (background=true) launches the command asynchronously and returns immediately; you are notified when it finishes.`, + description: `Execute one shell command string with the host user's filesystem, process, and network authority. The active Location is the default working directory. Relative workdir values resolve from that Location. External workdir values require external_directory approval; best-effort command-argument path warnings are advisory only. An optional timeout may be provided in milliseconds (zero: unlimited; foreground default: ${DEFAULT_TIMEOUT_MS}; maximum: ${MAX_TIMEOUT_MS}). Background commands default to unlimited. Uses the configured shell when set; otherwise uses /bin/sh on POSIX and COMSPEC or cmd.exe on Windows. Background mode (background=true) launches the command asynchronously and returns immediately; you are notified when it finishes.`, input: Input, output: Output, structured: StructuredOutput, @@ -191,7 +191,7 @@ export const Plugin = { if ((yield* fsUtil.stat(target.canonical)).type !== "Directory") return yield* Effect.fail(new Error(`Working directory is not a directory: ${target.canonical}`)) - const timeout = input.timeout ?? DEFAULT_TIMEOUT_MS + const timeout = input.background === true ? (input.timeout ?? 0) : (input.timeout ?? DEFAULT_TIMEOUT_MS) const info = yield* shell.create({ command: input.command, cwd: target.canonical, @@ -252,6 +252,7 @@ export const Plugin = { .block({ id: job.id, sessionID: context.sessionID }) .pipe(Effect.onInterrupt(() => runtime.job.cancel(job.id).pipe(Effect.ignore))) if (result?.type === "backgrounded") { + yield* shell.timeout(info.id, 0) yield* notifyWhenDone(context.sessionID, context.toolCallID, input.command) return { output: BACKGROUND_STARTED, @@ -270,7 +271,9 @@ export const Plugin = { ...(warnings.length ? { warnings } : {}), } }).pipe( - Effect.mapError(() => new ToolFailure({ message: `Unable to execute command: ${input.command}` })), + Effect.mapError( + (error) => new ToolFailure({ message: `Unable to execute command: ${input.command}`, error }), + ), ), }), ), diff --git a/packages/core/src/tool/subagent.ts b/packages/core/src/tool/subagent.ts index c5932fce8b..0f781c5e6c 100644 --- a/packages/core/src/tool/subagent.ts +++ b/packages/core/src/tool/subagent.ts @@ -107,7 +107,7 @@ export const Plugin = { .get(context.sessionID) .pipe( Effect.mapError( - () => new ToolFailure({ message: `Parent session not found: ${context.sessionID}` }), + (error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }), ), ) const agent = yield* agents.resolve(input.agent) @@ -128,7 +128,7 @@ export const Plugin = { }) .pipe( Effect.mapError( - () => new ToolFailure({ message: `Parent session not found: ${context.sessionID}` }), + (error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }), ), ) diff --git a/packages/core/src/tool/todowrite.ts b/packages/core/src/tool/todowrite.ts index f763be0429..189ee846b9 100644 --- a/packages/core/src/tool/todowrite.ts +++ b/packages/core/src/tool/todowrite.ts @@ -48,7 +48,7 @@ export const Plugin = { }) yield* todos.update({ sessionID: context.sessionID, todos: input.todos }) return { todos: input.todos } - }).pipe(Effect.mapError(() => new ToolFailure({ message: "Unable to update todos" }))), + }).pipe(Effect.mapError((error) => new ToolFailure({ message: "Unable to update todos", error }))), }), ), ) diff --git a/packages/core/src/tool/webfetch.ts b/packages/core/src/tool/webfetch.ts index 0e046daff9..d2efc61c73 100644 --- a/packages/core/src/tool/webfetch.ts +++ b/packages/core/src/tool/webfetch.ts @@ -172,7 +172,7 @@ export const Plugin = { format: input.format, output, } - }).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to fetch ${input.url}` }))), + }).pipe(Effect.mapError((error) => new ToolFailure({ message: `Unable to fetch ${input.url}`, error }))), }), ), ) diff --git a/packages/core/src/tool/websearch.ts b/packages/core/src/tool/websearch.ts index bca404e185..4064dc0f85 100644 --- a/packages/core/src/tool/websearch.ts +++ b/packages/core/src/tool/websearch.ts @@ -246,7 +246,9 @@ export const Plugin = { text: text ?? NO_RESULTS, } }).pipe( - Effect.mapError(() => new ToolFailure({ message: `Unable to search the web for ${input.query}` })), + Effect.mapError( + (error) => new ToolFailure({ message: `Unable to search the web for ${input.query}`, error }), + ), ) }, }), diff --git a/packages/core/src/tool/write.ts b/packages/core/src/tool/write.ts index 8e8f1b563b..c50a3a1e6d 100644 --- a/packages/core/src/tool/write.ts +++ b/packages/core/src/tool/write.ts @@ -85,7 +85,9 @@ export const Plugin = { source, }) return yield* files.writeTextPreservingBom({ target, content: input.content }) - }).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to write ${input.path}` }))), + }).pipe( + Effect.mapError((error) => new ToolFailure({ message: `Unable to write ${input.path}`, error })), + ), }), "edit", ), diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 1e7e3464dc..15b464f776 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -175,7 +175,7 @@ function mcp(info: typeof ConfigV1.Info.Type) { ) const timeout = info.experimental?.mcp_timeout if (!timeout && !Object.keys(servers).length) return undefined - return { timeout: timeout === undefined ? undefined : { request: timeout }, servers } + return { timeout: timeout === undefined ? undefined : { catalog: timeout, execution: timeout }, servers } } function migrateMcp(info: ConfigMCPV1.Info) { @@ -187,7 +187,7 @@ function migrateMcp(info: ConfigMCPV1.Info) { cwd: info.cwd, environment: info.environment, disabled, - timeout: info.timeout === undefined ? undefined : { request: info.timeout }, + timeout: info.timeout === undefined ? undefined : { catalog: info.timeout, execution: info.timeout }, } return { type: info.type, @@ -201,7 +201,7 @@ function migrateMcp(info: ConfigMCPV1.Info) { redirect_uri: info.oauth.redirectUri, }, disabled, - timeout: info.timeout === undefined ? undefined : { request: info.timeout }, + timeout: info.timeout === undefined ? undefined : { catalog: info.timeout, execution: info.timeout }, } } diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index ae7322292c..85db0a6bf3 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -142,7 +142,7 @@ describe("Config", () => { // V2 nests under `mcp.servers`, so it must not be misdetected and re-migrated. expect(ConfigMigrateV1.isV1({ mcp: { servers: { context7: { type: "local", command: ["npx"] } } } })).toBe(false) expect(ConfigMigrateV1.isV1({ mcp: {} })).toBe(false) - expect(ConfigMigrateV1.isV1({ mcp: { timeout: { request: 1000 } } })).toBe(false) + expect(ConfigMigrateV1.isV1({ mcp: { timeout: { execution: 1000 } } })).toBe(false) }), ) @@ -467,14 +467,14 @@ describe("Config", () => { }, tool_output: { max_lines: 1000, max_bytes: 32768 }, mcp: { - timeout: { startup: 5000, request: 60000 }, + timeout: { startup: 5000, catalog: 60000, execution: 43200000 }, servers: { local: { type: "local", command: ["node", "./mcp/server.js"], environment: { API_KEY: "secret" }, disabled: false, - timeout: { request: 10000 }, + timeout: { catalog: 10000 }, }, remote: { type: "remote", @@ -552,14 +552,14 @@ describe("Config", () => { }) expect(documents[0]?.info.tool_output).toEqual({ max_lines: 1000, max_bytes: 32768 }) expect(documents[0]?.info.mcp).toEqual({ - timeout: { startup: 5000, request: 60000 }, + timeout: { startup: 5000, catalog: 60000, execution: 43200000 }, servers: { local: { type: "local", command: ["node", "./mcp/server.js"], environment: { API_KEY: "secret" }, disabled: false, - timeout: { request: 10000 }, + timeout: { catalog: 10000 }, }, remote: { type: "remote", @@ -792,19 +792,19 @@ describe("Config", () => { buffer: 10000, }) expect(documents[0]?.info.mcp).toMatchObject({ - timeout: { request: 5000 }, + timeout: { catalog: 5000, execution: 5000 }, servers: { local: { type: "local", command: ["node", "server.js"], disabled: true, - timeout: { request: 10000 }, + timeout: { catalog: 10000, execution: 10000 }, }, remote: { type: "remote", url: "https://mcp.example.com", oauth: { client_id: "client", callback_port: 19876 }, - timeout: { request: 20000 }, + timeout: { catalog: 20000, execution: 20000 }, }, }, }) diff --git a/packages/core/test/database-migration.test.ts b/packages/core/test/database-migration.test.ts index eda14d14b1..4f147c0f3d 100644 --- a/packages/core/test/database-migration.test.ts +++ b/packages/core/test/database-migration.test.ts @@ -15,7 +15,10 @@ import eventSourcedSessionInputMigration from "@opencode-ai/core/database/migrat import contextEpochAgentMigration from "@opencode-ai/core/database/migration/20260605042240_add_context_epoch_agent" import simplifyIntegrationCredentialsMigration from "@opencode-ai/core/database/migration/20260611192811_lush_chimera" import simplifySessionInputMigration from "@opencode-ai/core/database/migration/20260622202450_simplify_session_input" +import resetSessionEventsMigration from "@opencode-ai/core/database/migration/20260703200000_reset_v2_session_events" +import durableSessionInboxMigration from "@opencode-ai/core/database/migration/20260707010146_durable_session_inbox" import renameInstructionsMigration from "@opencode-ai/core/database/migration/20260705180000_rename_instructions" +import addSessionForkMigration from "@opencode-ai/core/database/migration/20260706223930_add-session-fork" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" @@ -39,6 +42,29 @@ const run = (effect: Effect.Effect) => const makeDb = EffectDrizzleSqlite.makeWithDefaults() describe("DatabaseMigration", () => { + test("resets incompatible V2 Session event history", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + yield* db.run(sql`CREATE TABLE session_input (id text PRIMARY KEY)`) + yield* db.run(sql`CREATE TABLE session_message (id text PRIMARY KEY)`) + yield* db.run(sql`CREATE TABLE event (id text PRIMARY KEY)`) + yield* db.run(sql`CREATE TABLE event_sequence (aggregate_id text PRIMARY KEY, seq integer NOT NULL)`) + yield* db.run(sql`INSERT INTO session_input (id) VALUES ('input')`) + yield* db.run(sql`INSERT INTO session_message (id) VALUES ('message')`) + yield* db.run(sql`INSERT INTO event (id) VALUES ('event')`) + yield* db.run(sql`INSERT INTO event_sequence (aggregate_id, seq) VALUES ('session', 1)`) + + yield* DatabaseMigration.applyOnly(db, [resetSessionEventsMigration]) + + expect(yield* db.get(sql`SELECT id FROM session_input`)).toBeUndefined() + expect(yield* db.get(sql`SELECT id FROM session_message`)).toBeUndefined() + expect(yield* db.get(sql`SELECT id FROM event`)).toBeUndefined() + expect(yield* db.get(sql`SELECT aggregate_id FROM event_sequence`)).toBeUndefined() + }), + ) + }) + test("serializes concurrent embedded initialization for one database path", async () => { await using tmp = await tmpdir() const filename = path.join(tmp.path, "embedded.sqlite") @@ -84,13 +110,14 @@ describe("DatabaseMigration", () => { expect(yield* db.get(sql`SELECT count(*) as count FROM migration`)).toEqual({ count: migrations.length }) expect( yield* db.all( - sql`SELECT name FROM sqlite_master WHERE type = 'index' AND name IN ('event_aggregate_seq_idx', 'event_aggregate_type_seq_idx', 'session_input_session_pending_seq_idx', 'session_input_session_pending_delivery_seq_idx', 'session_input_session_admitted_seq_idx', 'session_input_session_promoted_seq_idx', 'session_message_session_idx', 'session_message_session_type_idx', 'session_message_session_seq_idx', 'session_message_session_type_seq_idx', 'session_message_session_time_created_id_idx') ORDER BY name`, + sql`SELECT name FROM sqlite_master WHERE type = 'index' AND name IN ('event_aggregate_seq_idx', 'event_aggregate_type_seq_idx', 'session_input_session_pending_seq_idx', 'session_input_session_pending_delivery_seq_idx', 'session_input_session_pending_type_delivery_seq_idx', 'session_input_session_pending_compaction_idx', 'session_input_session_admitted_seq_idx', 'session_input_session_promoted_seq_idx', 'session_message_session_idx', 'session_message_session_type_idx', 'session_message_session_seq_idx', 'session_message_session_type_seq_idx', 'session_message_session_time_created_id_idx') ORDER BY name`, ), ).toEqual([ { name: "event_aggregate_seq_idx" }, { name: "event_aggregate_type_seq_idx" }, { name: "session_input_session_admitted_seq_idx" }, - { name: "session_input_session_pending_delivery_seq_idx" }, + { name: "session_input_session_pending_compaction_idx" }, + { name: "session_input_session_pending_type_delivery_seq_idx" }, { name: "session_input_session_promoted_seq_idx" }, { name: "session_message_session_seq_idx" }, { name: "session_message_session_time_created_id_idx" }, @@ -132,6 +159,39 @@ describe("DatabaseMigration", () => { ) }) + test("separates existing fork provenance from subagent hierarchy", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY, parent_id text)`) + yield* db.run( + sql`CREATE TABLE event (aggregate_id text NOT NULL, seq integer NOT NULL, type text NOT NULL, data text NOT NULL)`, + ) + yield* db.run(sql`INSERT INTO session VALUES ('ses_source', NULL), ('ses_fork', 'ses_source')`) + yield* db.run( + sql`INSERT INTO event VALUES ('ses_fork', 0, 'session.forked', '{"sessionID":"ses_fork","parentID":"ses_source","from":"msg_boundary"}')`, + ) + + yield* DatabaseMigration.applyOnly(db, [addSessionForkMigration]) + + expect( + yield* db.get(sql`SELECT parent_id, fork_session_id, fork_message_id FROM session WHERE id = 'ses_fork'`), + ).toEqual({ + parent_id: null, + fork_session_id: "ses_source", + fork_message_id: "msg_boundary", + }) + expect( + yield* db.get(sql`SELECT parent_id, fork_session_id, fork_message_id FROM session WHERE id = 'ses_source'`), + ).toEqual({ + parent_id: null, + fork_session_id: null, + fork_message_id: null, + }) + }), + ) + }) + test("renames instruction state without losing rows or durable updates", async () => { await run( Effect.gen(function* () { @@ -295,7 +355,7 @@ describe("DatabaseMigration", () => { sql`INSERT INTO event (id, aggregate_id, seq, type, data, created) VALUES ('event', 'session', 9, 'session.updated.1', '{}', 1)`, ) yield* db.run( - sql`INSERT INTO session_input (id, session_id, prompt, delivery, admitted_seq, time_created) VALUES ('input', 'session', '{}', 'steer', 9, 1)`, + sql`INSERT INTO session_input (id, session_id, type, prompt, delivery, admitted_seq, time_created) VALUES ('input', 'session', 'prompt', '{}', 'steer', 9, 1)`, ) yield* db.run( sql`INSERT INTO session_message (id, session_id, type, seq, time_created, time_updated, data) VALUES ('projected', 'session', 'user', 9, 1, 1, '{}')`, @@ -358,6 +418,37 @@ describe("DatabaseMigration", () => { ) }) + test("preserves admitted prompts while generalizing the durable inbox", async () => { + await run( + Effect.gen(function* () { + const db = yield* makeDb + yield* db.run( + sql`CREATE TABLE session_input (id text PRIMARY KEY, session_id text NOT NULL, prompt text NOT NULL, delivery text NOT NULL, admitted_seq integer NOT NULL, promoted_seq integer, time_created integer NOT NULL)`, + ) + yield* db.run( + sql`INSERT INTO session_input (id, session_id, prompt, delivery, admitted_seq, promoted_seq, time_created) VALUES ('input', 'session', '{"text":"hello"}', 'steer', 4, NULL, 1)`, + ) + + yield* DatabaseMigration.applyOnly(db, [durableSessionInboxMigration]) + + expect( + yield* db.all( + sql`SELECT id, type, prompt, delivery, admitted_seq, promoted_seq FROM session_input ORDER BY admitted_seq`, + ), + ).toEqual([ + { + id: "input", + type: "prompt", + prompt: '{"text":"hello"}', + delivery: "steer", + admitted_seq: 4, + promoted_seq: null, + }, + ]) + }), + ) + }) + test("resets incompatible projected Session messages before adding sequence order", async () => { await run( Effect.gen(function* () { diff --git a/packages/core/test/fixture/mcp-timeout.ts b/packages/core/test/fixture/mcp-timeout.ts new file mode 100644 index 0000000000..15c851236c --- /dev/null +++ b/packages/core/test/fixture/mcp-timeout.ts @@ -0,0 +1,26 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js" +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" +import { + CallToolRequestSchema, + GetPromptRequestSchema, + ListPromptsRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js" + +const server = new Server({ name: "timeout", version: "1.0.0" }, { capabilities: { prompts: {}, tools: {} } }) + +server.setRequestHandler(ListToolsRequestSchema, async () => { + if (process.env.MCP_TIMEOUT_TARGET === "catalog") await Bun.sleep(100) + return { tools: [{ name: "slow", inputSchema: { type: "object" } }] } +}) +server.setRequestHandler(ListPromptsRequestSchema, () => Promise.resolve({ prompts: [{ name: "slow" }] })) +server.setRequestHandler(CallToolRequestSchema, async () => { + await Bun.sleep(100) + return { content: [] } +}) +server.setRequestHandler(GetPromptRequestSchema, async () => { + await Bun.sleep(100) + return { messages: [] } +}) + +await server.connect(new StdioServerTransport()) diff --git a/packages/core/test/mcp.test.ts b/packages/core/test/mcp.test.ts index 0a63188a96..50866cdd5d 100644 --- a/packages/core/test/mcp.test.ts +++ b/packages/core/test/mcp.test.ts @@ -71,9 +71,9 @@ const it = testEffect( describe("MCP errors", () => { test("expose useful messages", () => { expect(new MCP.NotFoundError({ server: MCP.ServerName.make("demo") }).message).toBe("MCP server not found: demo") - expect(new MCP.ToolCallError({ server: MCP.ServerName.make("demo"), tool: "search", message: "failed" }).message).toBe( - "failed", - ) + expect( + new MCP.ToolCallError({ server: MCP.ServerName.make("demo"), tool: "search", message: "failed" }).message, + ).toBe("failed") expect(new MCPClient.NeedsAuthError({ server: "demo" }).message).toBe("MCP server requires authentication: demo") expect(new MCPClient.ConnectError({ server: "demo", message: "offline" }).message).toBe("offline") }) @@ -177,6 +177,70 @@ test("retains output schemas across paginated MCP discovery", async () => { ]) }) +test("applies the configured MCP catalog timeout", async () => { + const result = Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const connection = yield* MCPClient.connect( + "catalog-timeout", + new ConfigMCP.Local({ + type: "local", + command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-timeout.ts")], + environment: { MCP_TIMEOUT_TARGET: "catalog" }, + timeout: new ConfigMCP.Timeout({ catalog: 10 }), + }), + import.meta.dir, + ) + return yield* connection.tools() + }), + ), + ) + + await expect(result).rejects.toThrow("Request timed out") +}) + +test("applies the configured MCP execution timeout", async () => { + const result = Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const connection = yield* MCPClient.connect( + "execution-timeout", + new ConfigMCP.Local({ + type: "local", + command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-timeout.ts")], + timeout: new ConfigMCP.Timeout({ execution: 10 }), + }), + import.meta.dir, + ) + return yield* connection.callTool({ name: "slow" }) + }), + ), + ) + + await expect(result).rejects.toThrow("Request timed out") +}) + +test("applies the configured MCP execution timeout to prompts", async () => { + const result = Effect.runPromise( + Effect.scoped( + Effect.gen(function* () { + const connection = yield* MCPClient.connect( + "prompt-timeout", + new ConfigMCP.Local({ + type: "local", + command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-timeout.ts")], + timeout: new ConfigMCP.Timeout({ execution: 10 }), + }), + import.meta.dir, + ) + return yield* connection.prompt({ name: "slow" }) + }), + ), + ) + + await expect(result).rejects.toThrow("Request timed out") +}) + it.effect("advertises MCP output schemas to Code Mode", () => Effect.gen(function* () { const registry = yield* ToolRegistry.Service @@ -232,7 +296,7 @@ it.effect("does not call MCP when permission is blocked", () => Effect.gen(function* () { calls = 0 assertion = yield* Deferred.make() - decision = Effect.fail(new PermissionV2.BlockedError({ rules: [] })) + decision = Effect.fail(new PermissionV2.BlockedError({ rules: [], permission: "demo_search", resources: ["*"] })) const registry = yield* ToolRegistry.Service yield* waitForTool(registry, "execute") diff --git a/packages/core/test/plugin/skill.test.ts b/packages/core/test/plugin/skill.test.ts index 1a2ba5009d..2bcf675052 100644 --- a/packages/core/test/plugin/skill.test.ts +++ b/packages/core/test/plugin/skill.test.ts @@ -41,8 +41,8 @@ describe("SkillPlugin.Plugin", () => { expect(skills).toContainEqual( expect.objectContaining({ - name: "customize-opencode", - description: expect.stringContaining("opencode's own configuration"), + name: "opencode", + description: expect.stringContaining("any question about OpenCode itself"), }), ) expect(skills).toContainEqual( diff --git a/packages/core/test/session-compact.test.ts b/packages/core/test/session-compact.test.ts index df63072fb9..40d9f48a19 100644 --- a/packages/core/test/session-compact.test.ts +++ b/packages/core/test/session-compact.test.ts @@ -75,7 +75,7 @@ const it = testEffect( ) describe("SessionV2.compact", () => { - it.effect("manually compacts the active session context", () => + it.effect("durably admits and coalesces manual compaction", () => Effect.gen(function* () { requests = [] const session = yield* SessionV2.Service @@ -95,13 +95,22 @@ describe("SessionV2.compact", () => { inputID: messageID, }) - yield* session.compact({ sessionID: created.id }) + expect(yield* session.compact({ id: messageID, sessionID: created.id }).pipe(Effect.flip)).toMatchObject({ + _tag: "Session.CompactionConflictError", + inputID: messageID, + }) + const first = yield* session.compact({ sessionID: created.id }) + const second = yield* session.compact({ sessionID: created.id }) - expect(requests).toHaveLength(1) - expect(JSON.stringify(requests[0]?.messages)).toContain("Please compact this session history.") - expect(yield* session.context(created.id)).toMatchObject([ - { type: "compaction", reason: "manual", summary: "manual session summary", recent: "" }, - ]) + expect(second.id).toBe(first.id) + expect(requests).toHaveLength(0) + expect((yield* session.context(created.id)).find((message) => message.id === first.id)).toMatchObject({ + type: "compaction", + status: "queued", + reason: "manual", + summary: "", + recent: "", + }) }), ) }) diff --git a/packages/core/test/session-compaction.test.ts b/packages/core/test/session-compaction.test.ts index dd5e9fb749..1cfd353f54 100644 --- a/packages/core/test/session-compaction.test.ts +++ b/packages/core/test/session-compaction.test.ts @@ -19,7 +19,7 @@ import { SessionV2 } from "@opencode-ai/core/session" import { Project } from "@opencode-ai/core/project" import { ProjectTable } from "@opencode-ai/core/project/sql" import { AbsolutePath } from "@opencode-ai/core/schema" -import { DateTime, Effect, Layer, Stream } from "effect" +import { DateTime, Effect, Fiber, Layer, Stream } from "effect" import { asc, eq } from "drizzle-orm" import { testEffect } from "./lib/effect" @@ -68,11 +68,30 @@ test("compaction describes tool media without embedding base64", () => { expect(serialized).not.toContain(base64) }) +test("compaction prompt requires the checkpoint headings in order", () => { + const prompt = SessionCompaction.buildPrompt({ context: ["Conversation history"] }) + expect(prompt.match(/^#{2,3} .+$/gm)).toEqual([ + "## Objective", + "## Important Details", + "## Work State", + "## Next Move", + ]) + expect(prompt).toContain("one or two brief sentences") + expect(prompt).toContain("constraints/preferences, decisions and why") + expect(prompt).toContain("Completed:") + expect(prompt).toContain("Active:") + expect(prompt).toContain("Blocked:") + expect(prompt).toContain("immediate concrete action") + expect(prompt).toContain("next action if known") + expect(prompt).toContain("Keep every section, even when empty.") +}) + it.effect("manual compaction summarizes short context instead of no-op", () => Effect.gen(function* () { requests = [] const db = (yield* Database.Service).db const compaction = yield* SessionCompaction.Service + const events = yield* EventV2.Service const store = yield* SessionStore.Service const sessionID = SessionV2.ID.make("ses_manual_compaction") const userMessage = { @@ -108,7 +127,12 @@ it.effect("manual compaction summarizes short context instead of no-op", () => ), ) + const delta = yield* events + .subscribe(SessionEvent.Compaction.Delta) + .pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + yield* Effect.yieldNow expect(yield* compaction.compactManual({ session, messages: [userMessage] })).toBe(true) + expect(Array.from(yield* Fiber.join(delta)).map((event) => event.data.text)).toEqual(["manual summary"]) expect(requests).toHaveLength(1) expect(JSON.stringify(requests[0]?.messages)).toContain("Manual compaction should include this short conversation.") diff --git a/packages/core/test/session-create.test.ts b/packages/core/test/session-create.test.ts index fc3b4d127d..0a2e57edf5 100644 --- a/packages/core/test/session-create.test.ts +++ b/packages/core/test/session-create.test.ts @@ -206,7 +206,8 @@ describe("SessionV2.create", () => { const forkContext = yield* session.context(forked.id) const history = Array.from(yield* Stream.runCollect(logEvents(session, forked.id))) - expect(forked).toMatchObject({ parentID: parent.id, title: "Parent (fork #1)" }) + expect(forked).toMatchObject({ title: "Parent (fork #1)", fork: { sessionID: parent.id } }) + expect(forked.parentID).toBeUndefined() expect(forkContext).toMatchObject([ { type: "user", text: "First" }, { type: "synthetic", text: "parent note", sessionID: forked.id }, @@ -264,6 +265,7 @@ describe("SessionV2.create", () => { const context = yield* session.context(forked.id) const history = Array.from(yield* Stream.runCollect(logEvents(session, forked.id))) + expect(forked.fork).toEqual({ sessionID: parent.id, messageID: second.id }) expect(context).toMatchObject([{ text: "First" }]) expect(context[0]?.id).not.toBe(first.id) expect(history[0]).toMatchObject({ data: { from: second.id } }) diff --git a/packages/core/test/session-error.test.ts b/packages/core/test/session-error.test.ts new file mode 100644 index 0000000000..4ae6196272 --- /dev/null +++ b/packages/core/test/session-error.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, test } from "bun:test" +import { + AuthenticationReason, + ContentPolicyReason, + InvalidProviderOutputReason, + InvalidRequestReason, + LLMError, + NoRouteReason, + ModelID, + ProviderID, + ProviderInternalReason, + QuotaExceededReason, + RateLimitReason, + TransportReason, + UnknownProviderReason, + ToolFailure, +} from "@opencode-ai/llm" +import { PermissionV2 } from "@opencode-ai/core/permission" +import { toSessionError } from "@opencode-ai/core/session/to-session-error" +import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry" + +const llm = (reason: LLMError["reason"]) => new LLMError({ module: "test", method: "stream", reason }) + +describe("toSessionError", () => { + test("maps every LLM reason to the open wire type", () => { + expect(toSessionError(llm(new RateLimitReason({ message: "rate", retryAfterMs: 123 })))).toEqual({ + type: "provider.rate-limit", + message: "rate", + }) + expect(toSessionError(llm(new AuthenticationReason({ message: "auth", kind: "invalid" }))).type).toBe( + "provider.auth", + ) + expect(toSessionError(llm(new QuotaExceededReason({ message: "quota" }))).type).toBe("provider.quota") + expect(toSessionError(llm(new ContentPolicyReason({ message: "blocked" }))).type).toBe("provider.content-filter") + expect(toSessionError(llm(new TransportReason({ message: "transport" }))).type).toBe("provider.transport") + expect(toSessionError(llm(new ProviderInternalReason({ message: "internal", status: 500 }))).type).toBe( + "provider.internal", + ) + expect(toSessionError(llm(new InvalidProviderOutputReason({ message: "output" }))).type).toBe( + "provider.invalid-output", + ) + expect(toSessionError(llm(new InvalidRequestReason({ message: "request" }))).type).toBe("provider.invalid-request") + expect( + toSessionError( + llm( + new NoRouteReason({ + route: "route", + provider: ProviderID.make("provider"), + model: ModelID.make("model"), + }), + ), + ).type, + ).toBe("provider.no-route") + expect(toSessionError(llm(new UnknownProviderReason({ message: "unknown" }))).type).toBe("provider.unknown") + }) + + test("preserves the permission rejection type without exposing internal fields", () => { + const blocked = new PermissionV2.BlockedError({ rules: [], permission: "external_directory", resources: [] }) + expect(toSessionError(blocked)).toEqual({ + type: "permission.rejected", + message: "Permission denied: external_directory", + }) + expect(toSessionError(new ToolFailure({ message: blocked.message, error: blocked }))).toEqual({ + type: "permission.rejected", + message: "Permission denied: external_directory", + }) + }) + + test("retries only rate limits, provider-internal failures, and transport failures", () => { + const eligible = [ + llm(new RateLimitReason({ message: "rate" })), + llm(new ProviderInternalReason({ message: "internal", status: 500 })), + llm(new TransportReason({ message: "transport" })), + ] + const ineligible = [ + llm(new AuthenticationReason({ message: "auth", kind: "invalid" })), + llm(new QuotaExceededReason({ message: "quota" })), + llm(new ContentPolicyReason({ message: "blocked" })), + llm(new InvalidProviderOutputReason({ message: "output" })), + llm(new InvalidRequestReason({ message: "request" })), + llm(new NoRouteReason({ route: "route", provider: ProviderID.make("provider"), model: ModelID.make("model") })), + llm(new UnknownProviderReason({ message: "unknown" })), + ] + + expect(eligible.map(SessionRunnerRetry.isRetryable)).toEqual([true, true, true]) + expect(ineligible.map(SessionRunnerRetry.isRetryable)).toEqual([false, false, false, false, false, false, false]) + }) +}) diff --git a/packages/core/test/session-execution-local.test.ts b/packages/core/test/session-execution-local.test.ts new file mode 100644 index 0000000000..be1c4aebea --- /dev/null +++ b/packages/core/test/session-execution-local.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "bun:test" +import { LLMError, TransportReason } from "@opencode-ai/llm" +import { terminal } from "@opencode-ai/core/session/execution/local" +import { UserInterruptedError } from "@opencode-ai/core/session/error" +import { ToolOutputStore } from "@opencode-ai/core/tool-output-store" +import { Effect, Exit } from "effect" + +describe("SessionExecutionLocal lifecycle", () => { + test("classifies success and typed failure terminals", () => { + expect(terminal(Exit.succeed(undefined))).toEqual({ type: "succeeded" }) + expect( + terminal( + Exit.fail( + new LLMError({ + module: "test", + method: "stream", + reason: new TransportReason({ message: "Disconnected" }), + }), + ), + ), + ).toEqual({ type: "failed", error: { type: "provider.transport", message: "Disconnected" } }) + const storage = new ToolOutputStore.StorageError({ operation: "encode", cause: new Error("invalid output") }) + expect(terminal(Exit.fail(storage))).toEqual({ + type: "failed", + error: { type: "unknown", message: storage.message }, + }) + }) + + test("defaults owner-scope interruption to shutdown and preserves explicit reasons", () => { + const interrupted = Effect.runSyncExit(Effect.interrupt) + expect(terminal(interrupted)).toEqual({ type: "interrupted", reason: "shutdown" }) + expect(terminal(interrupted, "user")).toEqual({ type: "interrupted", reason: "user" }) + expect(terminal(interrupted, "superseded")).toEqual({ type: "interrupted", reason: "superseded" }) + expect(terminal(Exit.fail(new UserInterruptedError()))).toEqual({ type: "interrupted", reason: "user" }) + }) +}) diff --git a/packages/core/test/session-projector.test.ts b/packages/core/test/session-projector.test.ts index 77dd272b12..ce30170362 100644 --- a/packages/core/test/session-projector.test.ts +++ b/packages/core/test/session-projector.test.ts @@ -101,7 +101,7 @@ describe("SessionProjector", () => { }) yield* events.publish(SessionEvent.RevertEvent.Committed, { sessionID, - messageID: boundary, + to: boundary, }) expect( (yield* db.select({ id: SessionMessageTable.id }).from(SessionMessageTable).all()).map((row) => row.id), @@ -437,6 +437,73 @@ describe("SessionProjector", () => { }), ) + it.effect("projects retry state and clears it at the next step or execution terminal", () => + Effect.gen(function* () { + const { db } = yield* Database.Service + yield* db + .insert(ProjectTable) + .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) + .run() + .pipe(Effect.orDie) + yield* db + .insert(SessionTable) + .values({ + id: sessionID, + project_id: Project.ID.global, + slug: "test", + directory: "/project", + title: "test", + version: "test", + }) + .run() + .pipe(Effect.orDie) + const events = yield* EventV2.Service + const first = SessionMessage.ID.make("msg_retry_first") + const second = SessionMessage.ID.make("msg_retry_second") + yield* events.publish(SessionEvent.Step.Started, { sessionID, assistantMessageID: first, agent: "build", model }) + yield* events.publish(SessionEvent.RetryScheduled, { + sessionID, + assistantMessageID: first, + attempt: 2, + at: 2_000, + error: { type: "provider.transport", message: "Disconnected" }, + }) + + const decode = (row: typeof SessionMessageTable.$inferSelect) => + Schema.decodeUnknownSync(SessionMessage.Message)({ ...row.data, id: row.id, type: row.type }) + const firstRow = yield* db + .select() + .from(SessionMessageTable) + .where(eq(SessionMessageTable.id, first)) + .get() + .pipe(Effect.orDie) + const projected = firstRow ?? (yield* Effect.die(new Error("Missing retry projection"))) + expect(decode(projected)).toMatchObject({ + retry: { attempt: 2, at: DateTime.makeUnsafe(2_000), error: { type: "provider.transport" } }, + }) + + yield* events.publish(SessionEvent.Step.Started, { sessionID, assistantMessageID: second, agent: "build", model }) + yield* events.publish(SessionEvent.RetryScheduled, { + sessionID, + assistantMessageID: second, + attempt: 3, + at: 6_000, + error: { type: "provider.internal", message: "Unavailable" }, + }) + yield* events.publish(SessionEvent.Execution.Interrupted, { sessionID, reason: "shutdown" }) + + const rows = yield* db + .select() + .from(SessionMessageTable) + .where(eq(SessionMessageTable.session_id, sessionID)) + .orderBy(asc(SessionMessageTable.seq)) + .all() + .pipe(Effect.orDie) + expect(decode(rows[0])).not.toHaveProperty("retry") + expect(decode(rows[1])).not.toHaveProperty("retry") + }), + ) + it.effect("updates only the newest incomplete assistant projection", () => Effect.gen(function* () { const { db } = yield* Database.Service @@ -530,7 +597,7 @@ describe("SessionProjector", () => { yield* service.publish(SessionEvent.Text.Started, { sessionID, assistantMessageID: SessionMessage.ID.make("msg_assistant_completed"), - textID: "text-stale", + ordinal: 0, }) const rows = yield* db @@ -549,7 +616,7 @@ describe("SessionProjector", () => { type: "assistant", agent: "build", model, - content: [SessionMessage.AssistantText.make({ type: "text", id: "text-stale", text: "" })], + content: [SessionMessage.AssistantText.make({ type: "text", text: "" })], time: { created: DateTime.makeUnsafe(1), completed: DateTime.makeUnsafe(2) }, }), SessionMessage.Assistant.make({ diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index 8250d876d2..3e76ddbb42 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -246,7 +246,9 @@ describe("SessionV2.prompt", () => { mention: { start: 8, end: 17, text: "[Image 1]" }, }, ]) - expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files) + const stored = yield* admitted(message.id) + expect(stored?.type).toBe("prompt") + if (stored?.type === "prompt") expect(stored.prompt.files).toEqual(message.prompt.files) }), ) @@ -275,31 +277,35 @@ describe("SessionV2.prompt", () => { source: { type: "uri", uri: sourceUri.href }, name: "main.ts", }) - expect(Buffer.from(message.prompt.files?.[0]?.data ?? "", "base64").toString("utf8").replace(/\r$/, "")).toBe( - 'import { describe, expect } from "bun:test"', - ) + expect( + Buffer.from(message.prompt.files?.[0]?.data ?? "", "base64") + .toString("utf8") + .replace(/\r$/, ""), + ).toBe('import { describe, expect } from "bun:test"') }), ) - it.effect("rejects directories as file attachments", () => + it.effect("materializes directories as directory attachments", () => Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service const uri = pathToFileURL(import.meta.dir).href - const error = yield* session - .prompt({ - sessionID, - prompt: { text: "Inspect this", files: [{ uri, name: "source" }] }, - resume: false, - }) - .pipe(Effect.flip) - - expect(error).toMatchObject({ - _tag: "Session.AttachmentError", - uri, - message: `Attachment is not a file: ${uri}`, + const message = yield* session.prompt({ + sessionID, + prompt: { text: "Inspect this", files: [{ uri, name: "source" }] }, + resume: false, }) + + expect(message.prompt.files).toHaveLength(1) + expect(message.prompt.files?.[0]).toMatchObject({ + mime: "application/x-directory", + source: { type: "uri", uri }, + name: "source", + }) + expect(Buffer.from(message.prompt.files?.[0]?.data ?? "", "base64").toString("utf8")).toContain( + "session-prompt.test.ts", + ) }), ) @@ -332,7 +338,8 @@ describe("SessionV2.prompt", () => { name: "image.png", }, ]) - expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files) + const stored = yield* admitted(message.id) + expect(stored?.type === "prompt" ? stored.prompt.files : undefined).toEqual(message.prompt.files) }), ) @@ -565,7 +572,12 @@ describe("SessionV2.prompt", () => { const { db } = yield* Database.Service const session = yield* SessionV2.Service const events = yield* EventV2.Service - yield* session.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "Promote once" }), resume: false }) + yield* session.prompt({ + id: messageID, + sessionID, + prompt: PromptInput.Prompt.make({ text: "Promote once" }), + resume: false, + }) yield* Effect.all( [SessionInput.promoteSteers(db, events, sessionID), SessionInput.promoteSteers(db, events, sessionID)], @@ -677,7 +689,12 @@ describe("SessionV2.prompt", () => { .pipe(Effect.orDie) const failure = yield* session - .prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "Conflicting prompt" }), resume: false }) + .prompt({ + id: messageID, + sessionID, + prompt: PromptInput.Prompt.make({ text: "Conflicting prompt" }), + resume: false, + }) .pipe(Effect.flip) expect(failure).toMatchObject({ _tag: "Session.PromptConflictError", sessionID, messageID }) diff --git a/packages/core/test/session-remove.test.ts b/packages/core/test/session-remove.test.ts new file mode 100644 index 0000000000..b0fa62e5a8 --- /dev/null +++ b/packages/core/test/session-remove.test.ts @@ -0,0 +1,62 @@ +import { describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Database } from "@opencode-ai/core/database/database" +import { EventV2 } from "@opencode-ai/core/event" +import { Location } from "@opencode-ai/core/location" +import { ProjectV2 } from "@opencode-ai/core/project" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { SessionV2 } from "@opencode-ai/core/session" +import { SessionExecution } from "@opencode-ai/core/session/execution" +import { SessionProjector } from "@opencode-ai/core/session/projector" +import { SessionStore } from "@opencode-ai/core/session/store" +import { testEffect } from "./lib/effect" + +const projects = Layer.succeed( + ProjectV2.Service, + ProjectV2.Service.of({ + list: () => Effect.succeed([]), + resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }), + directories: () => Effect.succeed([]), + commit: () => Effect.void, + }), +) +const it = testEffect( + AppNodeBuilder.build( + LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]), + [ + [ProjectV2.node, projects], + [SessionExecution.node, SessionExecution.noopLayer], + ], + ), +) +const location = Location.Ref.make({ directory: AbsolutePath.make("/project") }) + +describe("SessionV2.remove", () => { + it.effect("removes a session and its children", () => + Effect.gen(function* () { + const session = yield* SessionV2.Service + const parent = yield* session.create({ location }) + const child = yield* session.create({ parentID: parent.id }) + + yield* session.remove(parent.id) + + expect((yield* session.list()).data).toEqual([]) + expect(yield* Effect.result(session.get(parent.id))).toMatchObject({ _tag: "Failure" }) + expect(yield* Effect.result(session.get(child.id))).toMatchObject({ _tag: "Failure" }) + }), + ) + + it.effect("fails when the session does not exist", () => + Effect.gen(function* () { + const session = yield* SessionV2.Service + const sessionID = SessionV2.ID.make("ses_missing") + + expect(yield* Effect.result(session.remove(sessionID))).toMatchObject({ + _tag: "Failure", + failure: { _tag: "Session.NotFoundError", sessionID }, + }) + }), + ) +}) diff --git a/packages/core/test/session-run-coordinator.test.ts b/packages/core/test/session-run-coordinator.test.ts index ecc4e613ac..c566c3f33c 100644 --- a/packages/core/test/session-run-coordinator.test.ts +++ b/packages/core/test/session-run-coordinator.test.ts @@ -104,8 +104,10 @@ describe("SessionRunCoordinator", () => { Effect.gen(function* () { const failure = new Error("failed") const defect = new Error("defect") + const settled: Exit.Exit[] = [] const coordinator = yield* SessionRunCoordinator.make({ drain: (key: string) => (key === "failure" ? Effect.fail(failure) : Effect.die(defect)), + settled: (_key, exit) => Effect.sync(() => void settled.push(exit)), }) const failed = yield* coordinator.run("failure").pipe(Effect.exit) @@ -115,6 +117,25 @@ describe("SessionRunCoordinator", () => { const died = yield* coordinator.run("defect").pipe(Effect.exit) expect(Exit.isFailure(died) && Cause.hasDies(died.cause)).toBeTrue() expect(Array.from(yield* coordinator.active)).toEqual([]) + expect(settled).toHaveLength(2) + }), + ), + ) + + it.effect("preserves settlement hook defects while releasing ownership", () => + Effect.scoped( + Effect.gen(function* () { + const defect = new Error("terminal publication failed") + const coordinator = yield* SessionRunCoordinator.make({ + drain: () => Effect.void, + settled: () => Effect.die(defect), + }) + + const exit = yield* coordinator.run("session").pipe(Effect.exit) + + expect(Exit.isFailure(exit) && Cause.hasDies(exit.cause)).toBe(true) + if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBe(defect) + expect(yield* coordinator.active).toEqual(new Set()) }), ), ) @@ -209,8 +230,41 @@ describe("SessionRunCoordinator", () => { it.effect("does nothing when interrupted while idle", () => Effect.scoped( Effect.gen(function* () { - const coordinator = yield* SessionRunCoordinator.make({ drain: () => Effect.void }) - yield* coordinator.interrupt("session") + const reasons: Array = [] + const coordinator = yield* SessionRunCoordinator.make({ + drain: () => Effect.void, + settled: (_key, _exit, reason) => Effect.sync(() => void reasons.push(reason)), + }) + yield* coordinator.interrupt("session", "user") + yield* coordinator.run("session") + expect(reasons).toEqual([undefined]) + }), + ), + ) + + it.effect("does not attach a late interrupt reason after terminal settlement starts", () => + Effect.scoped( + Effect.gen(function* () { + const settling = yield* Deferred.make() + const release = yield* Deferred.make() + const reasons: Array = [] + const coordinator = yield* SessionRunCoordinator.make({ + drain: () => Effect.void, + settled: (_key, _exit, reason) => + Deferred.succeed(settling, undefined).pipe( + Effect.andThen(Deferred.await(release)), + Effect.andThen(Effect.sync(() => void reasons.push(reason))), + ), + }) + + const run = yield* coordinator.run("session").pipe(Effect.forkChild) + yield* Deferred.await(settling) + yield* coordinator.interrupt("session", "user") + yield* Deferred.succeed(release, undefined) + yield* Fiber.join(run) + yield* coordinator.run("session") + + expect(reasons).toEqual([undefined, undefined]) }), ), ) @@ -221,25 +275,28 @@ describe("SessionRunCoordinator", () => { const started = yield* Deferred.make() const interrupted = yield* Deferred.make() let runs = 0 - const coordinator = yield* SessionRunCoordinator.make({ + const reasons: Array = [] + const coordinator = yield* SessionRunCoordinator.make({ drain: () => Effect.sync(() => ++runs).pipe( Effect.andThen(Deferred.succeed(started, undefined)), Effect.andThen(Effect.never), Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)), ), + settled: (_key, _exit, reason) => Effect.sync(() => void reasons.push(reason)), }) const resumed = yield* coordinator.run("session").pipe(Effect.forkChild) yield* Deferred.await(started) yield* coordinator.wake("session") - yield* coordinator.interrupt("session") + yield* coordinator.interrupt("session", "user") yield* Deferred.await(interrupted) const exit = yield* Fiber.await(resumed) expect(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)).toBeTrue() expect(Array.from(yield* coordinator.active)).toEqual([]) expect(runs).toBe(1) + expect(reasons).toEqual(["user"]) }), ), ) @@ -252,6 +309,7 @@ describe("SessionRunCoordinator", () => { const cleanupGate = yield* Deferred.make() const secondStarted = yield* Deferred.make() let runs = 0 + let starts = 0 const coordinator = yield* SessionRunCoordinator.make({ drain: () => Effect.sync(() => ++runs).pipe( @@ -266,6 +324,7 @@ describe("SessionRunCoordinator", () => { : Deferred.succeed(secondStarted, undefined), ), ), + started: () => Effect.sync(() => starts++).pipe(Effect.asVoid), }) yield* coordinator.wake("session") @@ -278,6 +337,7 @@ describe("SessionRunCoordinator", () => { yield* Deferred.await(secondStarted) expect(runs).toBe(2) + expect(starts).toBe(2) }), ), ) @@ -399,6 +459,7 @@ describe("SessionRunCoordinator", () => { const gate = yield* Deferred.make() const idle = yield* Deferred.make() let drains = 0 + let starts = 0 const settled: Exit.Exit[] = [] const coordinator = yield* SessionRunCoordinator.make({ drain: () => @@ -410,6 +471,7 @@ describe("SessionRunCoordinator", () => { ), Effect.asVoid, ), + started: () => Effect.sync(() => starts++).pipe(Effect.asVoid), settled: (_key, exit) => Effect.sync(() => void settled.push(exit)).pipe( Effect.andThen(Deferred.succeed(idle, undefined)), @@ -424,6 +486,7 @@ describe("SessionRunCoordinator", () => { yield* Deferred.await(idle) expect(drains).toBe(2) + expect(starts).toBe(1) expect(settled).toHaveLength(1) expect(Exit.isSuccess(settled[0]!)).toBe(true) }), diff --git a/packages/core/test/session-runner-message.test.ts b/packages/core/test/session-runner-message.test.ts index f415614cf5..b34d0e9918 100644 --- a/packages/core/test/session-runner-message.test.ts +++ b/packages/core/test/session-runner-message.test.ts @@ -27,17 +27,14 @@ describe("toLLMMessages", () => { const messages = toLLMMessages( [ assistant("empty", []), - assistant("empty-text", [SessionMessage.AssistantText.make({ type: "text", id: "empty", text: "" })]), - assistant("empty-reasoning", [ - SessionMessage.AssistantReasoning.make({ type: "reasoning", id: "empty-reasoning", text: "" }), - ]), - assistant("text", [SessionMessage.AssistantText.make({ type: "text", id: "text", text: "Partial" })]), + assistant("empty-text", [SessionMessage.AssistantText.make({ type: "text", text: "" })]), + assistant("empty-reasoning", [SessionMessage.AssistantReasoning.make({ type: "reasoning", text: "" })]), + assistant("text", [SessionMessage.AssistantText.make({ type: "text", text: "Partial" })]), assistant("reasoning", [ SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: "reasoning", text: "", - providerMetadata: { anthropic: { signature: "sig_1" } }, + state: { signature: "sig_1" }, }), ]), ], @@ -109,6 +106,7 @@ describe("toLLMMessages", () => { SessionMessage.Compaction.make({ id: id("compaction"), type: "compaction", + status: "completed", reason: "auto", summary: "Earlier work", recent: "Recent work", @@ -220,6 +218,35 @@ Recent work ]) }) + test("lowers directory attachments as directory context", () => { + const directory = FileAttachment.make({ + data: Base64.make(Buffer.from("lib/\nindex.ts").toString("base64")), + mime: "application/x-directory", + source: { type: "uri", uri: "file:///project/src" }, + name: "src/", + }) + const messages = toLLMMessages( + [ + SessionMessage.User.make({ + id: id("user-directory"), + type: "user", + text: "Review this directory", + files: [directory], + time: { created }, + }), + ], + model, + ) + + expect(messages).toHaveLength(2) + expect(messages[0]).toMatchObject({ + role: "user", + content: [{ type: "text", text: "Attached directory: src/\n\nlib/\nindex.ts" }], + metadata: { attachment: { source: directory.source, name: "src/" } }, + }) + expect(messages[1]?.content).toEqual([{ type: "text", text: "Review this directory" }]) + }) + test("uses materialized image data as provider media and drops unsupported attachments", () => { const data = Base64.make("AAECAw==") const messages = toLLMMessages( @@ -258,12 +285,11 @@ Recent work agent: "build", model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") }, content: [ - SessionMessage.AssistantText.make({ type: "text", id: "text-1", text: "Checking" }), + SessionMessage.AssistantText.make({ type: "text", text: "Checking" }), SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: "reasoning-1", text: "Think", - providerMetadata: { anthropic: { signature: "sig_1" } }, + state: { signature: "sig_1" }, }), SessionMessage.AssistantTool.make({ type: "tool", @@ -308,11 +334,9 @@ Recent work type: "tool", id: "hosted", name: "web_search", - provider: { - executed: true, - metadata: { fake: { continuation: "hosted-call" } }, - resultMetadata: { fake: { continuation: "hosted-result" } }, - }, + executed: true, + providerState: { continuation: "hosted-call" }, + providerResultState: { continuation: "hosted-result" }, state: SessionMessage.ToolStateCompleted.make({ status: "completed", input: { query: "Effect" }, @@ -325,7 +349,8 @@ Recent work type: "tool", id: "hosted-failed", name: "write", - provider: { executed: true, metadata: { fake: { continuation: "failed" } } }, + executed: true, + providerState: { continuation: "failed" }, state: SessionMessage.ToolStateError.make({ status: "error", input: { path: "README.md" }, @@ -345,7 +370,7 @@ Recent work expect(messages.map((message) => message.role)).toEqual(["assistant", "tool"]) expect(messages[0]?.content).toEqual([ { type: "text", text: "Checking" }, - { type: "reasoning", text: "Think", providerMetadata: { anthropic: { signature: "sig_1" } } }, + { type: "reasoning", text: "Think", providerMetadata: { provider: { signature: "sig_1" } } }, { type: "tool-call", id: "pending", name: "read", input: { path: "README.md" } }, { type: "tool-call", id: "running", name: "read", input: { path: "README.md" } }, { @@ -360,14 +385,14 @@ Recent work name: "web_search", input: { query: "Effect" }, providerExecuted: true, - providerMetadata: { fake: { continuation: "hosted-call" } }, + providerMetadata: { provider: { continuation: "hosted-call" } }, }, { type: "tool-result", id: "hosted", name: "web_search", providerExecuted: true, - providerMetadata: { fake: { continuation: "hosted-result" } }, + providerMetadata: { provider: { continuation: "hosted-result" } }, result: { type: "text", value: "Found it" }, }, { @@ -376,14 +401,14 @@ Recent work name: "write", input: { path: "README.md" }, providerExecuted: true, - providerMetadata: { fake: { continuation: "failed" } }, + providerMetadata: { provider: { continuation: "failed" } }, }, { type: "tool-result", id: "hosted-failed", name: "write", providerExecuted: true, - providerMetadata: { fake: { continuation: "failed" } }, + providerMetadata: { provider: { continuation: "failed" } }, result: { type: "error", value: { error: { type: "unknown", message: "Denied" }, content: [], structured: {} }, @@ -417,9 +442,8 @@ Recent work content: [ SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: "reasoning-openai", text: "Think", - providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, + state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" }, }), ], time: { created, completed: created }, @@ -432,7 +456,7 @@ Recent work { type: "reasoning", text: "Think", - providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, + providerMetadata: { provider: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, }, ]) }) @@ -448,19 +472,16 @@ Recent work content: [ SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: "reasoning-failed", text: "Partial thought", - providerMetadata: { openai: { itemId: "rs_failed", reasoningEncryptedContent: null } }, + state: { itemId: "rs_failed", reasoningEncryptedContent: null }, }), SessionMessage.AssistantTool.make({ type: "tool", id: "hosted-failed", name: "web_search", - provider: { - executed: true, - metadata: { openai: { itemId: "call_failed" } }, - resultMetadata: { openai: { itemId: "result_failed" } }, - }, + executed: true, + providerState: { itemId: "call_failed" }, + providerResultState: { itemId: "result_failed" }, state: SessionMessage.ToolStateError.make({ status: "error", input: { query: "Effect" }, @@ -520,19 +541,16 @@ Recent work content: [ SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: "reasoning-old-model", text: "Visible thought", - providerMetadata: { anthropic: { signature: "sig_old" } }, + state: { signature: "sig_old" }, }), SessionMessage.AssistantTool.make({ type: "tool", id: "hosted-old-model", name: "web_search", - provider: { - executed: true, - metadata: { openai: { itemId: "hosted-old-model" } }, - resultMetadata: { openai: { itemId: "hosted-old-model" } }, - }, + executed: true, + providerState: { itemId: "hosted-old-model" }, + providerResultState: { itemId: "hosted-old-model" }, state: SessionMessage.ToolStateCompleted.make({ status: "completed", input: { query: "Effect" }, @@ -546,11 +564,9 @@ Recent work type: "tool", id: "local-old-model", name: "read", - provider: { - executed: false, - metadata: { fake: { call: "old" } }, - resultMetadata: { fake: { result: "old" } }, - }, + executed: false, + providerState: { call: "old" }, + providerResultState: { result: "old" }, state: SessionMessage.ToolStateCompleted.make({ status: "completed", input: { path: "README.md" }, @@ -620,9 +636,8 @@ Recent work content: [ SessionMessage.AssistantReasoning.make({ type: "reasoning", - id: "reasoning-alias", text: "Visible thought", - providerMetadata: { openai: { reasoningEncryptedContent: "encrypted" } }, + state: { reasoningEncryptedContent: "encrypted" }, }), ], time: { created, completed: created }, @@ -635,7 +650,7 @@ Recent work { type: "reasoning", text: "Visible thought", - providerMetadata: { openai: { reasoningEncryptedContent: "encrypted" } }, + providerMetadata: { provider: { reasoningEncryptedContent: "encrypted" } }, }, ]) }) diff --git a/packages/core/test/session-runner-tool-events.test.ts b/packages/core/test/session-runner-tool-events.test.ts index de4700ca07..4fdbb49e8b 100644 --- a/packages/core/test/session-runner-tool-events.test.ts +++ b/packages/core/test/session-runner-tool-events.test.ts @@ -45,6 +45,7 @@ const capture = () => { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider"), }, + provider: "openai", }), } } @@ -88,7 +89,7 @@ test("local tool success serializes media base64 once and reconstructs from stru }) }) -test("provider-executed success retains its compatibility result", async () => { +test("provider-executed success retains its raw provider result", async () => { const { published, publisher } = capture() await Effect.runPromise(publisher.publish(LLMEvent.toolCall({ ...call, providerExecuted: true }))) await Effect.runPromise(publisher.publish(LLMEvent.toolResult({ ...result, providerExecuted: true }))) @@ -96,6 +97,19 @@ test("provider-executed success retains its compatibility result", async () => { expect(success?.data).toHaveProperty("result") }) +test("provider state uses the route provider instead of the catalog provider", async () => { + const { published, publisher } = capture() + await Effect.runPromise( + publisher.publish( + LLMEvent.reasoningStart({ id: "reasoning", providerMetadata: { openai: { itemId: "reasoning" } } }), + ), + ) + + expect(published.find((event) => event.type === "session.reasoning.started.1")?.data).toMatchObject({ + state: { itemId: "reasoning" }, + }) +}) + test("binary failure emits no success event", async () => { const { published, publisher } = capture() await Effect.runPromise(publisher.publish(call)) @@ -112,7 +126,7 @@ test("binary failure emits no success event", async () => { expect(published.some((event) => event.type === "session.tool.failed.1")).toBe(true) }) -test("old success event data containing result still decodes", () => { +test("success event data can carry a provider-executed result", () => { const decoded = Schema.decodeUnknownSync(SessionEvent.Tool.Success.data)({ sessionID, assistantMessageID: SessionMessage.ID.create(), @@ -120,7 +134,7 @@ test("old success event data containing result still decodes", () => { structured: { type: "media", mime: "image/png" }, content: [{ type: "file", uri: `data:image/png;base64,${base64}`, mime: "image/png" }], result: { type: "content", value: [{ type: "file", uri: `data:image/png;base64,${base64}`, mime: "image/png" }] }, - provider: { executed: false }, + executed: true, }) expect(decoded.result).toMatchObject({ type: "content" }) }) @@ -133,3 +147,40 @@ test("step finish records settlement without publishing step ended", async () => expect(published.some((event) => event.type === "step.ended.2")).toBe(false) expect(publisher.stepSettlement()).toMatchObject({ finish: "stop" }) }) + +test("content-filter finish retains failure evidence until step closeout", async () => { + const { published, publisher } = capture() + await Effect.runPromise(publisher.publish(LLMEvent.stepStart({ index: 0 }))) + await Effect.runPromise(publisher.publish(LLMEvent.stepFinish({ index: 0, reason: "content-filter" }))) + + expect(published.map((event) => event.type)).toEqual(["session.step.started.1"]) + await Effect.runPromise(publisher.publishStepFailure()) + expect(published.map((event) => event.type)).toEqual(["session.step.started.1", "session.step.failed.1"]) + expect(published.at(-1)?.data).toMatchObject({ + error: { type: "provider.content-filter", message: "Provider blocked the response" }, + }) + expect(publisher.stepSettlement()).toBeUndefined() +}) + +test("content-filter finish preserves partial streamed text and never ends the step successfully", async () => { + const { published, publisher } = capture() + await Effect.runPromise( + Effect.forEach( + [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.textStart({ id: "text" }), + LLMEvent.textDelta({ id: "text", text: "Partial" }), + LLMEvent.stepFinish({ index: 0, reason: "content-filter" }), + ], + (event) => publisher.publish(event), + { discard: true }, + ), + ) + await Effect.runPromise(publisher.publishStepFailure()) + + expect(published.some((event) => event.type === "session.step.ended.1")).toBe(false) + expect(published.find((event) => event.type === "session.text.ended.1")?.data).toMatchObject({ text: "Partial" }) + expect(published.find((event) => event.type === "session.step.failed.1")?.data).toMatchObject({ + error: { type: "provider.content-filter" }, + }) +}) diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 83d7e04abf..ec893a5527 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -4,8 +4,10 @@ import { LLMError, LLMEvent, Model, + ToolFailure, TransportReason, InvalidRequestReason, + RateLimitReason, type LLMClientShape, type LLMRequest, } from "@opencode-ai/llm" @@ -16,7 +18,6 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" -import { Job } from "@opencode-ai/core/job" import { PermissionV2 } from "@opencode-ai/core/permission" import { EventTable } from "@opencode-ai/core/event/sql" import { Project } from "@opencode-ai/core/project" @@ -26,8 +27,6 @@ import { AbsolutePath } from "@opencode-ai/core/schema" import { SessionV2 } from "@opencode-ai/core/session" import { Snapshot } from "@opencode-ai/core/snapshot" import { SessionEvent } from "@opencode-ai/core/session/event" -import { SessionCompaction } from "@opencode-ai/core/session/compaction" -import { SessionTitle } from "@opencode-ai/core/session/title" import { SessionInput } from "@opencode-ai/core/session/input" import { SessionMessage } from "@opencode-ai/core/session/message" import { PromptInput } from "@opencode-ai/schema/prompt-input" @@ -63,6 +62,7 @@ import { ModelV2 } from "@opencode-ai/core/model" import { Location } from "@opencode-ai/core/location" import { ProviderV2 } from "@opencode-ai/core/provider" import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Schema, Stream } from "effect" +import { TestClock } from "effect/testing" import { asc, eq } from "drizzle-orm" import { testEffect } from "./lib/effect" @@ -154,7 +154,10 @@ const echo = Layer.effectDiscard( description: "Fail unexpectedly", input: Schema.Struct({}), output: Schema.Struct({}), - execute: () => Effect.die("unexpected tool defect"), + execute: () => + (toolExecutionGate ? Deferred.await(toolExecutionGate) : Effect.void).pipe( + Effect.andThen(Effect.die("unexpected tool defect")), + ), }), // BigInt output with no model content forces ToolOutputStore.bound onto its // JSON.stringify encode path, which fails with a typed StorageError. @@ -369,6 +372,20 @@ const providerUnavailable = () => reason: new TransportReason({ message: "Provider unavailable" }), }) +const invalidRequest = () => + new LLMError({ + module: "test", + method: "stream", + reason: new InvalidRequestReason({ message: "Invalid request" }), + }) + +const rateLimited = (retryAfterMs?: number) => + new LLMError({ + module: "test", + method: "stream", + reason: new RateLimitReason({ message: "Rate limited", retryAfterMs }), + }) + const setupOverflowRecovery = Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service @@ -406,6 +423,34 @@ const recordedEventTypes = (id: SessionV2.ID) => ) }) +const recordedStepSettlementEvents = (id: SessionV2.ID, assistantMessageID: SessionMessage.ID) => + Effect.gen(function* () { + const { db } = yield* Database.Service + const settlementTypes = new Set([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.success.1", + "session.tool.failed.1", + "session.step.ended.1", + "session.step.failed.1", + ]) + return (yield* db + .select({ type: EventTable.type, data: EventTable.data }) + .from(EventTable) + .where(eq(EventTable.aggregate_id, id)) + .orderBy(asc(EventTable.seq)) + .all() + .pipe(Effect.orDie)).filter( + (event) => settlementTypes.has(event.type) && event.data.assistantMessageID === assistantMessageID, + ) + }) + +const requireAssistant = (messages: readonly SessionMessage.Message[]) => { + const assistant = messages.find((message) => message.type === "assistant") + if (!assistant) throw new Error("Assistant message missing") + return assistant +} + const replaySessionProjection = (id: SessionV2.ID) => Effect.gen(function* () { const { db } = yield* Database.Service @@ -456,7 +501,7 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string LLMEvent.textStart({ id }), ...chunks.map((text) => LLMEvent.textDelta({ id, text })), ] - const expectedContent = { type: "text", id, text } + const expectedContent = { type: "text", text } return { delta: SessionEvent.Text.Delta, partialEvents, @@ -476,7 +521,7 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string LLMEvent.reasoningStart({ id }), ...chunks.map((text) => LLMEvent.reasoningDelta({ id, text })), ] - const expectedContent = { type: "reasoning", id, text } + const expectedContent = { type: "reasoning", text } return { delta: SessionEvent.Reasoning.Delta, partialEvents, @@ -511,6 +556,7 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string const verifyEphemeralDeltas = (kind: FragmentKind) => Effect.gen(function* () { yield* setup + requests.length = 0 const session = yield* SessionV2.Service const prompt = `Stream ${kind}` const chunks = Array.from({ length: 32 }, (_, index) => `${index},`) @@ -543,6 +589,7 @@ const verifyEphemeralDeltas = (kind: FragmentKind) => const verifyPartialFlushOnFailure = (kind: FragmentKind) => Effect.gen(function* () { yield* setup + requests.length = 0 const session = yield* SessionV2.Service const prompt = `Fail after ${kind}` const fixture = fragmentFixture(kind, fragmentID(kind, "partial"), ["Partial"]) @@ -556,10 +603,11 @@ const verifyPartialFlushOnFailure = (kind: FragmentKind) => { type: "assistant", finish: "error", - error: { type: "unknown", message: "Provider unavailable" }, + error: { type: "provider.transport", message: "Provider unavailable" }, content: [fixture.expectedContent], }, ]) + expect(requests).toHaveLength(1) }) const verifyPartialFlushOnInterruption = (kind: FragmentKind) => @@ -584,7 +632,7 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) => { type: "assistant", finish: "error", - error: { type: "unknown", message: "Step interrupted" }, + error: { type: "aborted", message: "Step interrupted" }, content: [ kind === "tool input" ? { type: "tool", id: fragmentID(kind, "interrupted"), state: { status: "error" } } @@ -613,7 +661,11 @@ describe("SessionRunnerLLM", () => { }), }), }) - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Use application context" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Use application context" }), + resume: false, + }) responses = [ [ LLMEvent.stepStart({ index: 0 }), @@ -661,7 +713,10 @@ describe("SessionRunnerLLM", () => { streamStarted = undefined response = [] - const message = yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Run automatically" }) }) + const message = yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Run automatically" }), + }) yield* session.wait(sessionID) expect(requests).toHaveLength(1) @@ -703,7 +758,12 @@ describe("SessionRunnerLLM", () => { const { db } = yield* Database.Service const messageID = SessionMessage.ID.create() systemUnavailable = true - yield* session.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false }) + yield* session.prompt({ + id: messageID, + sessionID, + prompt: PromptInput.Prompt.make({ text: "First" }), + resume: false, + }) requests.length = 0 const exit = yield* session.resume(sessionID).pipe(Effect.exit) @@ -1240,7 +1300,6 @@ describe("SessionRunnerLLM", () => { requests.length = 0 response = [] yield* session.resume(sessionID) - const compactionID = SessionMessage.ID.create() yield* events.publish(SessionEvent.Compaction.Started, { sessionID, reason: "manual", @@ -1265,6 +1324,101 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("runs one durable compaction barrier before later steer and queued prompts", () => + Effect.gen(function* () { + yield* setup + requests.length = 0 + currentModel = recoveryModel + const session = yield* SessionV2.Service + streamGate = yield* Deferred.make() + streamStarted = yield* Deferred.make() + responses = [ + fragmentFixture("text", "text-active", ["Active complete"]).completeEvents, + [LLMEvent.textDelta({ id: "summary", text: "durable summary" })], + fragmentFixture("text", "text-steer", ["Steer complete"]).completeEvents, + fragmentFixture("text", "text-queue", ["Queue complete"]).completeEvents, + ] + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Active work" }), resume: false }) + const active = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(streamStarted) + + const first = yield* session.compact({ sessionID }) + const second = yield* session.compact({ sessionID }) + expect(second.id).toBe(first.id) + expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toMatchObject({ + id: first.id, + }) + expect((yield* session.messages({ sessionID })).find((message) => message.id === first.id)).toMatchObject({ + type: "compaction", + status: "queued", + }) + + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Steer after compaction" }), + resume: false, + }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Queue after compaction" }), + delivery: "queue", + resume: false, + }) + expect(yield* SessionInput.hasPending((yield* Database.Service).db, sessionID, "steer")).toBe(false) + + yield* Deferred.succeed(streamGate, undefined) + yield* Fiber.join(active) + + expect(requests).toHaveLength(4) + expect(userTexts(requests[1])[0]).toContain("Create a new anchored summary") + expect(userTexts(requests[2])).toContain("Steer after compaction") + expect(userTexts(requests[3])).toContain("Queue after compaction") + expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toBeUndefined() + expect((yield* session.messages({ sessionID })).find((message) => message.id === first.id)).toMatchObject({ + type: "compaction", + status: "completed", + summary: "durable summary", + }) + }), + ) + + it.effect("releases queued prompts when durable compaction fails", () => + Effect.gen(function* () { + yield* setup + requests.length = 0 + currentModel = recoveryModel + const session = yield* SessionV2.Service + streamGate = yield* Deferred.make() + streamStarted = yield* Deferred.make() + responses = [ + fragmentFixture("text", "text-active-failure", ["Active complete"]).completeEvents, + [], + fragmentFixture("text", "text-after-failure", ["Continued"]).completeEvents, + ] + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Active work" }), resume: false }) + const active = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(streamStarted) + + const compaction = yield* session.compact({ sessionID }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Continue after failure" }), + delivery: "queue", + resume: false, + }) + yield* Deferred.succeed(streamGate, undefined) + yield* Fiber.join(active) + + expect(requests).toHaveLength(3) + expect(userTexts(requests[2])).toContain("Continue after failure") + expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toBeUndefined() + expect((yield* session.messages({ sessionID })).find((message) => message.id === compaction.id)).toMatchObject({ + type: "compaction", + status: "failed", + }) + }), + ) + it.effect("automatically compacts into a completed summary and retained recent turn", () => Effect.gen(function* () { yield* setup @@ -1370,7 +1524,7 @@ describe("SessionRunnerLLM", () => { overflow(), ] yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false }) - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long") expect(requests).toHaveLength(3) expect(yield* session.context(sessionID)).toMatchObject([ @@ -1416,7 +1570,7 @@ describe("SessionRunnerLLM", () => { [LLMEvent.providerError({ message: "summary unavailable" })], ] yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false }) - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long") expect(requests).toHaveLength(2) const context = yield* session.context(sessionID) @@ -1466,7 +1620,6 @@ describe("SessionRunnerLLM", () => { systemBaseline = "Changed context" yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false }) yield* session.resume(sessionID) - const compactionID = SessionMessage.ID.create() yield* events.publish(SessionEvent.Compaction.Started, { sessionID, reason: "manual", @@ -1553,7 +1706,7 @@ describe("SessionRunnerLLM", () => { finish: "tool-calls", tokens: { input: 8, output: 3, reasoning: 1, cache: { read: 2, write: 0 } }, content: [ - { type: "reasoning", id: "reasoning-1", text: "Think" }, + { type: "reasoning", text: "Think" }, { type: "tool", id: "call-error", @@ -1561,14 +1714,16 @@ describe("SessionRunnerLLM", () => { state: { status: "error", input: { path: "README.md" }, - error: { type: "unknown", message: "Denied" }, + error: { type: "tool.execution", message: "Denied" }, }, }, { type: "tool", id: "call-provider", name: "web_search", - provider: { executed: true, metadata: { fake: { source: "provider" } } }, + executed: true, + providerState: { source: "provider" }, + providerResultState: { source: "provider" }, state: { status: "completed", input: { query: "hello" }, @@ -1619,7 +1774,8 @@ describe("SessionRunnerLLM", () => { expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "assistant", "tool"]) expect(authorizations).toMatchObject([{ sessionID, toolCallID: "call-echo" }]) expect(executions).toEqual(["hello"]) - expect(yield* session.context(sessionID)).toMatchObject([ + const context = yield* session.context(sessionID) + expect(context).toMatchObject([ { type: "user", text: "Echo this" }, { type: "assistant", @@ -1638,7 +1794,14 @@ describe("SessionRunnerLLM", () => { }, ], }, - { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] }, + { type: "assistant", finish: "stop", content: [{ type: "text", text: "Done" }] }, + ]) + const assistant = requireAssistant(context) + expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.success.1", + "session.step.ended.1", ]) }), ) @@ -1697,15 +1860,24 @@ describe("SessionRunnerLLM", () => { LLMEvent.stepStart({ index: 0 }), LLMEvent.reasoningStart({ id: "reasoning-anthropic" }), LLMEvent.reasoningDelta({ id: "reasoning-anthropic", text: "Signed thought" }), - LLMEvent.reasoningEnd({ id: "reasoning-anthropic", providerMetadata: { anthropic: { signature: "sig_1" } } }), + LLMEvent.reasoningEnd({ + id: "reasoning-anthropic", + providerMetadata: { fake: { signature: "sig_1" }, anthropic: { ignored: true } }, + }), LLMEvent.reasoningStart({ id: "reasoning-openai", - providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: null } }, + providerMetadata: { + fake: { itemId: "rs_1", reasoningEncryptedContent: null }, + openai: { ignored: true }, + }, }), LLMEvent.reasoningDelta({ id: "reasoning-openai", text: "Encrypted thought" }), LLMEvent.reasoningEnd({ id: "reasoning-openai", - providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, + providerMetadata: { + fake: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" }, + openai: { ignored: true }, + }, }), LLMEvent.stepFinish({ index: 0, reason: "stop" }), LLMEvent.finish({ reason: "stop" }), @@ -1718,11 +1890,11 @@ describe("SessionRunnerLLM", () => { { type: "assistant", content: [ - { type: "reasoning", text: "Signed thought", providerMetadata: { anthropic: { signature: "sig_1" } } }, + { type: "reasoning", text: "Signed thought", state: { signature: "sig_1" } }, { type: "reasoning", text: "Encrypted thought", - providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, + state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" }, }, ], }, @@ -1733,11 +1905,11 @@ describe("SessionRunnerLLM", () => { yield* session.resume(sessionID) expect(requests[1]?.messages[1]?.content).toEqual([ - { type: "reasoning", text: "Signed thought", providerMetadata: { anthropic: { signature: "sig_1" } } }, + { type: "reasoning", text: "Signed thought", providerMetadata: { fake: { signature: "sig_1" } } }, { type: "reasoning", text: "Encrypted thought", - providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, + providerMetadata: { fake: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } }, }, ]) }), @@ -1757,14 +1929,14 @@ describe("SessionRunnerLLM", () => { name: "web_search", input: { query: "Effect" }, providerExecuted: true, - providerMetadata: { openai: { itemId: "hosted-search" } }, + providerMetadata: { fake: { itemId: "hosted-search" }, openai: { ignored: true } }, }), LLMEvent.toolResult({ id: "hosted-search", name: "web_search", result: { type: "json", value: [{ title: "Effect" }] }, providerExecuted: true, - providerMetadata: { anthropic: { blockType: "web_search_tool_result" } }, + providerMetadata: { fake: { blockType: "web_search_tool_result" }, anthropic: { ignored: true } }, }), LLMEvent.stepFinish({ index: 0, reason: "stop" }), LLMEvent.finish({ reason: "stop" }), @@ -1784,7 +1956,7 @@ describe("SessionRunnerLLM", () => { name: "web_search", input: { query: "Effect" }, providerExecuted: true, - providerMetadata: { openai: { itemId: "hosted-search" } }, + providerMetadata: { fake: { itemId: "hosted-search" } }, }, { type: "tool-result", @@ -1792,7 +1964,7 @@ describe("SessionRunnerLLM", () => { name: "web_search", result: { type: "json", value: [{ title: "Effect" }] }, providerExecuted: true, - providerMetadata: { anthropic: { blockType: "web_search_tool_result" } }, + providerMetadata: { fake: { blockType: "web_search_tool_result" } }, }, ]) }), @@ -1981,7 +2153,7 @@ describe("SessionRunnerLLM", () => { expect(requests).toHaveLength(1) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Run once" }, - { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-once", text: "Once" }] }, + { type: "assistant", finish: "stop", content: [{ type: "text", text: "Once" }] }, ]) }), ) @@ -2081,7 +2253,11 @@ describe("SessionRunnerLLM", () => { yield* setup const session = yield* SessionV2.Service const { db } = yield* Database.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt current work" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Interrupt current work" }), + resume: false, + }) requests.length = 0 responses = [ @@ -2124,7 +2300,11 @@ describe("SessionRunnerLLM", () => { yield* setup const session = yield* SessionV2.Service const { db } = yield* Database.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt current work" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Interrupt current work" }), + resume: false, + }) requests.length = 0 responses = [ @@ -2280,7 +2460,10 @@ describe("SessionRunnerLLM", () => { yield* Deferred.succeed(firstGate, undefined) while (requests.length < 2) yield* Effect.yieldNow yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Steer before next queued input" }) }) - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Also steer before next queued input" }) }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Also steer before next queued input" }), + }) yield* Deferred.succeed(secondGate, undefined) yield* Fiber.join(first) streamGate = undefined @@ -2353,7 +2536,7 @@ describe("SessionRunnerLLM", () => { requests.length = 0 responses = undefined response = [] - streamFailure = providerUnavailable() + streamFailure = invalidRequest() streamGate = yield* Deferred.make() streamStarted = yield* Deferred.make() @@ -2378,7 +2561,11 @@ describe("SessionRunnerLLM", () => { yield* setup const session = yield* SessionV2.Service const events = yield* EventV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Recover interrupted tool" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Recover interrupted tool" }), + resume: false, + }) yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID) const assistantMessageID = SessionMessage.ID.create() yield* events.publish(SessionEvent.Step.Started, { @@ -2403,9 +2590,8 @@ describe("SessionRunnerLLM", () => { sessionID, assistantMessageID, callID: "call-interrupted", - tool: "echo", input: { text: "stale" }, - provider: { executed: false }, + executed: false, }) requests.length = 0 response = [] @@ -2421,7 +2607,10 @@ describe("SessionRunnerLLM", () => { { type: "tool", id: "call-interrupted", - state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } }, + state: { + status: "error", + error: { type: "tool.stale", message: "Tool execution interrupted: echo" }, + }, }, ], }, @@ -2463,9 +2652,9 @@ describe("SessionRunnerLLM", () => { sessionID, assistantMessageID, callID: "call-hosted-interrupted", - tool: "web_search", input: { query: "stale" }, - provider: { executed: true, metadata: { openai: { itemId: "call-hosted-interrupted" } } }, + executed: true, + state: { itemId: "call-hosted-interrupted" }, }) requests.length = 0 response = [] @@ -2478,7 +2667,7 @@ describe("SessionRunnerLLM", () => { type: "tool-call", id: "call-hosted-interrupted", providerExecuted: true, - providerMetadata: { openai: { itemId: "call-hosted-interrupted" } }, + providerMetadata: { fake: { itemId: "call-hosted-interrupted" } }, }, { type: "tool-result", id: "call-hosted-interrupted", providerExecuted: true, result: { type: "error" } }, ]) @@ -2550,7 +2739,11 @@ describe("SessionRunnerLLM", () => { const defect = new Error("fail after prompt promotion") let fail = true yield* events.project(SessionEvent.PromptPromoted, () => (fail ? Effect.die(defect) : Effect.void)) - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Recover promoted input" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Recover promoted input" }), + resume: false, + }) expect(yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))).toBe(defect) fail = false @@ -2598,7 +2791,11 @@ describe("SessionRunnerLLM", () => { yield* insertSession(otherSessionID) const session = yield* SessionV2.Service yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Run first" }), resume: false }) - yield* session.prompt({ sessionID: otherSessionID, prompt: PromptInput.Prompt.make({ text: "Run second" }), resume: false }) + yield* session.prompt({ + sessionID: otherSessionID, + prompt: PromptInput.Prompt.make({ text: "Run second" }), + resume: false, + }) requests.length = 0 responses = undefined @@ -2659,12 +2856,16 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Retry after failure" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Retry after failure" }), + resume: false, + }) requests.length = 0 responses = undefined response = [] - streamFailure = providerUnavailable() + streamFailure = invalidRequest() streamGate = yield* Deferred.make() streamStarted = yield* Deferred.make() @@ -2727,7 +2928,7 @@ describe("SessionRunnerLLM", () => { }, ], }, - { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-after-error", text: "Recovered" }] }, + { type: "assistant", finish: "stop", content: [{ type: "text", text: "Recovered" }] }, ]) }), ) @@ -2760,7 +2961,8 @@ describe("SessionRunnerLLM", () => { expect(requests).toHaveLength(2) expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "assistant", "tool"]) - expect(yield* session.context(sessionID)).toMatchObject([ + const context = yield* session.context(sessionID) + expect(context).toMatchObject([ { type: "user", text: "Call defect" }, { type: "assistant", @@ -2770,13 +2972,20 @@ describe("SessionRunnerLLM", () => { id: "call-defect", state: { status: "error", - error: { type: "unknown", message: "Tool execution failed: unexpected tool defect" }, + error: { type: "unknown", message: "unexpected tool defect" }, }, }, ], }, { type: "assistant", finish: "stop", content: [{ type: "text", text: "Recovered" }] }, ]) + const assistant = requireAssistant(context) + expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.ended.1", + ]) }), ) @@ -2791,7 +3000,7 @@ describe("SessionRunnerLLM", () => { input: Schema.Struct({}), output: Schema.Struct({}), execute: () => - Effect.fail(new PermissionV2.BlockedError({ rules: [] })).pipe( + Effect.fail(new PermissionV2.BlockedError({ rules: [], permission: "blocked", resources: ["*"] })).pipe( Effect.mapError(() => new Tool.Failure({ message: "Permission blocked" })), ), }), @@ -2955,13 +3164,84 @@ describe("SessionRunnerLLM", () => { status: "error", error: { type: "unknown", - message: expect.stringContaining("Tool execution failed: Failed to encode tool output"), + message: expect.stringContaining("Failed to encode tool output"), + }, + }, + }, + ], + finish: "error", + error: { type: "unknown", message: expect.stringContaining("Failed to encode tool output") }, + }, + ]) + }), + ) + + it.effect("preserves permission rejection and stops before continuation", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + const registry = yield* ToolRegistry.Service + yield* registry.register({ + permissionfail: Tool.make({ + description: "Reject a permission", + input: Schema.Struct({}), + output: Schema.Struct({}), + execute: () => + new ToolFailure({ + message: "Permission denied: edit", + error: new PermissionV2.BlockedError({ + rules: [], + permission: "edit", + resources: ["src/index.ts"], + }), + }), + }), + }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Reject permission" }), + resume: false, + }) + requests.length = 0 + responses = [ + [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ id: "call-permission", name: "permissionfail", input: {} }), + LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }), + LLMEvent.finish({ reason: "tool-calls" }), + ], + [LLMEvent.stepStart({ index: 0 }), LLMEvent.stepFinish({ index: 0, reason: "stop" })], + ] + + const exit = yield* session.resume(sessionID).pipe(Effect.exit) + + expect(exit._tag).toBe("Failure") + expect(requests).toHaveLength(1) + expect(yield* session.context(sessionID)).toMatchObject([ + { type: "user" }, + { + type: "assistant", + finish: "error", + error: { + type: "permission.rejected", + message: "Permission denied: edit", + }, + content: [ + { + type: "tool", + id: "call-permission", + state: { + status: "error", + error: { + type: "permission.rejected", + message: "Permission denied: edit", }, }, }, ], }, ]) + expect(yield* recordedEventTypes(sessionID)).not.toContain("session.step.ended.1") }), ) @@ -3005,7 +3285,7 @@ describe("SessionRunnerLLM", () => { { type: "tool", id: "call-question", - state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } }, + state: { status: "error", error: { type: "aborted", message: "Tool execution interrupted" } }, }, ], }, @@ -3017,7 +3297,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Settle before failing" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Settle before failing" }), + resume: false, + }) const failure = providerUnavailable() toolExecutionGate = yield* Deferred.make() responseStream = Stream.concat( @@ -3035,7 +3319,8 @@ describe("SessionRunnerLLM", () => { expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure) toolExecutionGate = undefined - expect(yield* session.context(sessionID)).toMatchObject([ + const context = yield* session.context(sessionID) + expect(context).toMatchObject([ { type: "user", text: "Settle before failing" }, { type: "assistant", @@ -3044,6 +3329,13 @@ describe("SessionRunnerLLM", () => { ], }, ]) + const assistant = requireAssistant(context) + expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.success.1", + "session.step.failed.1", + ]) }), ) @@ -3051,7 +3343,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt blocked tool" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Interrupt blocked tool" }), + resume: false, + }) executions.length = 0 toolExecutionGate = yield* Deferred.make() responseStream = Stream.concat( @@ -3069,7 +3365,8 @@ describe("SessionRunnerLLM", () => { expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" }) yield* session.interrupt(sessionID) - expect(yield* session.context(sessionID)).toMatchObject([ + const context = yield* session.context(sessionID) + expect(context).toMatchObject([ { type: "user", text: "Interrupt blocked tool" }, { type: "assistant", @@ -3077,11 +3374,18 @@ describe("SessionRunnerLLM", () => { { type: "tool", id: "call-before-interrupt", - state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } }, + state: { status: "error", error: { type: "aborted", message: "Tool execution interrupted" } }, }, ], }, ]) + const assistant = requireAssistant(context) + expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.failed.1", + ]) yield* replaySessionProjection(sessionID) @@ -3101,7 +3405,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt provider" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Interrupt provider" }), + resume: false, + }) requests.length = 0 response = [] streamGate = yield* Deferred.make() @@ -3118,7 +3426,7 @@ describe("SessionRunnerLLM", () => { expect(requests).toHaveLength(1) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Interrupt provider" }, - { type: "assistant", finish: "error", error: { type: "unknown", message: "Step interrupted" } }, + { type: "assistant", finish: "error", error: { type: "aborted", message: "Step interrupted" } }, ]) expect(yield* recordedEventTypes(sessionID)).toContain("session.step.failed.1") yield* session.interrupt(sessionID) @@ -3129,7 +3437,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt tool settlement" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Interrupt tool settlement" }), + resume: false, + }) executions.length = 0 toolExecutionGate = yield* Deferred.make() toolExecutionsStarted = yield* Deferred.make() @@ -3153,12 +3465,12 @@ describe("SessionRunnerLLM", () => { { type: "assistant", finish: "error", - error: { type: "unknown", message: "Step interrupted" }, + error: { type: "aborted", message: "Step interrupted" }, content: [ { type: "tool", id: "call-await-interrupt", - state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } }, + state: { status: "error", error: { type: "aborted", message: "Tool execution interrupted" } }, }, ], }, @@ -3179,7 +3491,11 @@ describe("SessionRunnerLLM", () => { }), ) const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Finish at the limit" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Finish at the limit" }), + resume: false, + }) requests.length = 0 executions.length = 0 @@ -3281,12 +3597,12 @@ describe("SessionRunnerLLM", () => { streamStarted = undefined response = [LLMEvent.stepStart({ index: 0 }), LLMEvent.providerError({ message: "Provider unavailable" })] - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable") expect(requests).toHaveLength(1) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Fail durably" }, - { type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } }, + { type: "assistant", finish: "error", error: { type: "provider.unknown", message: "Provider unavailable" } }, ]) }), ) @@ -3300,21 +3616,92 @@ describe("SessionRunnerLLM", () => { requests.length = 0 response = [LLMEvent.providerError({ message: "Provider unavailable" })] - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable") expect(requests).toHaveLength(1) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Fail before step" }, - { type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } }, + { type: "assistant", finish: "error", error: { type: "provider.unknown", message: "Provider unavailable" } }, ]) }), ) + it.effect("projects content-filter finishes as visible terminal failures", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Blocked response" }), resume: false }) + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.textStart({ id: "partial" }), + LLMEvent.textDelta({ id: "partial", text: "Partial" }), + LLMEvent.stepFinish({ index: 0, reason: "content-filter" }), + LLMEvent.finish({ reason: "content-filter" }), + ] + + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider blocked the response") + expect(yield* session.context(sessionID)).toMatchObject([ + { type: "user" }, + { + type: "assistant", + finish: "error", + error: { type: "provider.content-filter" }, + content: [{ type: "text", text: "Partial" }], + }, + ]) + expect(yield* recordedEventTypes(sessionID)).not.toContain("session.step.ended.1") + }), + ) + + it.effect("settles a local tool before one content-filter step failure", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Tool before blocked response" }), + resume: false, + }) + toolExecutionGate = yield* Deferred.make() + toolExecutionsStarted = yield* Deferred.make() + toolExecutionsReady = 1 + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ id: "call-before-content-filter", name: "echo", input: { text: "settled" } }), + LLMEvent.stepFinish({ index: 0, reason: "content-filter" }), + LLMEvent.finish({ reason: "content-filter" }), + ] + + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(toolExecutionsStarted) + yield* Deferred.succeed(toolExecutionGate, undefined) + expect((yield* Fiber.join(run).pipe(Effect.flip)).message).toBe("Provider blocked the response") + toolExecutionGate = undefined + toolExecutionsStarted = undefined + + const assistant = requireAssistant(yield* session.context(sessionID)) + const events = yield* recordedStepSettlementEvents(sessionID, assistant.id) + expect(events.map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.success.1", + "session.step.failed.1", + ]) + expect( + events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"), + ).toHaveLength(1) + }), + ) + it.effect("does not recover context overflow after durable assistant output", () => Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail after output" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Fail after output" }), + resume: false, + }) requests.length = 0 response = [ @@ -3324,7 +3711,7 @@ describe("SessionRunnerLLM", () => { LLMEvent.textEnd({ id: "text-partial" }), LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" }), ] - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long") expect(requests).toHaveLength(1) expect(yield* session.context(sessionID)).toMatchObject([ @@ -3343,19 +3730,155 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail raw stream durably" }), resume: false }) - const failure = providerUnavailable() + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Fail raw stream durably" }), + resume: false, + }) + const failure = invalidRequest() responseStream = Stream.fail(failure) expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure) yield* replaySessionProjection(sessionID) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Fail raw stream durably" }, - { type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } }, + { type: "assistant", finish: "error", error: { type: "provider.invalid-request", message: "Invalid request" } }, ]) }), ) + it.effect("retries eligible pre-output failures after exponential backoff", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Retry transport" }), resume: false }) + requests.length = 0 + responseStream = Stream.fail(providerUnavailable()) + response = fragmentFixture("text", "retry-success", ["Recovered"]).completeEvents + + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + while (requests.length < 1) yield* Effect.yieldNow + yield* TestClock.adjust("1999 millis") + expect(requests).toHaveLength(1) + yield* TestClock.adjust("1 millis") + yield* Fiber.join(run) + + expect(requests).toHaveLength(2) + const eventTypes = yield* recordedEventTypes(sessionID) + expect(eventTypes).toContain("session.retry.scheduled.1") + expect(eventTypes.filter((type) => type === "session.step.started.1")).toHaveLength(2) + expect(yield* session.context(sessionID)).toMatchObject([ + { type: "user" }, + { type: "assistant", finish: "stop", content: [{ type: "text", text: "Recovered" }] }, + ]) + yield* replaySessionProjection(sessionID) + expect((yield* session.context(sessionID)).filter((message) => message.type === "assistant")).toHaveLength(1) + }), + ) + + it.effect("uses a larger provider retry-after delay", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Retry rate limit" }), resume: false }) + requests.length = 0 + responseStream = Stream.fail(rateLimited(5_000)) + response = fragmentFixture("text", "retry-after-success", ["Recovered"]).completeEvents + + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + while (requests.length < 1) yield* Effect.yieldNow + yield* TestClock.adjust("4999 millis") + expect(requests).toHaveLength(1) + yield* TestClock.adjust("1 millis") + yield* Fiber.join(run) + expect(requests).toHaveLength(2) + }), + ) + + it.effect("stops after five total retry attempts", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Exhaust retries" }), resume: false }) + requests.length = 0 + streamFailure = providerUnavailable() + + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + while (requests.length < 1) yield* Effect.yieldNow + for (const [index, delay] of [2_000, 4_000, 8_000, 16_000].entries()) { + yield* TestClock.adjust(delay) + while (requests.length < index + 2) yield* Effect.yieldNow + } + expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(streamFailure) + expect(requests).toHaveLength(5) + + const database = (yield* Database.Service).db + const retries = yield* database + .select({ data: EventTable.data }) + .from(EventTable) + .where(eq(EventTable.type, "session.retry.scheduled.1")) + .orderBy(asc(EventTable.seq)) + .all() + .pipe(Effect.orDie) + expect(retries.map((event) => event.data)).toMatchObject([ + { attempt: 2, at: 2_000 }, + { attempt: 3, at: 6_000 }, + { attempt: 4, at: 14_000 }, + { attempt: 5, at: 30_000 }, + ]) + expect((yield* recordedEventTypes(sessionID)).filter((type) => type === "session.step.started.1")).toHaveLength(5) + expect((yield* session.context(sessionID)).filter((message) => message.type === "assistant")).toHaveLength(1) + }), + ) + + it.effect("counts retry attempts against the agent step allowance", () => + Effect.gen(function* () { + yield* setup + const agents = yield* AgentV2.Service + yield* agents.transform((editor) => + editor.update(AgentV2.ID.make("build"), (agent) => { + agent.steps = 2 + }), + ) + const session = yield* SessionV2.Service + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Bound retries by steps" }), + resume: false, + }) + requests.length = 0 + const failure = providerUnavailable() + responseStream = Stream.fail(failure) + streamFailure = failure + + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + while (requests.length < 1) yield* Effect.yieldNow + yield* TestClock.adjust("2 seconds") + expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure) + + expect(requests).toHaveLength(2) + const eventTypes = yield* recordedEventTypes(sessionID) + expect(eventTypes.filter((type) => type === "session.step.started.1")).toHaveLength(2) + expect(eventTypes.filter((type) => type === "session.retry.scheduled.1")).toHaveLength(1) + expect((yield* session.context(sessionID)).filter((message) => message.type === "assistant")).toHaveLength(1) + }), + ) + + it.effect("does not retry non-eligible provider failures", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Do not retry" }), resume: false }) + requests.length = 0 + const failure = invalidRequest() + streamFailure = failure + + expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure) + expect(requests).toHaveLength(1) + expect(yield* recordedEventTypes(sessionID)).not.toContain("session.retry.scheduled.1") + }), + ) + it.effect("does not continue automatically after a provider error follows a local tool call", () => Effect.gen(function* () { yield* setup @@ -3368,16 +3891,32 @@ describe("SessionRunnerLLM", () => { requests.length = 0 const executionCount = executions.length + toolExecutionGate = yield* Deferred.make() + toolExecutionsStarted = yield* Deferred.make() + toolExecutionsReady = 1 response = [ LLMEvent.stepStart({ index: 0 }), LLMEvent.toolCall({ id: "call-before-provider-error", name: "echo", input: { text: "settled" } }), LLMEvent.providerError({ message: "Provider unavailable" }), ] - yield* session.resume(sessionID) + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(toolExecutionsStarted) + yield* Deferred.succeed(toolExecutionGate, undefined) + expect((yield* Fiber.join(run).pipe(Effect.flip)).message).toBe("Provider unavailable") + toolExecutionGate = undefined + toolExecutionsStarted = undefined expect(requests).toHaveLength(1) expect(executions.slice(executionCount)).toEqual(["settled"]) + const context = yield* session.context(sessionID) + const assistant = requireAssistant(context) + expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.success.1", + "session.step.failed.1", + ]) }), ) @@ -3385,7 +3924,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail hosted tool durably" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Fail hosted tool durably" }), + resume: false, + }) requests.length = 0 response = [ @@ -3399,16 +3942,54 @@ describe("SessionRunnerLLM", () => { LLMEvent.providerError({ message: "Provider unavailable" }), ] - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable") expect(requests).toHaveLength(1) - expect(yield* session.context(sessionID)).toMatchObject([ + const context = yield* session.context(sessionID) + expect(context).toMatchObject([ { type: "user", text: "Fail hosted tool durably" }, { type: "assistant", content: [{ type: "tool", id: "call-hosted-provider-error", state: { status: "error" } }], }, ]) + const assistant = requireAssistant(context) + expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.failed.1", + ]) + }), + ) + + it.effect("preserves a tool defect before provider failure settlement", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Defect while provider fails" }), + resume: false, + }) + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ id: "call-defect-provider-error", name: "defect", input: {} }), + LLMEvent.providerError({ message: "Provider unavailable" }), + ] + + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable") + + const context = yield* session.context(sessionID) + const assistant = requireAssistant(context) + const events = yield* recordedStepSettlementEvents(sessionID, assistant.id) + expect(events.map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.failed.1", + ]) + expect(events[2]?.data.error).toMatchObject({ type: "unknown", message: "unexpected tool defect" }) }), ) @@ -3416,7 +3997,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail hosted tool at EOF" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Fail hosted tool at EOF" }), + resume: false, + }) response = [ LLMEvent.stepStart({ index: 0 }), LLMEvent.toolCall({ @@ -3427,16 +4012,117 @@ describe("SessionRunnerLLM", () => { }), ] - yield* session.resume(sessionID) + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider did not return a tool result") + const assistant = requireAssistant(yield* session.context(sessionID)) + const events = yield* recordedStepSettlementEvents(sessionID, assistant.id) + expect(events.map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.failed.1", + ]) + expect( + events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"), + ).toHaveLength(1) yield* replaySessionProjection(sessionID) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Fail hosted tool at EOF" }, - { type: "assistant", content: [{ type: "tool", id: "call-hosted-eof", state: { status: "error" } }] }, + { + type: "assistant", + finish: "error", + error: { type: "tool.result-missing" }, + content: [{ type: "tool", id: "call-hosted-eof", state: { status: "error" } }], + }, ]) }), ) + it.effect("fails an unresolved hosted tool before one clean step end", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Settle hosted tool before ending" }), + resume: false, + }) + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ + id: "call-hosted-clean-end", + name: "web_search", + input: { query: "effect" }, + providerExecuted: true, + }), + LLMEvent.stepFinish({ index: 0, reason: "stop" }), + LLMEvent.finish({ reason: "stop" }), + ] + + yield* session.resume(sessionID) + + const assistant = requireAssistant(yield* session.context(sessionID)) + const events = yield* recordedStepSettlementEvents(sessionID, assistant.id) + expect(events.map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.ended.1", + ]) + expect( + events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"), + ).toHaveLength(1) + }), + ) + + it.effect("settles unresolved local and hosted tools before one raw provider failure", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Fail unresolved tools" }), + resume: false, + }) + const failure = invalidRequest() + const providerFailed = yield* Deferred.make() + toolExecutionGate = yield* Deferred.make() + responseStream = Stream.concat( + Stream.fromIterable([ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.toolCall({ id: "call-local-raw-failure", name: "defect", input: {} }), + LLMEvent.toolCall({ + id: "call-hosted-raw-failure-pair", + name: "web_search", + input: { query: "effect" }, + providerExecuted: true, + }), + ]), + Stream.fromEffect(Deferred.succeed(providerFailed, undefined)).pipe(Stream.flatMap(() => Stream.fail(failure))), + ) + + const run = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(providerFailed) + yield* Deferred.succeed(toolExecutionGate, undefined) + expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure) + toolExecutionGate = undefined + + const assistant = requireAssistant(yield* session.context(sessionID)) + const events = yield* recordedStepSettlementEvents(sessionID, assistant.id) + expect(events.map((event) => ({ type: event.type, callID: event.data.callID }))).toEqual([ + { type: "session.step.started.1", callID: undefined }, + { type: "session.tool.called.1", callID: "call-local-raw-failure" }, + { type: "session.tool.called.1", callID: "call-hosted-raw-failure-pair" }, + { type: "session.tool.failed.1", callID: "call-local-raw-failure" }, + { type: "session.tool.failed.1", callID: "call-hosted-raw-failure-pair" }, + { type: "session.step.failed.1", callID: undefined }, + ]) + expect( + events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"), + ).toHaveLength(1) + }), + ) + it.effect("durably fails a hosted tool left unresolved by a raw provider stream failure", () => Effect.gen(function* () { yield* setup @@ -3446,6 +4132,7 @@ describe("SessionRunnerLLM", () => { prompt: PromptInput.Prompt.make({ text: "Fail hosted tool on raw failure" }), resume: false, }) + requests.length = 0 const failure = providerUnavailable() responseStream = Stream.concat( Stream.fromIterable([ @@ -3461,20 +4148,32 @@ describe("SessionRunnerLLM", () => { ) expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure) + expect(requests).toHaveLength(1) + const assistant = requireAssistant(yield* session.context(sessionID)) + const events = yield* recordedStepSettlementEvents(sessionID, assistant.id) + expect(events.map((event) => event.type)).toEqual([ + "session.step.started.1", + "session.tool.called.1", + "session.tool.failed.1", + "session.step.failed.1", + ]) + expect( + events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"), + ).toHaveLength(1) yield* replaySessionProjection(sessionID) expect(yield* session.context(sessionID)).toMatchObject([ { type: "user", text: "Fail hosted tool on raw failure" }, { type: "assistant", finish: "error", - error: { type: "unknown", message: "Provider unavailable" }, + error: { type: "provider.transport", message: "Provider unavailable" }, content: [{ type: "tool", id: "call-hosted-raw-failure", state: { status: "error" } }], }, ]) }), ) - it.effect("keeps interleaved assistant text blocks separate", () => + it.effect("rejects a second text start before the open fragment ends", () => Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service @@ -3487,9 +4186,31 @@ describe("SessionRunnerLLM", () => { LLMEvent.stepStart({ index: 0 }), LLMEvent.textStart({ id: "text-1" }), LLMEvent.textStart({ id: "text-2" }), + ] + + const defect = yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed)) + expect(defect).toBeInstanceOf(Error) + if (!(defect instanceof Error)) return + expect(defect.message).toBe("text start before end: text-2") + }), + ) + + it.effect("projects sequential text fragments as separate content parts", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Two blocks" }), resume: false }) + + responses = undefined + streamGate = undefined + streamStarted = undefined + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.textStart({ id: "text-1" }), LLMEvent.textDelta({ id: "text-1", text: "First" }), - LLMEvent.textDelta({ id: "text-2", text: "Second" }), LLMEvent.textEnd({ id: "text-1" }), + LLMEvent.textStart({ id: "text-2" }), + LLMEvent.textDelta({ id: "text-2", text: "Second" }), LLMEvent.textEnd({ id: "text-2" }), LLMEvent.stepFinish({ index: 0, reason: "stop" }), LLMEvent.finish({ reason: "stop" }), @@ -3502,8 +4223,8 @@ describe("SessionRunnerLLM", () => { { type: "assistant", content: [ - { type: "text", id: "text-1", text: "First" }, - { type: "text", id: "text-2", text: "Second" }, + { type: "text", text: "First" }, + { type: "text", text: "Second" }, ], }, ]) @@ -3542,7 +4263,11 @@ describe("SessionRunnerLLM", () => { Effect.gen(function* () { yield* setup const session = yield* SessionV2.Service - yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Call provider tool" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: PromptInput.Prompt.make({ text: "Call provider tool" }), + resume: false, + }) responses = undefined streamGate = undefined @@ -3553,6 +4278,8 @@ describe("SessionRunnerLLM", () => { LLMEvent.toolInputDelta({ id: "call-parsed", name: "web_search", text: '{"query":"hello"}' }), LLMEvent.toolInputEnd({ id: "call-parsed", name: "web_search" }), LLMEvent.toolCall({ id: "call-parsed", name: "web_search", input: { query: "hello" }, providerExecuted: true }), + LLMEvent.stepFinish({ index: 0, reason: "stop" }), + LLMEvent.finish({ reason: "stop" }), ] yield* session.resume(sessionID) diff --git a/packages/core/test/session-tool-progress.test.ts b/packages/core/test/session-tool-progress.test.ts index e730b3556b..8344771d2a 100644 --- a/packages/core/test/session-tool-progress.test.ts +++ b/packages/core/test/session-tool-progress.test.ts @@ -76,9 +76,8 @@ describe("Tool.Progress", () => { sessionID, assistantMessageID, callID, - tool: "bash", input: { command: "pwd" }, - provider: { executed: false }, + executed: false, }) }) @@ -104,7 +103,7 @@ describe("Tool.Progress", () => { callID: "call-success", structured: { phase: "done" }, content: content("complete"), - provider: { executed: false }, + executed: false, }) expect((yield* readAssistant).content[0]).toMatchObject({ state: { status: "completed", structured: { phase: "done" }, content: content("complete") }, @@ -123,7 +122,7 @@ describe("Tool.Progress", () => { assistantMessageID, callID: "call-failed", error: { type: "unknown", message: "boom" }, - provider: { executed: false }, + executed: false, }) expect((yield* readAssistant).content[1]).toMatchObject({ state: { diff --git a/packages/core/test/shared-schema.test.ts b/packages/core/test/shared-schema.test.ts index 9e48790dd0..ae68d6c7ff 100644 --- a/packages/core/test/shared-schema.test.ts +++ b/packages/core/test/shared-schema.test.ts @@ -103,6 +103,7 @@ test("Core reuses the canonical shared schemas", async () => { [coreIntegration.Ref, Integration.Ref], [coreLocation.Ref, Location.Ref], [coreLLM.ProviderMetadata, LLM.ProviderMetadata], + [coreLLM.FinishReason, LLM.FinishReason], [coreLLM.ToolTextContent, LLM.ToolTextContent], [coreLLM.ToolFileContent, LLM.ToolFileContent], [coreLLM.ToolContent, LLM.ToolContent], @@ -137,7 +138,7 @@ test("Core reuses the canonical shared schemas", async () => { [coreSessionInput.Delivery, SessionInput.Delivery], [coreSessionInput.Admitted, SessionInput.Admitted], [coreSessionMessage.ID, SessionMessage.ID], - [coreSessionMessage.UnknownError, SessionMessage.UnknownError], + [coreSessionMessage.AssistantRetry, SessionMessage.AssistantRetry], [coreSessionMessage.AgentSelected, SessionMessage.AgentSelected], [coreSessionMessage.ModelSelected, SessionMessage.ModelSelected], [coreSessionMessage.User, SessionMessage.User], @@ -183,7 +184,7 @@ test("Core reuses the canonical shared schemas", async () => { test("shared record schemas construct and decode plain objects", () => { const made = Prompt.make({ text: "hello" }) const decoded = Schema.decodeUnknownSync(Prompt)({ text: "hello" }) - const content = Schema.decodeUnknownSync(SessionMessage.AssistantText)({ type: "text", id: "part_1", text: "hi" }) + const content = Schema.decodeUnknownSync(SessionMessage.AssistantText)({ type: "text", text: "hi" }) expect(Object.getPrototypeOf(made)).toBe(Object.prototype) expect(Object.getPrototypeOf(decoded)).toBe(Object.prototype) diff --git a/packages/core/test/tool-apply-patch.test.ts b/packages/core/test/tool-apply-patch.test.ts index a97c3f662a..6aa7c89aa2 100644 --- a/packages/core/test/tool-apply-patch.test.ts +++ b/packages/core/test/tool-apply-patch.test.ts @@ -47,7 +47,15 @@ const permission = Layer.succeed( }).pipe( Effect.andThen(input.action === "edit" ? Effect.suspend(afterEditApproval) : Effect.void), Effect.andThen( - input.action === denyAction ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void, + input.action === denyAction + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), ), ask: () => Effect.die("unused"), diff --git a/packages/core/test/tool-edit.test.ts b/packages/core/test/tool-edit.test.ts index 7481c087cc..ded236e654 100644 --- a/packages/core/test/tool-edit.test.ts +++ b/packages/core/test/tool-edit.test.ts @@ -40,7 +40,15 @@ const permission = Layer.succeed( assert: (input) => Effect.sync(() => assertions.push(input)).pipe( Effect.andThen( - input.action === denyAction ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void, + input.action === denyAction + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), ), ask: () => Effect.die("unused"), diff --git a/packages/core/test/tool-question.test.ts b/packages/core/test/tool-question.test.ts index 4ac6f558a9..7afb2158c8 100644 --- a/packages/core/test/tool-question.test.ts +++ b/packages/core/test/tool-question.test.ts @@ -23,7 +23,17 @@ const permission = Layer.succeed( PermissionV2.Service.of({ assert: (input) => Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen(deny ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void), + Effect.andThen( + deny + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, + ), ), ask: () => Effect.die("unused"), reply: () => Effect.die("unused"), @@ -82,7 +92,13 @@ describe("QuestionTool", () => { ...toolIdentity, call: { type: "tool-call", id: "call-question-denied", name: "question", input: { questions: [] } }, }), - ).toEqual({ result: { type: "error", value: "Permission denied: question" } }) + ).toEqual({ + result: { type: "error", value: "Permission denied: question" }, + error: { + type: "permission.rejected", + message: "Permission denied: question", + }, + }) expect(capturedInput()).toBeUndefined() deny = false }), diff --git a/packages/core/test/tool-read.test.ts b/packages/core/test/tool-read.test.ts index 227be289e9..c5e7eaac48 100644 --- a/packages/core/test/tool-read.test.ts +++ b/packages/core/test/tool-read.test.ts @@ -81,7 +81,19 @@ const permission = Layer.succeed( assert: (input) => Effect.sync(() => { assertions.push(input) - }).pipe(Effect.andThen(allow ? Effect.void : Effect.fail(new PermissionV2.BlockedError({ rules: [] })))), + }).pipe( + Effect.andThen( + allow + ? Effect.void + : Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ), + ), + ), ask: () => Effect.die("unused"), reply: () => Effect.die("unused"), get: () => Effect.die("unused"), diff --git a/packages/core/test/tool-shell.test.ts b/packages/core/test/tool-shell.test.ts index ab3926f3b9..e92465fc3d 100644 --- a/packages/core/test/tool-shell.test.ts +++ b/packages/core/test/tool-shell.test.ts @@ -2,7 +2,7 @@ import fs from "fs/promises" import { realpathSync } from "node:fs" import path from "path" import { describe, expect, test } from "bun:test" -import { DateTime, Effect, Fiber, Layer, Scope } from "effect" +import { DateTime, Duration, Effect, Fiber, Layer, Scope } from "effect" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { makeGlobalNode } from "@opencode-ai/core/effect/app-node" @@ -47,7 +47,15 @@ const permission = Layer.succeed( Effect.sync(() => assertions.push(input)).pipe( Effect.andThen(Effect.suspend(() => afterPermission(input))), Effect.andThen( - input.action === denyAction ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void, + input.action === denyAction + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), ), ask: () => Effect.die("unused"), @@ -75,7 +83,6 @@ const executionNode = makeGlobalNode({ const session = yield* store.get(id) if (!session) return const assistantMessageID = SessionMessage.ID.create() - const textID = "text_shell_test" yield* events.publish(SessionEvent.Step.Started, { sessionID: id, assistantMessageID, @@ -85,12 +92,12 @@ const executionNode = makeGlobalNode({ yield* events.publish(SessionEvent.Text.Started, { sessionID: id, assistantMessageID, - textID, + ordinal: 0, }) yield* events.publish(SessionEvent.Text.Ended, { sessionID: id, assistantMessageID, - textID, + ordinal: 0, text: "ok", }) yield* events.publish(SessionEvent.Step.Ended, { @@ -435,7 +442,10 @@ describe("ShellTool", () => { reset() return withSession(tmp.path, (registry) => Effect.gen(function* () { - const settled = yield* settleTool(registry, call({ command: idleCommand, background: true })) + const settled = yield* settleTool( + registry, + call({ command: idleCommand, timeout: 50, background: true }), + ) const structured = settled.output?.structured as Record | undefined const shellID = typeof structured?.shellID === "string" ? structured.shellID : undefined expect(settled.output?.structured).toMatchObject({ truncated: false }) @@ -445,7 +455,45 @@ describe("ShellTool", () => { if (!shellID) return const id = ShellSchema.ID.make(shellID) expect((yield* shell.list()).map((info) => info.id)).toContain(id) - yield* shell.remove(id) + expect((yield* shell.wait(id)).status).toBe("timeout") + }), + ) + }, + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)), + ), + ) + + it.live("updates and clears a running shell timeout", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => { + reset() + return withSession(tmp.path, (registry) => + Effect.gen(function* () { + const shell = yield* Shell.Service + const timed = yield* settleTool( + registry, + call({ command: idleCommand, background: true }, "call-updated-timeout"), + ) + const timedID = (timed.output?.structured as Record | undefined)?.shellID + expect(typeof timedID).toBe("string") + if (typeof timedID !== "string") return + const timedShellID = ShellSchema.ID.make(timedID) + yield* shell.timeout(timedShellID, 50) + expect((yield* shell.wait(timedShellID)).status).toBe("timeout") + + const cleared = yield* settleTool( + registry, + call({ command: idleCommand, timeout: 50, background: true }, "call-cleared-timeout"), + ) + const clearedID = (cleared.output?.structured as Record | undefined)?.shellID + expect(typeof clearedID).toBe("string") + if (typeof clearedID !== "string") return + const clearedShellID = ShellSchema.ID.make(clearedID) + yield* shell.timeout(clearedShellID, 0) + yield* Effect.sleep(Duration.millis(100)) + expect((yield* shell.get(clearedShellID)).status).toBe("running") + yield* shell.remove(clearedShellID) }), ) }, @@ -462,9 +510,10 @@ describe("ShellTool", () => { Effect.gen(function* () { const jobs = yield* Job.Service const scope = yield* Scope.Scope - const waiting = yield* settleTool(registry, call({ command: idleCommand }, "call-background-signal")).pipe( - Effect.forkIn(scope, { startImmediately: true }), - ) + const waiting = yield* settleTool( + registry, + call({ command: idleCommand, timeout: 50 }, "call-background-signal"), + ).pipe(Effect.forkIn(scope, { startImmediately: true })) const backgroundWhenReady = (remaining = 1000): Effect.Effect => Effect.gen(function* () { @@ -475,7 +524,6 @@ describe("ShellTool", () => { return yield* backgroundWhenReady(remaining - 1) }) expect(yield* backgroundWhenReady()).toMatchObject([{ id: "call-background-signal", type: "shell" }]) - const settled = yield* Fiber.join(waiting) const structured = settled.output?.structured as Record | undefined const shellID = typeof structured?.shellID === "string" ? structured.shellID : undefined @@ -493,6 +541,8 @@ describe("ShellTool", () => { const shell = yield* Shell.Service if (!shellID) return const id = ShellSchema.ID.make(shellID) + yield* Effect.sleep(Duration.millis(100)) + expect((yield* shell.get(id)).status).toBe("running") expect((yield* shell.list()).map((info) => info.id)).toContain(id) yield* shell.remove(id) }), diff --git a/packages/core/test/tool-skill.test.ts b/packages/core/test/tool-skill.test.ts index a9987154d8..6a49fa22fa 100644 --- a/packages/core/test/tool-skill.test.ts +++ b/packages/core/test/tool-skill.test.ts @@ -55,7 +55,17 @@ describe("SkillTool", () => { PermissionV2.Service.of({ assert: (input) => Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen(deny ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void), + Effect.andThen( + deny + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, + ), ), ask: () => Effect.die("unused"), reply: () => Effect.die("unused"), diff --git a/packages/core/test/tool-subagent.test.ts b/packages/core/test/tool-subagent.test.ts index 4dd46da691..9f7bf8c7b9 100644 --- a/packages/core/test/tool-subagent.test.ts +++ b/packages/core/test/tool-subagent.test.ts @@ -49,7 +49,6 @@ const executionNode = makeGlobalNode({ } completed.add(sessionID) const assistantMessageID = SessionMessage.ID.create() - const textID = "text_subagent_test" yield* events.publish(SessionEvent.Step.Started, { sessionID, assistantMessageID, @@ -59,12 +58,12 @@ const executionNode = makeGlobalNode({ yield* events.publish(SessionEvent.Text.Started, { sessionID, assistantMessageID, - textID, + ordinal: 0, }) yield* events.publish(SessionEvent.Text.Ended, { sessionID, assistantMessageID, - textID, + ordinal: 0, text: childText, }) yield* events.publish(SessionEvent.Step.Ended, { diff --git a/packages/core/test/tool-todowrite.test.ts b/packages/core/test/tool-todowrite.test.ts index 1e967c5fcf..055df2fbc7 100644 --- a/packages/core/test/tool-todowrite.test.ts +++ b/packages/core/test/tool-todowrite.test.ts @@ -33,7 +33,17 @@ const permission = Layer.succeed( PermissionV2.Service.of({ assert: (input) => Effect.sync(() => assertions.push(input)).pipe( - Effect.andThen(deny ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void), + Effect.andThen( + deny + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, + ), ), ask: () => Effect.die("unused"), reply: () => Effect.die("unused"), diff --git a/packages/core/test/tool-write.test.ts b/packages/core/test/tool-write.test.ts index 6bd4f2b78e..1fc82f1428 100644 --- a/packages/core/test/tool-write.test.ts +++ b/packages/core/test/tool-write.test.ts @@ -38,7 +38,15 @@ const permission = Layer.succeed( assert: (input) => Effect.sync(() => assertions.push(input)).pipe( Effect.andThen( - input.action === denyAction ? Effect.fail(new PermissionV2.BlockedError({ rules: [] })) : Effect.void, + input.action === denyAction + ? Effect.fail( + new PermissionV2.BlockedError({ + rules: [], + permission: input.action, + resources: input.resources, + }), + ) + : Effect.void, ), ), ask: () => Effect.die("unused"), diff --git a/packages/docs/AGENTS.md b/packages/docs/AGENTS.md new file mode 100644 index 0000000000..8d82c52655 --- /dev/null +++ b/packages/docs/AGENTS.md @@ -0,0 +1,22 @@ +# V2 documentation guide + +## Structure + +- This directory is a standalone Mintlify site deployed from `packages/docs` on the `dev` branch. +- Write documentation in MDX. Every page should have `title` and `description` frontmatter. +- `docs.json` owns site configuration and navigation. Add, move, or remove its page entries whenever the corresponding MDX pages change. +- Put static files in `assets/` and reference them with root-relative paths such as `/assets/example.svg`. +- The API endpoint reference is generated by Mintlify from `openapi.json`; do not duplicate endpoint documentation as hand-written MDX. +- Keep documentation aligned with the V2 packages. Do not use `packages/opencode` as the source of truth unless the task explicitly concerns V1. + +## Local development + +- At the start of documentation work, launch `bun dev` from `packages/docs` using the shell tool with `background: true`. Never run the dev server in a foreground shell call and do not poll the process; wait for the background completion notification. +- Preview the site at `http://localhost:3333`. Mintlify does not expose a host option and binds the preview to all network interfaces. The server reloads changes to MDX and `docs.json` automatically. +- Use the running preview to verify navigation, links, Mintlify components, code blocks, and desktop and mobile layout. + +## Validation + +- Run `bun validate` from `packages/docs` after making documentation or configuration changes. +- Run `bun broken-links` from `packages/docs` when pages, navigation, headings, or links change. +- Treat validation errors and broken internal links as blockers. Also verify external links relevant to the change when practical. diff --git a/packages/docs/README.md b/packages/docs/README.md index 792f64b3f8..17b06848e1 100644 --- a/packages/docs/README.md +++ b/packages/docs/README.md @@ -4,21 +4,19 @@ The V2 documentation is a Mintlify site deployed from `packages/docs` on the `de ## Local preview -The Mintlify CLI requires Node.js 20 through 24. - From this directory, run: ```bash -npx mint dev +bun dev ``` -The preview opens at `http://localhost:3000` and reloads when MDX or `docs.json` changes. +The preview opens at `http://localhost:3333` and reloads when MDX or `docs.json` changes. Validate changes before opening a pull request: ```bash -npx mint validate -npx mint broken-links +bun validate +bun broken-links ``` The hosted preview is available at [opencode.mintlify.site](https://opencode.mintlify.site). diff --git a/packages/docs/config.mdx b/packages/docs/config.mdx index 42028fd726..09294d094a 100644 --- a/packages/docs/config.mdx +++ b/packages/docs/config.mdx @@ -6,3 +6,439 @@ description: "Configure OpenCode." You shouldn't have to configure OpenCode manually. Ask OpenCode to update its configuration for you. + +## Format + +OpenCode supports both **JSON** and **JSONC** (JSON with Comments) configuration files. + +```jsonc title="opencode.jsonc" +{ + "$schema": "https://opencode.ai/config.json", + "model": "openai/gpt-5.2-custom", + "providers": { + "openai": { + "models": { + "gpt-5.2-custom": { + "modelID": "gpt-5.2", + "name": "GPT-5.2 Custom" + } + } + } + } +} +``` + +## Locations + +OpenCode loads global configuration from: + +```text +~/.config/opencode/opencode.json(c) +``` + +Project-specific configuration can use either form: + +```text +/home/user/projects/my-app/opencode.json(c) +/home/user/projects/my-app/.opencode/opencode.json(c) +``` + +When OpenCode starts, it searches for configuration files from the current +directory upward to the project root. The files are merged, and configuration +closer to the current directory takes precedence. + +For example, consider a monorepo with OpenCode started from +`/home/user/projects/acme/packages/web`: + +```text +~/.config/opencode/opencode.json + +/home/user/projects/acme/ +├── opencode.json +└── packages/ + └── web/ + ├── opencode.json + └── src/ +``` + +OpenCode applies these files from lowest to highest precedence: + +1. `~/.config/opencode/opencode.json` +2. `/home/user/projects/acme/opencode.json` +3. `/home/user/projects/acme/packages/web/opencode.json` + +Settings in the package config override matching settings from the repository +config, which override matching settings from the global config. Settings that +do not conflict are preserved from every file. + +## Schema + +The complete OpenCode configuration schema is available at +[opencode.ai/config.json](https://opencode.ai/config.json). + +Add the `$schema` field to your configuration file to enable validation and +autocomplete in editors that support JSON Schema: + +```json title="opencode.json" +{ + "$schema": "https://opencode.ai/config.json" +} +``` + +Use the schema as the source of truth for available fields, accepted values, +and nested configuration shapes. + +### Shell + +Set the shell used by the terminal and shell tools. + +```jsonc +{ + "shell": "/bin/zsh" +} +``` + +### Model + +Set the default model in `provider/model` format. Add `#variant` to select a +specific model variant. + +```jsonc +{ + "model": "anthropic/claude-sonnet-4-5#high" +} +``` + +See the [models guide](https://opencode.ai/docs/models/) for model selection +and local models. + +### Default agent + +Choose the primary agent used when a session does not select one explicitly. + +```jsonc +{ + "default_agent": "build" +} +``` + +See the [agents guide](https://opencode.ai/docs/agents/) for built-in and custom +agents. + +### Autoupdate + +Control automatic updates. Set this to `false` to disable updates or `"notify"` +to receive update notifications. + +```jsonc +{ + "autoupdate": false +} +``` + +### Sharing + +Control whether sessions can be shared manually, shared automatically, or not +shared at all. + +```jsonc +{ + "share": "manual" +} +``` + +See the [sharing guide](https://opencode.ai/docs/share/) for more details. + +### Username + +Set the username displayed in conversations. + +```jsonc +{ + "username": "alice" +} +``` + +### Permissions + +Define ordered rules that allow, deny, or ask before an agent uses a tool on a +matching resource. + +```jsonc +{ + "permissions": [ + { + "action": "bash", + "resource": "git push *", + "effect": "ask" + } + ] +} +``` + +See the [permissions guide](https://opencode.ai/docs/permissions/) for rule +matching and available actions. + +### Agents + +Override built-in agents or define specialized agents with their own model, +instructions, mode, and permissions. + +```jsonc +{ + "agents": { + "reviewer": { + "description": "Review changes without editing files", + "mode": "subagent", + "system": "Focus on correctness, security, and missing tests.", + "permissions": [ + { "action": "edit", "resource": "*", "effect": "deny" } + ] + } + } +} +``` + +See the [agents guide](https://opencode.ai/docs/agents/) for all agent options +and file-based agents. + +### Snapshots + +Enable or disable the snapshots used by undo and revert behavior. + +```jsonc +{ + "snapshots": false +} +``` + +### Watcher + +Ignore files and directories that should not trigger filesystem updates. + +```jsonc +{ + "watcher": { + "ignore": ["dist/**", "coverage/**"] + } +} +``` + +### Formatter + +Enable built-in formatters, disable formatting entirely, or configure formatter +commands by name. + +```jsonc +{ + "formatter": { + "prettier": { + "command": ["bunx", "prettier", "--write", "$FILE"], + "extensions": [".js", ".ts", ".tsx"] + } + } +} +``` + +See the [formatters guide](https://opencode.ai/docs/formatters/) for built-in +formatters and custom commands. + +### LSP + +Enable built-in language servers, disable them, or configure servers by name. + +```jsonc +{ + "lsp": { + "typescript": { + "command": ["typescript-language-server", "--stdio"], + "extensions": [".ts", ".tsx"] + } + } +} +``` + +See the [LSP guide](https://opencode.ai/docs/lsp/) for language server setup. + +### Attachments + +Control how oversized image attachments are resized or rejected before they are +sent to a model. + +```jsonc +{ + "attachments": { + "image": { + "auto_resize": true, + "max_width": 2000, + "max_height": 2000, + "max_base64_bytes": 5242880 + } + } +} +``` + +### Tool output + +Set the maximum number of lines and bytes retained from a tool result. + +```jsonc +{ + "tool_output": { + "max_lines": 2000, + "max_bytes": 51200 + } +} +``` + +### MCP + +Configure local and remote Model Context Protocol servers. Global timeouts can +be overridden by an individual server. + +```jsonc +{ + "mcp": { + "servers": { + "playwright": { + "type": "local", + "command": ["bunx", "@playwright/mcp"] + } + } + } +} +``` + +See the [MCP guide](https://opencode.ai/docs/mcp-servers/) for remote servers, +OAuth, environment variables, and timeouts. + +### Compaction + +Control automatic context compaction and how much recent context it preserves. + +```jsonc +{ + "compaction": { + "auto": true, + "keep": { + "tokens": 8000 + }, + "buffer": 20000 + } +} +``` + +### Skills + +Add directories or URLs that OpenCode should search for agent skills. + +```jsonc +{ + "skills": ["./team-skills", "https://example.com/.well-known/skills/"] +} +``` + +See the [skills guide](https://opencode.ai/docs/skills/) for skill structure and +automatic discovery under `.opencode/skills/`. + +### Commands + +Define reusable slash commands as named prompt templates. + +```jsonc +{ + "commands": { + "review": { + "description": "Review the current changes", + "template": "Review the current diff for correctness and missing tests." + } + } +} +``` + +See the [commands guide](https://opencode.ai/docs/commands/) for arguments, +models, agents, and file-based commands. + +### Instructions + +Load additional instruction files, globs, or URLs into the agent's context. + +```jsonc +{ + "instructions": ["CONTRIBUTING.md", "docs/guidelines/*.md"] +} +``` + +See the [rules guide](https://opencode.ai/docs/rules/) for project instructions +and `AGENTS.md`. + +### References + +Make local directories or Git repositories available as named supporting +context. + +```jsonc +{ + "references": { + "docs": { + "path": "../product-docs", + "description": "Product behavior and terminology" + }, + "effect": { + "repository": "Effect-TS/effect", + "branch": "main" + } + } +} +``` + +See the [references guide](https://opencode.ai/docs/references/) for shorthand, +visibility, and path resolution. + +### Plugins + +Load plugins from packages or local files. Use the object form when a plugin +accepts options. + +```jsonc +{ + "plugins": [ + "opencode-example-plugin", + { + "package": "./plugins/local.ts", + "options": { + "enabled": true + } + } + ] +} +``` + +See the [plugins guide](/plugins) for plugin development and configuration. + +### Providers + +Configure providers and add or override their models, request settings, +headers, and model variants. + +```jsonc +{ + "providers": { + "openai": { + "models": { + "gpt-5.2-custom": { + "modelID": "gpt-5.2", + "name": "GPT-5.2 Custom", + "limit": { + "context": 200000, + "output": 32000 + } + } + } + } + } +} +``` + +See the [providers guide](https://opencode.ai/docs/providers/) for credentials, +custom endpoints, provider packages, and model configuration. diff --git a/packages/docs/package.json b/packages/docs/package.json new file mode 100644 index 0000000000..01f35998c5 --- /dev/null +++ b/packages/docs/package.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@opencode-ai/docs", + "private": true, + "scripts": { + "dev": "bun --bun mint dev --no-open --port 3333", + "validate": "bun --bun mint validate", + "broken-links": "bun --bun mint broken-links" + }, + "devDependencies": { + "mint": "4.2.666" + } +} diff --git a/packages/llm/src/protocols/openai-responses.ts b/packages/llm/src/protocols/openai-responses.ts index de18bf42a0..e0546f47ed 100644 --- a/packages/llm/src/protocols/openai-responses.ts +++ b/packages/llm/src/protocols/openai-responses.ts @@ -619,6 +619,11 @@ const onOutputTextDelta = (state: ParserState, event: OpenAIResponsesEvent): Ste ] } +const onOutputTextDone = (state: ParserState, event: OpenAIResponsesEvent): StepResult => { + const events: LLMEvent[] = [] + return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, event.item_id ?? "text-0") }, events] +} + const onReasoningDelta = (state: ParserState, event: OpenAIResponsesEvent): StepResult => { if (!event.delta) return [state, NO_EVENTS] const events: LLMEvent[] = [] @@ -810,6 +815,8 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function* const item = event.item if (!item) return [state, NO_EVENTS] satisfies StepResult + if (item.type === "message" && item.id) return onOutputTextDone(state, { ...event, item_id: item.id }) + if (item.type === "function_call") { if (!item.id || !item.call_id || !item.name) return [state, NO_EVENTS] satisfies StepResult const tools = state.tools[item.id] @@ -920,6 +927,7 @@ const onError = (state: ParserState, event: OpenAIResponsesEvent): StepResult => const step = (state: ParserState, event: OpenAIResponsesEvent) => { if (event.type === "response.output_text.delta") return Effect.succeed(onOutputTextDelta(state, event)) + if (event.type === "response.output_text.done") return Effect.succeed(onOutputTextDone(state, event)) if ( event.type === "response.reasoning_text.delta" || event.type === "response.reasoning_summary.delta" || diff --git a/packages/llm/src/schema/ids.ts b/packages/llm/src/schema/ids.ts index 279a3097e1..4775caf2ef 100644 --- a/packages/llm/src/schema/ids.ts +++ b/packages/llm/src/schema/ids.ts @@ -1,5 +1,5 @@ import { Schema } from "effect" -import { ProviderMetadata } from "@opencode-ai/schema/llm" +import { LLM, ProviderMetadata } from "@opencode-ai/schema/llm" export { ProviderMetadata } @@ -36,7 +36,7 @@ export type TextVerbosity = Schema.Schema.Type export const MessageRole = Schema.Literals(["system", "user", "assistant", "tool"]) export type MessageRole = Schema.Schema.Type -export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"]) +export const FinishReason = LLM.FinishReason export type FinishReason = Schema.Schema.Type export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown) diff --git a/packages/llm/test/provider/openai-responses.test.ts b/packages/llm/test/provider/openai-responses.test.ts index fbc5f2a864..3d048d4eee 100644 --- a/packages/llm/test/provider/openai-responses.test.ts +++ b/packages/llm/test/provider/openai-responses.test.ts @@ -764,6 +764,35 @@ describe("OpenAI Responses route", () => { }), ) + // OpenAI's documented stream orders output text within one message item; no + // provider-valid same-kind overlap is evidenced, so done boundaries close it. + it.effect("closes sequential output messages before starting the next", () => + Effect.gen(function* () { + const response = yield* LLMClient.generate(request).pipe( + Effect.provide( + fixedResponse( + sseEvents( + { type: "response.output_text.delta", item_id: "msg_1", delta: "First" }, + { type: "response.output_text.done", item_id: "msg_1" }, + { type: "response.output_text.delta", item_id: "msg_2", delta: "Second" }, + { type: "response.output_item.done", item: { type: "message", id: "msg_2" } }, + { type: "response.completed", response: { id: "resp_1" } }, + ), + ), + ), + ) + + expect(response.events.filter((event) => event.type.startsWith("text-"))).toEqual([ + { type: "text-start", id: "msg_1" }, + { type: "text-delta", id: "msg_1", text: "First" }, + { type: "text-end", id: "msg_1" }, + { type: "text-start", id: "msg_2" }, + { type: "text-delta", id: "msg_2", text: "Second" }, + { type: "text-end", id: "msg_2" }, + ]) + }), + ) + it.effect("parses reasoning summary stream fixtures", () => Effect.gen(function* () { const body = sseEvents( diff --git a/packages/opencode/specs/simulation/simulated-network-llm.md b/packages/opencode/specs/simulation/simulated-network-llm.md index c7631cf4cb..f09685b26a 100644 --- a/packages/opencode/specs/simulation/simulated-network-llm.md +++ b/packages/opencode/specs/simulation/simulated-network-llm.md @@ -65,7 +65,7 @@ Exchange = { id, body, queue: Queue, deferred lifecycle } ### 4. Backend control WebSocket (simulation-gated) -Started when the simulation module loads (lazy import, `OPENCODE_SIMULATION` only): a loopback JSON-RPC 2.0 WebSocket on `127.0.0.1:40950+`, hosted by the backend process. Drivers connect to it directly — the standalone topology has exactly one backend per TUI, so there is no proxying through the frontend. This socket is also the headless-simulation interface: it works with no TUI at all. +Started when `OPENCODE_DRIVE` names a registry manifest: a loopback JSON-RPC 2.0 WebSocket at that manifest's exact backend endpoint, hosted by the backend process. Drivers connect to it directly — the standalone topology has exactly one backend per TUI, so there is no proxying through the frontend. This socket is also the headless-simulation interface: it works with no TUI at all. Server -> driver notification (after `llm.attach`; pending exchanges are replayed on attach so late-attaching drivers miss nothing): @@ -101,8 +101,8 @@ Failure injection (`llm.fail`: HTTP status instead of SSE) is specced but not ye A driver manages two loopback WebSocket connections: -- TUI control server (`127.0.0.1:40900+`) — UI state, actions, render, trace. -- Backend control server (`127.0.0.1:40950+`) — LLM exchanges, network log. +- TUI control server (manifest `endpoints.ui`) — UI state, actions, render, trace. +- Backend control server (manifest `endpoints.backend`) — LLM exchanges, network log. Both speak the same JSON-RPC shape. Headless drivers use only the backend socket plus the normal HTTP API. Multiple drivers are out of scope; last attach wins. @@ -117,7 +117,7 @@ The driver-facing model must be selectable in the TUI. Simulation seeds config ( ## End-to-end flow ``` -driver TUI sim server (40900+) backend + control WS (40950+) +driver TUI drive server backend + drive WS | | | |-- ui.action (submit) ----->| | | |-- (normal app HTTP) ---->| session runner starts diff --git a/packages/opencode/specs/simulation/simulation-phases.md b/packages/opencode/specs/simulation/simulation-phases.md index ebc4bd3743..c8a470a449 100644 --- a/packages/opencode/specs/simulation/simulation-phases.md +++ b/packages/opencode/specs/simulation/simulation-phases.md @@ -12,11 +12,11 @@ This phase proves the core shape without swapping every foundational layer yet. Implementation checklist: -- [x] Add `OPENCODE_SIMULATION=1` activation in V1/full-TUI startup. +- [x] Add `OPENCODE_DRIVE=` activation in V1/full-TUI startup. - [x] Add simulation trace service with in-memory append-only records. - [x] Add OpenTUI UI state extraction for screen, focus, elements, and generated actions. - [x] Add OpenTUI UI action execution for typing, keys, enter, arrows, focus, and click. -- [x] Add reusable JSON-RPC WebSocket server on `127.0.0.1:40900+`. +- [x] Add reusable JSON-RPC WebSocket server at the manifest's UI endpoint. - [x] Expose `ui.state`, `ui.action`, `ui.render`. - [x] Expose `trace.list`, `trace.clear`, `trace.export`. - [x] Wire visible V1/full-TUI renderer path through the same action protocol. @@ -24,8 +24,8 @@ Implementation checklist: Scope: -- Add `OPENCODE_SIMULATION=1` activation. -- Start a TUI-owned JSON-RPC WebSocket server on `127.0.0.1:40900+`. +- Add `OPENCODE_DRIVE=` activation. +- Start a TUI-owned JSON-RPC WebSocket server at the manifest's UI endpoint. - Expose `ui.state`, `ui.action`, `ui.render`. - Use the old simulation action model: type text, press keys, press enter, arrows, focus, click. - Support fake OpenTUI renderer and visible renderer through the same action protocol. @@ -34,7 +34,7 @@ Scope: Done when: -- `OPENCODE_SIMULATION=1 bun run dev` starts the normal app. +- `OPENCODE_DRIVE= bun run dev` starts the normal app and UI drive server. - A local driver can connect to the WebSocket. - The driver can inspect current screen/elements/actions. - The driver can execute real TUI inputs. @@ -54,19 +54,19 @@ Goal: make the app safe and controlled by swapping the lowest layers, not app lo Implementation checklist: - [x] Add `packages/simulation/src/backend` as the home for backend simulation layer replacements, exported from `backend/index.ts` as `simulationReplacements`; `@opencode-ai/simulation` is private/non-published and depends on logic/framework packages (`core`, `llm`, `effect`, OpenTUI), while `server` and `tui` consume it. -- [x] Wire simulation replacements through the server's `makeRoutes` via `Layer.unwrap` + dynamic `import("@opencode-ai/simulation/backend")` gated on `OPENCODE_SIMULATION`, so the simulation module is never loaded eagerly and `makeRoutes` stays synchronous. +- [x] Wire simulation replacements through the server's `makeRoutes` via `Layer.unwrap` + dynamic `import("@opencode-ai/simulation/backend")` gated on `OPENCODE_SIMULATE`, so the simulation module is never loaded eagerly and `makeRoutes` stays synchronous. - [x] Implement in-memory `FileSystem.FileSystem` (`simulation/filesystem.ts`) replacing the `NodeFileSystem` platform node. Backed by a flat path map; implements the operations the app uses (stat, access, chmod, realPath, read/write file, make/read directory, remove, rename, copy, copyFile, temp dirs, read-only open handles); unused operations die with a clear defect; `watch` fails as unsupported. -- [x] Root the fake filesystem at `OPENCODE_SIMULATION_ROOT` (falling back to `process.cwd()` at layer-build time). The anchor is a real, empty host directory the runner creates and cds into. +- [x] Root the fake filesystem at `process.cwd()` at layer-build time. The anchor is a real, empty host directory the runner creates and cds into. - [x] Deny host filesystem escapes loudly: content/mutation operations outside the root fail with `PermissionDenied` simulation errors. Probe operations (`stat`/`access`/`exists`) report `NotFound` outside the root so walk-up loops (project discovery, `findUp`, `globUp`) terminate naturally. - [x] Add `SimulationFSUtil` replacement (`simulation/fs-util.ts`): wraps the real `FSUtil` layer and reroutes `readDirectoryEntries`, `glob`, and `globUp` — which bypass the injected `FileSystem` via node `fs/promises` and the `glob` package — through the simulated filesystem. - [x] Fix `LayerNode.hoist` conflict detection to compare node implementations instead of object identity; replacement rewriting produces dependency-rewritten copies of the same node, which previously false-positived as "conflicting implementations". -- [x] Add snapshot seeding from `OPENCODE_SIMULATION_STATE`: `project/` contents of the snapshot directory are read from the host once at layer-build time and seeded into the in-memory tree joined onto the anchor root. -- [x] Verify end to end: `opencode serve` boots with `OPENCODE_SIMULATION=1` + `OPENCODE_SIMULATION_ROOT` + path/DB env seams (`OPENCODE_CONFIG_DIR`, `OPENCODE_TEST_HOME`, `OPENCODE_DB=:memory:`); `fs.list`/`fs.read` observe only seeded in-memory files; the anchor directory on the host remains empty after the run. -- [ ] Create the anchor directory + `chdir` + env seam setup automatically in CLI startup when simulation mode is enabled (currently set manually by the runner; a full run needs `OPENCODE_SIMULATION_ROOT/STATE`, `OPENCODE_CONFIG_DIR`, `OPENCODE_TEST_HOME`, `OPENCODE_DB=:memory:`, and `XDG_*_HOME` pointed into the anchor, plus Bun's `--preload=@opentui/solid/preload` when launched outside `packages/cli`). +- [x] Add snapshot seeding from `OPENCODE_SIMULATE_STATE`: `files/` contents of the snapshot directory are read from the host once at layer-build time and seeded into the in-memory tree joined onto the anchor root. +- [x] Verify end to end: `opencode serve` boots with `OPENCODE_SIMULATE=1` + `OPENCODE_SIMULATE_STATE` + path/DB env seams (`OPENCODE_CONFIG_DIR`, `OPENCODE_TEST_HOME`, `OPENCODE_DB=:memory:`); `fs.list`/`fs.read` observe only seeded in-memory files; the anchor directory on the host remains empty after the run. +- [ ] Create the anchor directory + `chdir` + env seam setup automatically in CLI startup when simulation mode is enabled (currently set manually by the runner; a full run needs `OPENCODE_SIMULATE_STATE`, `OPENCODE_CONFIG_DIR`, `OPENCODE_TEST_HOME`, `OPENCODE_DB=:memory:`, and `XDG_*_HOME` pointed into the anchor, plus Bun's `--preload=@opentui/solid/preload` when launched outside `packages/cli`). - [ ] Assert the anchor directory is still empty at the end of the run (KV/log/flock still write through real XDG paths; they are contained in the anchor by the env seams but not yet in-memory). - [x] Add simulated network registry (`packages/simulation/src/backend/network.ts`): replaces the `httpClient` platform node, resolves all outbound HTTP against an in-memory route table, denies unknown destinations loudly, and keeps a bounded request log (design: `simulated-network-llm.md`). - [x] Add driver-answered LLM as an OpenAI route in the simulated network (`openai.ts` + `llm-exchange.ts`): provider requests open exchanges; the driver streams chunks back which are encoded as real OpenAI Chat SSE (schema-checked against `OpenAIChatEvent`) and consumed by the real protocol pipeline. No enqueue store — the driver is the model. -- [x] Add backend-hosted simulation control WebSocket (`control.ts`): JSON-RPC on `127.0.0.1:40950+`, started when the simulation module loads. Drivers connect directly (standalone topology — no frontend proxy): `llm.attach` (replays pending exchanges), `llm.chunk`, `llm.finish`, `llm.pending`, `network.log`; `llm.request` notifications push opened exchanges. This is also the headless-simulation interface. Drivers manage two sockets: TUI control (40900+) for UI, backend control (40950+) for LLM/network. +- [x] Add backend-hosted drive control WebSocket (`control.ts`): JSON-RPC at the named manifest's backend endpoint, started when `OPENCODE_DRIVE` is set. Drivers connect directly (standalone topology — no frontend proxy): `llm.attach` (replays pending exchanges), `llm.chunk`, `llm.finish`, `llm.pending`, `network.log`; `llm.request` notifications push opened exchanges. This is also the headless-simulation interface. Drivers manage the manifest's UI endpoint for UI control and backend endpoint for LLM/network control. - [x] Answer `https://models.dev/api.json` with an empty catalog in the simulated network; providers come from seeded config (`opencode.json` in the snapshot defines an openai-compatible provider with a dummy `apiKey`, which passes the catalog availability gate and resolves onto the real openai-chat route). - [x] Fix `buildLocationServiceMap` to apply replacements when compiling hoisted global nodes; platform-node replacements (filesystem, httpClient) were silently ignored inside hoisted globals. - [x] Verify end to end headless (real route stack in-process + backend control WS: prompt -> `llm.request` -> driver chunks -> assistant message contains driver text; script: `packages/server/script/e2e-sim.ts`) and through the TUI (fake renderer, both sockets: type + submit via TUI WS, answer `llm.request` via backend WS, assistant reply rendered on screen; script: `packages/tui/script/sim-llm-driver.ts`). @@ -78,7 +78,7 @@ Scope: - Wire simulation replacements through `AppNodeBuilder.build(...)` and `AppNodeBuilderV1.build(...)`. - Create a real, empty anchor directory (`mkdtemp`) and `process.chdir` into it before any command resolves its working directory; skip creation when the runner already spawned the app inside an anchor. - Root the in-memory filesystem at `process.cwd()` (the anchor). No cwd monkey-patching: cwd, `$PWD`, and `path.resolve()` stay truthful. -- Add snapshot loading from `OPENCODE_SIMULATION_STATE`: read the snapshot directory once at startup and seed the in-memory filesystem (snapshot `project/` paths joined onto the anchor root), config, env, and optional LLM/network state from it. +- Add snapshot loading from `OPENCODE_SIMULATE_STATE`: read the snapshot directory once at startup and seed the in-memory filesystem (snapshot `files/` paths joined onto the anchor root), config, env, and optional LLM/network state from it. - Route config/data/state/cache/temp paths into the simulated space using existing env seams (`OPENCODE_CONFIG_DIR`, `OPENCODE_TEST_HOME`, `OPENCODE_DB=:memory:`), set before `packages/core/src/global.ts` import-time path setup runs. - Deny host filesystem escapes loudly (paths outside the anchor root fail with typed simulation errors). - Assert the anchor directory on the host is still empty at the end of the run; anything written there means a code path bypassed the simulated filesystem. @@ -97,7 +97,7 @@ Done when: - Unknown network fails with a simulation error. - Host filesystem escape fails with a simulation error. - The anchor directory on the host is empty after a run. -- The app boots from a snapshot directory via `OPENCODE_SIMULATION_STATE` and observes the seeded project files, config, and env through normal app paths. +- The app boots from a snapshot directory via `OPENCODE_SIMULATE_STATE` and observes the seeded project files, config, and env through normal app paths. - A driver can seed a project filesystem. - A driver can enqueue an LLM script and submit a prompt through the TUI. - The real session/tool path consumes the scripted LLM behavior. diff --git a/packages/opencode/specs/simulation/simulation.md b/packages/opencode/specs/simulation/simulation.md index 5478796e78..7682fad0c4 100644 --- a/packages/opencode/specs/simulation/simulation.md +++ b/packages/opencode/specs/simulation/simulation.md @@ -11,7 +11,7 @@ The first milestone is an interactive exploration and model-based testing enviro This is not intended to be a custom simulated app or a separate `simulate` command. The normal app should run, with simulation enabled by one required flag: ```sh -OPENCODE_SIMULATION=1 bun run dev +OPENCODE_SIMULATE=1 bun run dev ``` ## Non-Goals @@ -21,7 +21,7 @@ OPENCODE_SIMULATION=1 bun run dev - Do not build shrinking in the first milestone. - Do not make generated randomized runs part of CI yet. - Do not build differential testing in the first milestone. -- Do not expose simulation controls when `OPENCODE_SIMULATION` is not set. +- Do not expose drive controls when `OPENCODE_DRIVE` is not set. ## Design Principles @@ -37,12 +37,12 @@ OPENCODE_SIMULATION=1 bun run dev ## Activation -`OPENCODE_SIMULATION=1` is the only required flag. +`OPENCODE_SIMULATE=1` swaps the backend's foundational layers for simulated implementations. `OPENCODE_DRIVE=` independently starts the frontend and backend control WebSockets using the exact endpoints from the named opencode-drive registry manifest. `OPENCODE_DRIVE=1` starts an unnamed instance at `ws://127.0.0.1:40900` for the UI and `ws://127.0.0.1:40950` for the backend. Initial state is provided through an optional snapshot directory: ```sh -OPENCODE_SIMULATION=1 OPENCODE_SIMULATION_STATE=/path/to/snapshot bun run dev +OPENCODE_SIMULATE=1 OPENCODE_DRIVE=demo OPENCODE_SIMULATE_STATE=/path/to/snapshot bun run dev ``` Optional flags can be added later, but should stay minimal. Reasonable optional parameters later include renderer mode, trace output path, seed, or port override. @@ -54,15 +54,15 @@ When enabled: - The app creates and changes into a real, empty anchor directory (see Filesystem). - The app reads the snapshot directory, if provided, and seeds all simulated state from it. - The app builds with simulation layer replacements. -- The TUI process starts a loopback WebSocket control server. +- The TUI and backend processes start loopback WebSocket control servers when `OPENCODE_DRIVE` is set. - Simulation-gated backend control routes become available only to the frontend/control path. - In-memory trace recording starts automatically. Path seams reuse existing environment variables where they already exist: `OPENCODE_CONFIG_DIR` for global config, `OPENCODE_TEST_HOME` for home, and `OPENCODE_DB=:memory:` for the database. Simulation mode should set these before foundational modules load rather than inventing parallel mechanisms. -## Control Server +## Control Servers -The external control surface lives in the TUI/frontend process, not the backend API server. +The UI control surface lives in the TUI/frontend process. A separate backend control surface handles simulated LLM and network operations. This is important because the frontend has direct access to the renderer, screen state, focus state, interactable elements, and user input APIs. The backend remains the normal backend, with only simulation-gated control routes used internally by the frontend when needed. @@ -70,9 +70,10 @@ Protocol: - JSON-RPC 2.0 over WebSocket. - Loopback only. -- Start at `127.0.0.1:40900`. -- If occupied, scan upward and report the actual URL. -- External drivers connect only to this frontend WebSocket. +- `OPENCODE_DRIVE` names a manifest in the opencode-drive registry, or is `1` for the unnamed default endpoints. +- The manifest supplies exact loopback `ui` and `backend` WebSocket endpoints. +- Startup fails rather than scanning when either manifest endpoint is unavailable. +- External drivers connect to both WebSockets when they need UI and backend controls. The app should not send JSON-RPC requests back to the driver in the first milestone. The driver sends requests; the app responds and emits notifications/events as useful. @@ -129,7 +130,7 @@ Both fake OpenTUI renderer and visible terminal renderer should share this proto The backend server should be exactly the normal backend server. -Simulation-only backend routes may exist, but only when `OPENCODE_SIMULATION=1`. They are private implementation details for the frontend simulation server to proxy commands like filesystem seeding, LLM scripting, network registration, and snapshots. +Simulation-only backend routes may exist, but only when `OPENCODE_SIMULATE=1`. They are private implementation details for commands like filesystem seeding, LLM scripting, network registration, and snapshots. External drivers should not use backend simulation routes directly. @@ -197,13 +198,13 @@ The anchor may be created by the app itself at activation, or by an external run ## Initial State Snapshot -`OPENCODE_SIMULATION_STATE` points at a directory containing one complete initial state. On startup the app slurps this directory once and constructs all simulated state from it. The snapshot is never written back to; it is a pure input. +`OPENCODE_SIMULATE_STATE` points at a directory containing one complete initial state. On startup the app slurps this directory once and constructs all simulated state from it. The snapshot is never written back to; it is a pure input. Proposed layout: ```text snapshot/ - project/... # workspace files, seeded into the in-memory FS under the anchor root + files/... # workspace files, seeded into the in-memory FS under the anchor root config/opencode.json # global config; the directory backs OPENCODE_CONFIG_DIR env.json # extra environment values to apply llm/... # scripted LLM behavior to pre-enqueue (optional) @@ -212,8 +213,8 @@ snapshot/ Rules: -- Paths inside `project/` are snapshot-relative. The loader joins them onto the anchor root, so absolute virtual paths look like real host paths under the anchor. -- Anything the config references (skills, instructions, reference paths) must exist inside `project/`. A snapshot that references missing files is invalid. +- Paths inside `files/` are snapshot-relative. The loader joins them onto the anchor root, so absolute virtual paths look like real host paths under the anchor. +- Anything the config references (skills, instructions, reference paths) must exist inside `files/`. A snapshot that references missing files is invalid. - The snapshot directory format is the contract between external state generators and the app. Generators (such as the opencode-probe project) produce snapshot directories plus a derived expected model; the app consumes only the snapshot. - Seeding through the control server (`backend.filesystem.seed` and friends) remains available for incremental changes during a run; the snapshot covers initial state. @@ -443,8 +444,8 @@ More advanced model/refinement, metamorphic, and differential properties are fut The first major demo should show this system as a real environment for exploring the app in controlled states: -1. Start opencode normally with `OPENCODE_SIMULATION=1`. -2. TUI starts and exposes the simulation WebSocket on `127.0.0.1:40900+`. +1. Start opencode normally with `OPENCODE_SIMULATE=1` and `OPENCODE_DRIVE=`. +2. TUI and backend start their drive WebSockets at the named manifest endpoints. 3. External runner connects. 4. Runner provides a snapshot directory (or seeds the in-memory project filesystem through the control server). 5. Runner generates and enables plugin-provided config state. @@ -459,10 +460,11 @@ The first major demo should show this system as a real environment for exploring ## Done-When Checklist -- `OPENCODE_SIMULATION=1` starts the normal app with simulation wiring. +- `OPENCODE_SIMULATE=1` starts the normal app with simulation wiring. +- `OPENCODE_DRIVE=` starts both drive WebSockets at the manifest endpoints. - Simulation code is isolated under a dedicated simulation/testing area. - App changes outside simulation are limited to activation hooks, builder replacements, TUI startup, and gated backend routes. -- TUI exposes JSON-RPC WebSocket on `127.0.0.1:40900+`. +- TUI and backend expose JSON-RPC WebSockets at the manifest endpoints. - Driver can call `ui.state`. - Driver can execute generated UI actions. - Fake and visible renderer paths use the same action protocol. diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 5a04ec2139..e95e8c27a1 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -24,15 +24,8 @@ const EXTERNAL_SKILL_PATTERN = "skills/**/SKILL.md" const OPENCODE_SKILL_PATTERN = "{skill,skills}/**/SKILL.md" const SKILL_PATTERN = "**/SKILL.md" -// Built-in skill that ships with opencode. The model's intuition for what an -// opencode.json should look like is often wrong, and opencode hard-fails on -// invalid config, so users hit cryptic startup errors. Loading this skill -// when the model is asked to touch opencode's own config files gives it the -// actual schemas instead of guesses. -const CUSTOMIZE_OPENCODE_SKILL_NAME = "customize-opencode" -const CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION = - "Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself." -const CUSTOMIZE_OPENCODE_SKILL_BODY = SkillPlugin.CustomizeOpencodeContent +const OPENCODE_SKILL_NAME = "opencode" +const OPENCODE_SKILL_BODY = SkillPlugin.OpencodeContent export const Info = Schema.Struct({ name: Schema.String, @@ -275,11 +268,11 @@ const layer = Layer.effect( const s: State = { skills: {}, dirs: new Set() } // Register the built-in skill BEFORE disk discovery so a user-disk // skill with the same name can override it. - s.skills[CUSTOMIZE_OPENCODE_SKILL_NAME] = { - name: CUSTOMIZE_OPENCODE_SKILL_NAME, - description: CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION, + s.skills[OPENCODE_SKILL_NAME] = { + name: OPENCODE_SKILL_NAME, + description: SkillPlugin.OpencodeDescription, location: "", - content: CUSTOMIZE_OPENCODE_SKILL_BODY, + content: OPENCODE_SKILL_BODY, } yield* loadSkills(s, yield* InstanceState.get(discovered), events) return s diff --git a/packages/opencode/test/cli/run/noninteractive.test.ts b/packages/opencode/test/cli/run/noninteractive.test.ts index e77d28b62b..377d6c582b 100644 --- a/packages/opencode/test/cli/run/noninteractive.test.ts +++ b/packages/opencode/test/cli/run/noninteractive.test.ts @@ -28,11 +28,20 @@ function prompted(inputID: string): V2Event { } function settled(outcome: "success" | "interrupted" = "success"): V2Event { + if (outcome === "interrupted") + return { + id: "evt_interrupted", + created: 0, + type: "session.execution.interrupted", + durable: { aggregateID: "ses_1", seq: 1, version: 1 }, + data: { sessionID: "ses_1", reason: "user" }, + } return { - id: "evt_settled", + id: "evt_succeeded", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome }, + type: "session.execution.succeeded", + durable: { aggregateID: "ses_1", seq: 1, version: 1 }, + data: { sessionID: "ses_1" }, } } @@ -58,8 +67,7 @@ async function run(input: { turn: (inputID: string) => V2Event[]; pendingForms?: spyOn(sdk.permission, "list").mockImplementation(() => ok([]) as never) spyOn(sdk.question, "list").mockImplementation(() => ok([]) as never) spyOn(sdk.form, "list").mockImplementation( - (request) => - ok(input.pendingForms?.filter((item) => item.sessionID === request.sessionID) ?? []) as never, + (request) => ok(input.pendingForms?.filter((item) => item.sessionID === request.sessionID) ?? []) as never, ) spyOn(sdk.form, "cancel").mockImplementation(() => ok(undefined) as never) spyOn(sdk.session, "prompt").mockImplementation((request) => { diff --git a/packages/opencode/test/cli/run/stream-v2.transport.test.ts b/packages/opencode/test/cli/run/stream-v2.transport.test.ts index 5400c6c05e..be248d3a3d 100644 --- a/packages/opencode/test/cli/run/stream-v2.transport.test.ts +++ b/packages/opencode/test/cli/run/stream-v2.transport.test.ts @@ -2,7 +2,12 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" import fs from "fs/promises" import path from "path" import { pathToFileURL } from "node:url" -import { OpenCode, type EventSubscribeOutput, type MessageListOutput, type OpenCodeClient } from "@opencode-ai/client/promise" +import { + OpenCode, + type EventSubscribeOutput, + type MessageListOutput, + type OpenCodeClient, +} from "@opencode-ai/client/promise" import { createSessionTransport } from "@opencode-ai/cli/mini/stream-v2.transport" import type { FooterApi, FooterEvent, StreamCommit } from "@opencode-ai/cli/mini/types" import { tmpdir } from "../../fixture/fixture" @@ -200,15 +205,16 @@ describe("V2 mini transport", () => { data: { sessionID: "ses_1", assistantMessageID: "msg_assistant", - textID: "txt_1", + ordinal: 0, delta: "answer", }, }) events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) await turn @@ -253,8 +259,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) }) return ok({ @@ -347,8 +354,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) }) return ok({ @@ -444,8 +452,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) }) return ok({ @@ -693,7 +702,7 @@ describe("V2 mini transport", () => { type: "assistant", agent: "build", model: { providerID: "test", id: "model" }, - content: [{ type: "text", id: "txt_1", text: "the answer" }], + content: [{ type: "text", text: "the answer" }], time: { created: 2, completed: 3 }, }, ], @@ -705,6 +714,17 @@ describe("V2 mini transport", () => { reset = resolve }) const replay = transport.replayOnResize({ localRows: () => [], reset: () => resetting }) + events.push({ + id: "evt_text_started", + created: 0, + type: "session.text.started", + durable: durable("ses_1"), + data: { + sessionID: "ses_1", + assistantMessageID: "msg_assistant", + ordinal: 0, + }, + }) events.push({ id: "evt_text", created: 0, @@ -712,7 +732,7 @@ describe("V2 mini transport", () => { data: { sessionID: "ses_1", assistantMessageID: "msg_assistant", - textID: "txt_1", + ordinal: 0, delta: "answer", }, }) @@ -725,7 +745,7 @@ describe("V2 mini transport", () => { await transport.close() }) - test("scopes repeated text and reasoning ids by assistant message", async () => { + test("scopes text and reasoning ordinals by assistant message", async () => { const events = feed() events.push(connected()) const client = sdk({ streams: [events] }) @@ -738,8 +758,8 @@ describe("V2 mini transport", () => { agent: "build", model: { providerID: "test", id: "model" }, content: [ - { type: "reasoning", id: "reasoning-0", text: "second thought" }, - { type: "text", id: "text-0", text: "second answer" }, + { type: "reasoning", text: "second thought" }, + { type: "text", text: "second answer" }, ], time: { created: 4, completed: 5 }, }, @@ -749,8 +769,8 @@ describe("V2 mini transport", () => { agent: "build", model: { providerID: "test", id: "model" }, content: [ - { type: "reasoning", id: "reasoning-0", text: "first thought" }, - { type: "text", id: "text-0", text: "first answer" }, + { type: "reasoning", text: "first thought" }, + { type: "text", text: "first answer" }, ], time: { created: 2, completed: 3 }, }, @@ -798,7 +818,7 @@ describe("V2 mini transport", () => { data: { sessionID: "ses_1", assistantMessageID: "msg_assistant", - reasoningID: "reasoning_1", + ordinal: 0, text: "considering", }, }) @@ -808,6 +828,52 @@ describe("V2 mini transport", () => { await transport.close() }) + test("renders a live tool start when the call begins", async () => { + const events = feed() + events.push(connected()) + const client = sdk({ streams: [events] }) + const ui = footer() + const transport = await createSessionTransport({ + sdk: client, + sessionID: "ses_1", + thinking: false, + limits: () => ({}), + footer: ui.api, + }) + + events.push({ + id: "evt_tool_input", + created: 1, + type: "session.tool.input.started", + durable: durable("ses_1"), + data: { + sessionID: "ses_1", + assistantMessageID: "msg_assistant", + callID: "call_read", + name: "read", + }, + }) + events.push({ + id: "evt_tool_called", + created: 2, + type: "session.tool.called", + durable: durable("ses_1", 1), + data: { + sessionID: "ses_1", + assistantMessageID: "msg_assistant", + callID: "call_read", + input: { path: "README.md" }, + executed: false, + }, + }) + await Bun.sleep(0) + + expect(ui.commits).toContainEqual( + expect.objectContaining({ kind: "tool", phase: "start", partID: "prt_call_read", tool: "read" }), + ) + await transport.close() + }) + test("resolves an interrupted turn even when promotion never arrived", async () => { const events = feed() events.push(connected()) @@ -849,8 +915,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.interrupted", + durable: durable("ses_1"), + data: { sessionID: "ses_1", reason: "user" }, }) await turn @@ -913,8 +980,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) await turn @@ -975,8 +1043,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.interrupted", + durable: durable("ses_1"), + data: { sessionID: "ses_1", reason: "user" }, }) await turn @@ -1413,8 +1482,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) }) return ok({ @@ -1488,8 +1558,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) }) return ok(undefined) as never @@ -1569,8 +1640,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_unrelated_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) await Bun.sleep(0) await Bun.sleep(0) @@ -1590,8 +1662,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_skill_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_1", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_1"), + data: { sessionID: "ses_1" }, }) await turn @@ -1701,18 +1774,19 @@ describe("V2 mini transport", () => { ], }, }) - spyOn(client.session, "get").mockImplementation(() => - ok({ - id: "ses_child", - parentID: "ses_1", - projectID: "proj_1", - agent: "explore", - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - time: { created: 1, updated: 1 }, - title: "Find files", - location: { directory: "/tmp" }, - }) as never, + spyOn(client.session, "get").mockImplementation( + () => + ok({ + id: "ses_child", + parentID: "ses_1", + projectID: "proj_1", + agent: "explore", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + time: { created: 1, updated: 1 }, + title: "Find files", + location: { directory: "/tmp" }, + }) as never, ) const ui = footer() const transport = await createSessionTransport({ @@ -1750,7 +1824,7 @@ describe("V2 mini transport", () => { data: { sessionID: "ses_child", assistantMessageID: "msg_child_a", - textID: "txt_child", + ordinal: 0, delta: "child answer", }, }) @@ -1760,8 +1834,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_child_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_child", outcome: "success" }, + type: "session.execution.succeeded", + durable: durable("ses_child"), + data: { sessionID: "ses_child" }, }) while (!states().some((state) => state.tabs.some((tab) => tab.status === "completed"))) await Bun.sleep(0) await transport.close() @@ -1801,7 +1876,9 @@ describe("V2 mini transport", () => { }) await Bun.sleep(0) expect( - states().at(-1)?.details.ses_child?.commits.some((item) => item.messageID === "msg_child_prompt"), + states() + .at(-1) + ?.details.ses_child?.commits.some((item) => item.messageID === "msg_child_prompt"), ).toBe(false) events.push({ @@ -1955,19 +2032,36 @@ describe("V2 mini transport", () => { data: { sessionID: "ses_child", assistantMessageID: "msg_overflow_assistant", - textID: `txt_overflow_${index}`, + ordinal: index, delta: `live ${index}`, }, }) - while (!states().at(-1)?.details.ses_child?.commits.some((item) => item.text === "live 64")) await Bun.sleep(0) + while ( + !states() + .at(-1) + ?.details.ses_child?.commits.some((item) => item.text === "live 64") + ) + await Bun.sleep(0) releaseStale() while (childRequests < 2) await Bun.sleep(0) - expect(states().at(-1)?.details.ses_child?.commits.some((item) => item.text === "live 64")).toBe(true) + expect( + states() + .at(-1) + ?.details.ses_child?.commits.some((item) => item.text === "live 64"), + ).toBe(true) releaseRetry() - while (!states().at(-1)?.details.ses_child?.commits.some((item) => item.text === "baseline history")) + while ( + !states() + .at(-1) + ?.details.ses_child?.commits.some((item) => item.text === "baseline history") + ) await Bun.sleep(0) - expect(states().at(-1)?.details.ses_child?.commits.some((item) => item.text === "live 64")).toBe(true) + expect( + states() + .at(-1) + ?.details.ses_child?.commits.some((item) => item.text === "live 64"), + ).toBe(true) expect(childRequests).toBe(2) await transport.close() }) @@ -2032,7 +2126,7 @@ describe("V2 mini transport", () => { durable: durable("ses_child", seq), data: { sessionID: "ses_child", assistantMessageID: "msg_tool_projected", callID, name }, }) - const called = (callID: string, tool: string, input: Record, seq: number) => + const called = (callID: string, input: Record, seq: number) => events.push({ id: `evt_called_${callID}`, created: seq, @@ -2042,14 +2136,13 @@ describe("V2 mini transport", () => { sessionID: "ses_child", assistantMessageID: "msg_tool_projected", callID, - tool, input, - provider: { executed: true }, + executed: true, }, }) inputStarted("call_terminal", "grep", 0) - called("call_terminal", "grep", { pattern: "needle" }, 1) + called("call_terminal", { pattern: "needle" }, 1) await Bun.sleep(0) transport.selectSubagent("ses_child") while (!childHydrating) await Bun.sleep(0) @@ -2064,11 +2157,11 @@ describe("V2 mini transport", () => { callID: "call_terminal", structured: {}, content: [{ type: "text", text: "found" }], - provider: { executed: true }, + executed: true, }, }) inputStarted("call_overlap", "bash", 3) - called("call_overlap", "bash", { command: "stale" }, 4) + called("call_overlap", { command: "stale" }, 4) await Bun.sleep(0) const beforeHydration = states().length releaseHydration() @@ -2137,8 +2230,9 @@ describe("V2 mini transport", () => { events.push({ id: "evt_child_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_child", outcome: "interrupted" }, + type: "session.execution.interrupted", + durable: durable("ses_child"), + data: { sessionID: "ses_child", reason: "user" }, }) await Bun.sleep(0) resolveGet?.() @@ -2192,6 +2286,18 @@ describe("V2 mini transport", () => { }, }) // Parent's background subagent tool.success adopts the child mid-discovery. + events.push({ + id: "evt_parent_input", + created: 0, + type: "session.tool.input.started", + durable: durable("ses_1"), + data: { + sessionID: "ses_1", + assistantMessageID: "msg_parent_a", + callID: "call_sub", + name: "subagent", + }, + }) events.push({ id: "evt_parent_call", created: 0, @@ -2201,9 +2307,8 @@ describe("V2 mini transport", () => { sessionID: "ses_1", assistantMessageID: "msg_parent_a", callID: "call_sub", - tool: "subagent", input: { agent: "explore", description: "Find things", prompt: "go", background: true }, - provider: { executed: true }, + executed: true, }, }) events.push({ @@ -2217,15 +2322,16 @@ describe("V2 mini transport", () => { callID: "call_sub", structured: { sessionID: "ses_child", status: "running", output: "" }, content: [], - provider: { executed: true }, + executed: true, }, }) // The settled event arrives after adoption, so it applies directly. events.push({ id: "evt_child_settled", created: 0, - type: "session.execution.settled", - data: { sessionID: "ses_child", outcome: "interrupted" }, + type: "session.execution.interrupted", + durable: durable("ses_child"), + data: { sessionID: "ses_child", reason: "shutdown" }, }) while (!states().some((state) => state.tabs.some((tab) => tab.status === "cancelled"))) await Bun.sleep(0) diff --git a/packages/opencode/test/v2/session-message-updater.test.ts b/packages/opencode/test/v2/session-message-updater.test.ts index 1fc3dda0df..8f8286d562 100644 --- a/packages/opencode/test/v2/session-message-updater.test.ts +++ b/packages/opencode/test/v2/session-message-updater.test.ts @@ -101,7 +101,7 @@ test.skip("text ended populates assistant text content", () => { data: { sessionID, assistantMessageID, - textID: "text-1", + ordinal: 0, }, } satisfies SessionEvent.Event), ) @@ -115,7 +115,7 @@ test.skip("text ended populates assistant text content", () => { data: { sessionID, assistantMessageID, - textID: "text-1", + ordinal: 0, text: "hello assistant", }, } satisfies SessionEvent.Event), @@ -123,7 +123,7 @@ test.skip("text ended populates assistant text content", () => { expect(state.messages[0]?.type).toBe("assistant") if (state.messages[0]?.type !== "assistant") return - expect(state.messages[0].content).toEqual([{ type: "text", id: "text-1", text: "hello assistant" }]) + expect(state.messages[0].content).toEqual([{ type: "text", text: "hello assistant" }]) }) test.skip("tool completion stores completed timestamp", () => { @@ -176,9 +176,9 @@ test.skip("tool completion stores completed timestamp", () => { sessionID, assistantMessageID, callID, - tool: "bash", input: { command: "pwd" }, - provider: { executed: true, metadata: { fake: { source: "provider" } } }, + executed: true, + state: { source: "provider" }, }, } satisfies SessionEvent.Event), ) @@ -195,7 +195,8 @@ test.skip("tool completion stores completed timestamp", () => { callID, structured: {}, content: [{ type: "text", text: "/tmp" }], - provider: { executed: true, metadata: { fake: { status: "done" } } }, + executed: true, + resultState: { status: "done" }, }, } satisfies SessionEvent.Event), ) @@ -205,7 +206,11 @@ test.skip("tool completion stores completed timestamp", () => { expect(state.messages[0].content[0]?.type).toBe("tool") if (state.messages[0].content[0]?.type !== "tool") return expect(state.messages[0].content[0].time.completed).toEqual(DateTime.makeUnsafe(4)) - expect(state.messages[0].content[0].provider).toEqual({ executed: true, metadata: { fake: { status: "done" } } }) + expect(state.messages[0].content[0]).toMatchObject({ + executed: true, + providerState: { source: "provider" }, + providerResultState: { status: "done" }, + }) }) test("compaction events reduce to compaction message only when completed", () => { diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index 8819d7d9e4..090df62eb6 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -189,6 +189,21 @@ export const makeSessionGroup = (sessionLo }), ), ) + .add( + HttpApiEndpoint.delete("session.remove", "/api/session/:sessionID", { + params: { sessionID: Session.ID }, + success: HttpApiSchema.NoContent, + error: SessionNotFoundError, + }) + .middleware(sessionLocationMiddleware) + .annotateMerge( + OpenApi.annotations({ + identifier: "v2.session.remove", + summary: "Delete session", + description: "Delete a session and its child sessions.", + }), + ), + ) .add( HttpApiEndpoint.post("session.fork", "/api/session/:sessionID/fork", { params: { sessionID: Session.ID }, @@ -290,13 +305,7 @@ export const makeSessionGroup = (sessionLo resume: Schema.Boolean.pipe(Schema.optional), }), success: Schema.Struct({ data: SessionInput.Admitted }), - error: [ - ConflictError, - InvalidRequestError, - SessionNotFoundError, - CommandNotFoundError, - CommandEvaluationError, - ], + error: [ConflictError, InvalidRequestError, SessionNotFoundError, CommandNotFoundError, CommandEvaluationError], }) .middleware(sessionLocationMiddleware) .annotateMerge( @@ -371,15 +380,16 @@ export const makeSessionGroup = (sessionLo .add( HttpApiEndpoint.post("session.compact", "/api/session/:sessionID/compact", { params: { sessionID: Session.ID }, - success: HttpApiSchema.NoContent, - error: [SessionNotFoundError, SessionBusyError, ServiceUnavailableError, UnknownError], + payload: Schema.Struct({ id: SessionMessage.ID.pipe(Schema.optional) }), + success: Schema.Struct({ data: SessionInput.Compaction }), + error: [ConflictError, SessionNotFoundError], }) .middleware(sessionLocationMiddleware) .annotateMerge( OpenApi.annotations({ identifier: "v2.session.compact", summary: "Compact session", - description: "Compact a session conversation.", + description: "Queue a durable session compaction request.", }), ), ) diff --git a/packages/protocol/src/groups/shell.ts b/packages/protocol/src/groups/shell.ts index 31fe35541e..0b4cc081f6 100644 --- a/packages/protocol/src/groups/shell.ts +++ b/packages/protocol/src/groups/shell.ts @@ -1,10 +1,15 @@ import { Shell } from "@opencode-ai/schema/shell" import { Location } from "@opencode-ai/schema/location" +import { NonNegativeInt } from "@opencode-ai/schema/schema" import { Schema } from "effect" import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" import { ShellNotFoundError } from "../errors.js" import { LocationQuery, locationQueryOpenApi } from "./location.js" +const TimeoutInput = Schema.Struct({ + timeout: NonNegativeInt, +}) + export const ShellGroup = HttpApiGroup.make("server.shell") .add( HttpApiEndpoint.get("shell.list", "/api/shell", { @@ -52,6 +57,23 @@ export const ShellGroup = HttpApiGroup.make("server.shell") }), ), ) + .add( + HttpApiEndpoint.patch("shell.timeout", "/api/shell/:id/timeout", { + params: { id: Shell.ID }, + query: LocationQuery, + payload: TimeoutInput, + success: Location.response(Shell.Info), + error: ShellNotFoundError, + }) + .annotateMerge(locationQueryOpenApi) + .annotateMerge( + OpenApi.annotations({ + identifier: "v2.shell.timeout", + summary: "Update shell timeout", + description: "Replace a running shell command's timeout from now, or clear it with zero.", + }), + ), + ) .add( HttpApiEndpoint.get("shell.output", "/api/shell/:id/output", { params: { id: Shell.ID }, diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 831f97b066..3f2efee68d 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -20,6 +20,7 @@ export { Revert } from "./revert.js" export { Session } from "./session.js" export { Vcs } from "./vcs.js" export { SessionInput } from "./session-input.js" +export { SessionError } from "./session-error.js" export { SessionMessage } from "./session-message.js" export { Shell } from "./shell.js" export { Skill } from "./skill.js" diff --git a/packages/schema/src/llm.ts b/packages/schema/src/llm.ts index 5e19ecc6d3..2bc1d93bd8 100644 --- a/packages/schema/src/llm.ts +++ b/packages/schema/src/llm.ts @@ -8,6 +8,9 @@ export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schem }) export type ProviderMetadata = Schema.Schema.Type +export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"]) +export type FinishReason = typeof FinishReason.Type + export interface ToolTextContent extends Schema.Schema.Type {} export const ToolTextContent = Schema.Struct({ type: Schema.Literal("text"), diff --git a/packages/schema/src/session-error.ts b/packages/schema/src/session-error.ts new file mode 100644 index 0000000000..b11efdbdee --- /dev/null +++ b/packages/schema/src/session-error.ts @@ -0,0 +1,9 @@ +export * as SessionError from "./session-error.js" + +import { Schema } from "effect" + +export interface Error extends Schema.Schema.Type {} +export const Error = Schema.Struct({ + type: Schema.String, + message: Schema.String, +}).annotate({ identifier: "Session.StructuredError" }) diff --git a/packages/schema/src/session-event.ts b/packages/schema/src/session-event.ts index c79b0da746..8e96321ce2 100644 --- a/packages/schema/src/session-event.ts +++ b/packages/schema/src/session-event.ts @@ -3,16 +3,18 @@ export * as SessionEvent from "./session-event.js" import { Schema } from "effect" import { optional } from "./schema.js" import { Event } from "./event.js" -import { ProviderMetadata, ToolContent } from "./llm.js" +import { ToolContent } from "./llm.js" +import { FinishReason } from "./llm.js" import { Delivery } from "./session-delivery.js" import { Model } from "./model.js" -import { NonNegativeInt, RelativePath } from "./schema.js" +import { NonNegativeInt, PositiveInt, RelativePath } from "./schema.js" import { FileAttachment, Prompt } from "./prompt.js" import { SessionID } from "./session-id.js" import { Location } from "./location.js" import { SessionMessage } from "./session-message.js" import { Revert } from "./revert.js" import { Shell as ShellSchema } from "./shell.js" +import { SessionError } from "./session-error.js" export { FileAttachment } @@ -41,16 +43,6 @@ const options = { version: 1, }, } as const -const stepSettlementOptions = { - durable: { - aggregate: "sessionID", - version: 1, - }, -} as const - -export const UnknownError = SessionMessage.UnknownError -export type UnknownError = SessionMessage.UnknownError - export const AgentSelected = Event.durable({ type: "session.agent.selected", ...options, @@ -92,6 +84,16 @@ export const Renamed = Event.durable({ }) export type Renamed = typeof Renamed.Type +export const Deleted = Event.durable({ + type: "session.deleted", + durable: { + aggregate: "sessionID", + version: 2, + }, + schema: Base, +}) +export type Deleted = typeof Deleted.Type + export const Forked = Event.durable({ type: "session.forked", ...options, @@ -120,15 +122,27 @@ export const PromptAdmitted = Event.durable({ }) export type PromptAdmitted = typeof PromptAdmitted.Type -export const ExecutionSettled = Event.ephemeral({ - type: "session.execution.settled", - schema: { - ...Base, - outcome: Schema.Literals(["success", "failure", "interrupted"]), - error: UnknownError.pipe(optional), - }, -}) -export type ExecutionSettled = typeof ExecutionSettled.Type +export namespace Execution { + export const Started = Event.durable({ type: "session.execution.started", ...options, schema: Base }) + export type Started = typeof Started.Type + + export const Succeeded = Event.durable({ type: "session.execution.succeeded", ...options, schema: Base }) + export type Succeeded = typeof Succeeded.Type + + export const Failed = Event.durable({ + type: "session.execution.failed", + ...options, + schema: { ...Base, error: SessionError.Error }, + }) + export type Failed = typeof Failed.Type + + export const Interrupted = Event.durable({ + type: "session.execution.interrupted", + ...options, + schema: { ...Base, reason: Schema.Literals(["user", "shutdown", "superseded"]) }, + }) + export type Interrupted = typeof Interrupted.Type +} export const InstructionsUpdated = Event.durable({ type: "session.instructions.updated", @@ -204,11 +218,11 @@ export namespace Step { export const Ended = Event.durable({ type: "session.step.ended", - ...stepSettlementOptions, + ...options, schema: { ...Base, assistantMessageID: SessionMessage.ID, - finish: Schema.String, + finish: FinishReason, cost: Schema.Finite, tokens: Schema.Struct({ input: Schema.Finite, @@ -227,11 +241,11 @@ export namespace Step { export const Failed = Event.durable({ type: "session.step.failed", - ...stepSettlementOptions, + ...options, schema: { ...Base, assistantMessageID: SessionMessage.ID, - error: UnknownError, + error: SessionError.Error, }, }) export type Failed = typeof Failed.Type @@ -244,7 +258,7 @@ export namespace Text { schema: { ...Base, assistantMessageID: SessionMessage.ID, - textID: Schema.String, + ordinal: NonNegativeInt, }, }) export type Started = typeof Started.Type @@ -255,7 +269,7 @@ export namespace Text { schema: { ...Base, assistantMessageID: SessionMessage.ID, - textID: Schema.String, + ordinal: NonNegativeInt, delta: Schema.String, }, }) @@ -267,7 +281,7 @@ export namespace Text { schema: { ...Base, assistantMessageID: SessionMessage.ID, - textID: Schema.String, + ordinal: NonNegativeInt, text: Schema.String, }, }) @@ -281,8 +295,8 @@ export namespace Reasoning { schema: { ...Base, assistantMessageID: SessionMessage.ID, - reasoningID: Schema.String, - providerMetadata: ProviderMetadata.pipe(optional), + ordinal: NonNegativeInt, + state: SessionMessage.ProviderState.pipe(optional), }, }) export type Started = typeof Started.Type @@ -293,7 +307,7 @@ export namespace Reasoning { schema: { ...Base, assistantMessageID: SessionMessage.ID, - reasoningID: Schema.String, + ordinal: NonNegativeInt, delta: Schema.String, }, }) @@ -305,9 +319,9 @@ export namespace Reasoning { schema: { ...Base, assistantMessageID: SessionMessage.ID, - reasoningID: Schema.String, + ordinal: NonNegativeInt, text: Schema.String, - providerMetadata: ProviderMetadata.pipe(optional), + state: SessionMessage.ProviderState.pipe(optional), }, }) export type Ended = typeof Ended.Type @@ -357,12 +371,9 @@ export namespace Tool { ...options, schema: { ...ToolBase, - tool: Schema.String, input: Schema.Record(Schema.String, Schema.Unknown), - provider: Schema.Struct({ - executed: Schema.Boolean, - metadata: ProviderMetadata.pipe(optional), - }), + executed: Schema.Boolean, + state: SessionMessage.ProviderState.pipe(optional), }, }) export type Called = typeof Called.Type @@ -391,10 +402,8 @@ export namespace Tool { content: Schema.Array(ToolContent), outputPaths: Schema.Array(Schema.String).pipe(optional), result: Schema.Unknown.pipe(optional), - provider: Schema.Struct({ - executed: Schema.Boolean, - metadata: ProviderMetadata.pipe(optional), - }), + executed: Schema.Boolean, + resultState: SessionMessage.ProviderState.pipe(optional), }, }) export type Success = typeof Success.Type @@ -404,41 +413,39 @@ export namespace Tool { ...options, schema: { ...ToolBase, - error: UnknownError, + error: SessionError.Error, result: Schema.Unknown.pipe(optional), - provider: Schema.Struct({ - executed: Schema.Boolean, - metadata: ProviderMetadata.pipe(optional), - }), + executed: Schema.Boolean, + resultState: SessionMessage.ProviderState.pipe(optional), }, }) export type Failed = typeof Failed.Type } -export const RetryError = Schema.Struct({ - message: Schema.String, - statusCode: Schema.Finite.pipe(optional), - isRetryable: Schema.Boolean, - responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(optional), - responseBody: Schema.String.pipe(optional), - metadata: Schema.Record(Schema.String, Schema.String).pipe(optional), -}).annotate({ - identifier: "session.retry.error", -}) -export interface RetryError extends Schema.Schema.Type {} - -export const Retried = Event.durable({ - type: "session.retried", +export const RetryScheduled = Event.durable({ + type: "session.retry.scheduled", ...options, schema: { ...Base, - attempt: Schema.Finite, - error: RetryError, + assistantMessageID: SessionMessage.ID, + attempt: PositiveInt, + at: NonNegativeInt, + error: SessionError.Error, }, }) -export type Retried = typeof Retried.Type +export type RetryScheduled = typeof RetryScheduled.Type export namespace Compaction { + export const Admitted = Event.durable({ + type: "session.compaction.admitted", + ...options, + schema: { + ...Base, + inputID: SessionMessage.ID, + }, + }) + export type Admitted = typeof Admitted.Type + export const Started = Event.durable({ type: "session.compaction.started", ...options, @@ -469,6 +476,13 @@ export namespace Compaction { }, }) export type Ended = typeof Ended.Type + + export const Failed = Event.durable({ + type: "session.compaction.failed", + ...options, + schema: Base, + }) + export type Failed = typeof Failed.Type } export namespace RevertEvent { @@ -481,7 +495,7 @@ export namespace RevertEvent { export const Committed = Event.durable({ type: "session.revert.committed", ...options, - schema: { ...Base, messageID: SessionMessage.ID }, + schema: { ...Base, to: SessionMessage.ID }, }) } @@ -490,10 +504,14 @@ export const Definitions = Event.inventory( ModelSelected, Moved, Renamed, + Deleted, Forked, PromptPromoted, PromptAdmitted, - ExecutionSettled, + Execution.Started, + Execution.Succeeded, + Execution.Failed, + Execution.Interrupted, InstructionsUpdated, Synthetic, Skill.Activated, @@ -515,10 +533,12 @@ export const Definitions = Event.inventory( Tool.Progress, Tool.Success, Tool.Failed, - Retried, + RetryScheduled, + Compaction.Admitted, Compaction.Started, Compaction.Delta, Compaction.Ended, + Compaction.Failed, RevertEvent.Staged, RevertEvent.Cleared, RevertEvent.Committed, diff --git a/packages/schema/src/session-input.ts b/packages/schema/src/session-input.ts index eefe68be2b..a59c00b40e 100644 --- a/packages/schema/src/session-input.ts +++ b/packages/schema/src/session-input.ts @@ -21,3 +21,22 @@ export const Admitted = Schema.Struct({ timeCreated: DateTimeUtcFromMillis, promotedSeq: NonNegativeInt.pipe(optional), }).annotate({ identifier: "SessionInput.Admitted" }) + +export interface PromptEntry extends Schema.Schema.Type {} +export const PromptEntry = Schema.Struct({ + type: Schema.Literal("prompt"), + ...Admitted.fields, +}).annotate({ identifier: "SessionInput.PromptEntry" }) + +export interface Compaction extends Schema.Schema.Type {} +export const Compaction = Schema.Struct({ + type: Schema.Literal("compaction"), + admittedSeq: NonNegativeInt, + id: SessionMessage.ID, + sessionID: SessionID, + timeCreated: DateTimeUtcFromMillis, + handledSeq: NonNegativeInt.pipe(optional), +}).annotate({ identifier: "SessionInput.Compaction" }) + +export const Entry = Schema.Union([PromptEntry, Compaction]).pipe(Schema.toTaggedUnion("type")) +export type Entry = typeof Entry.Type diff --git a/packages/schema/src/session-message.ts b/packages/schema/src/session-message.ts index c9f39193a5..62e1a1038d 100644 --- a/packages/schema/src/session-message.ts +++ b/packages/schema/src/session-message.ts @@ -2,14 +2,16 @@ export * as SessionMessage from "./session-message.js" import { Schema } from "effect" import { optional } from "./schema.js" -import { ProviderMetadata, ToolContent } from "./llm.js" +import { ToolContent } from "./llm.js" import { Model } from "./model.js" import { FileAttachment, Prompt } from "./prompt.js" -import { DateTimeUtcFromMillis, RelativePath, statics } from "./schema.js" +import { DateTimeUtcFromMillis, PositiveInt, RelativePath, statics } from "./schema.js" import { SessionID } from "./session-id.js" import { ascending } from "./identifier.js" import { Event } from "./event.js" import { Shell as ShellSchema } from "./shell.js" +import { FinishReason } from "./llm.js" +import { SessionError } from "./session-error.js" export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe( Schema.brand("Session.Message.ID"), @@ -20,18 +22,17 @@ export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe( ) export type ID = typeof ID.Type -export interface UnknownError extends Schema.Schema.Type {} -export const UnknownError = Schema.Struct({ - type: Schema.Literal("unknown"), - message: Schema.String, -}).annotate({ identifier: "Session.Error.Unknown" }) - const Base = { id: ID, metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional), time: Schema.Struct({ created: DateTimeUtcFromMillis }), } +export const ProviderState = Schema.Record(Schema.String, Schema.Unknown).annotate({ + identifier: "Session.Message.ProviderState", +}) +export type ProviderState = typeof ProviderState.Type + export interface AgentSelected extends Schema.Schema.Type {} export const AgentSelected = Schema.Struct({ ...Base, @@ -123,7 +124,7 @@ export const ToolStateError = Schema.Struct({ input: Schema.Record(Schema.String, Schema.Unknown), content: ToolContent.pipe(Schema.Array), structured: Schema.Record(Schema.String, Schema.Unknown), - error: UnknownError, + error: SessionError.Error, result: Schema.Unknown.pipe(optional), }).annotate({ identifier: "Session.Message.ToolState.Error" }) @@ -137,11 +138,9 @@ export const AssistantTool = Schema.Struct({ type: Schema.Literal("tool"), id: Schema.String, name: Schema.String, - provider: Schema.Struct({ - executed: Schema.Boolean, - metadata: ProviderMetadata.pipe(optional), - resultMetadata: ProviderMetadata.pipe(optional), - }).pipe(optional), + executed: Schema.Boolean.pipe(optional), + providerState: ProviderState.pipe(optional), + providerResultState: ProviderState.pipe(optional), state: ToolState, time: Schema.Struct({ created: DateTimeUtcFromMillis, @@ -154,16 +153,14 @@ export const AssistantTool = Schema.Struct({ export interface AssistantText extends Schema.Schema.Type {} export const AssistantText = Schema.Struct({ type: Schema.Literal("text"), - id: Schema.String, text: Schema.String, }).annotate({ identifier: "Session.Message.Assistant.Text" }) export interface AssistantReasoning extends Schema.Schema.Type {} export const AssistantReasoning = Schema.Struct({ type: Schema.Literal("reasoning"), - id: Schema.String, text: Schema.String, - providerMetadata: ProviderMetadata.pipe(optional), + state: ProviderState.pipe(optional), time: Schema.Struct({ created: DateTimeUtcFromMillis, completed: DateTimeUtcFromMillis.pipe(optional), @@ -175,6 +172,13 @@ export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, ) export type AssistantContent = AssistantText | AssistantReasoning | AssistantTool +export interface AssistantRetry extends Schema.Schema.Type {} +export const AssistantRetry = Schema.Struct({ + attempt: PositiveInt, + at: DateTimeUtcFromMillis, + error: SessionError.Error, +}).annotate({ identifier: "Session.Message.Assistant.Retry" }) + export interface Assistant extends Schema.Schema.Type {} export const Assistant = Schema.Struct({ ...Base, @@ -187,7 +191,7 @@ export const Assistant = Schema.Struct({ end: Schema.String.pipe(optional), files: Schema.Array(RelativePath).pipe(optional), }).pipe(optional), - finish: Schema.String.pipe(optional), + finish: FinishReason.pipe(optional), cost: Schema.Finite.pipe(optional), tokens: Schema.Struct({ input: Schema.Finite, @@ -195,7 +199,8 @@ export const Assistant = Schema.Struct({ reasoning: Schema.Finite, cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite }), }).pipe(optional), - error: UnknownError.pipe(optional), + error: SessionError.Error.pipe(optional), + retry: AssistantRetry.pipe(optional), time: Schema.Struct({ created: DateTimeUtcFromMillis, completed: DateTimeUtcFromMillis.pipe(optional), @@ -205,6 +210,7 @@ export const Assistant = Schema.Struct({ export interface Compaction extends Schema.Schema.Type {} export const Compaction = Schema.Struct({ type: Schema.Literal("compaction"), + status: Schema.Literals(["queued", "running", "completed", "failed"]), reason: Schema.Literals(["auto", "manual"]), summary: Schema.String, recent: Schema.String, diff --git a/packages/schema/src/session.ts b/packages/schema/src/session.ts index 2ae169c138..1d5a5ffef0 100644 --- a/packages/schema/src/session.ts +++ b/packages/schema/src/session.ts @@ -8,6 +8,7 @@ import { Project } from "./project.js" import { DateTimeUtcFromMillis, optional, RelativePath } from "./schema.js" import { SessionEvent } from "./session-event.js" import { SessionID } from "./session-id.js" +import { SessionMessage } from "./session-message.js" import { Revert } from "./revert.js" export const ID = SessionID @@ -19,6 +20,10 @@ export interface Info extends Schema.Schema.Type {} export const Info = Schema.Struct({ id: ID, parentID: ID.pipe(optional), + fork: Schema.Struct({ + sessionID: ID, + messageID: SessionMessage.ID.pipe(optional), + }).pipe(optional), projectID: Project.ID, agent: Agent.ID.pipe(optional), model: Model.Ref.pipe(optional), diff --git a/packages/schema/src/shell.ts b/packages/schema/src/shell.ts index fbe877c974..1826752d37 100644 --- a/packages/schema/src/shell.ts +++ b/packages/schema/src/shell.ts @@ -57,7 +57,7 @@ export const Event = { Created, Exited, Deleted, Definitions: inventory(Created, export const CreateInput = Schema.Struct({ command: Schema.String, cwd: optional(Schema.String), - timeout: optional(NonNegativeInt), + timeout: NonNegativeInt, metadata: optional(Metadata), }) export interface CreateInput extends Schema.Schema.Type {} diff --git a/packages/schema/test/contract-hygiene.test.ts b/packages/schema/test/contract-hygiene.test.ts index 58ba6ba856..236e15b91f 100644 --- a/packages/schema/test/contract-hygiene.test.ts +++ b/packages/schema/test/contract-hygiene.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { Schema } from "effect" +import { DateTime, Schema } from "effect" import { Agent } from "../src/agent.js" import { FileSystem } from "../src/filesystem.js" import { Model } from "../src/model.js" @@ -8,6 +8,7 @@ import { Provider } from "../src/provider.js" import { Pty } from "../src/pty.js" import { Question } from "../src/question.js" import { Session } from "../src/session.js" +import { SessionMessage } from "../src/session-message.js" import { SessionTodo } from "../src/session-todo.js" import { optional } from "../src/schema.js" @@ -81,4 +82,25 @@ describe("contract hygiene", () => { expect(source).not.toContain("Schema.Any") expect(source).not.toContain("Schema.mutable") }) + + test("assistant content keeps only domain identities", () => { + expect(SessionMessage.AssistantText.make({ type: "text", text: "hello" })).toEqual({ + type: "text", + text: "hello", + }) + expect( + SessionMessage.AssistantReasoning.make({ type: "reasoning", text: "thinking", state: { id: "opaque" } }), + ).toEqual({ type: "reasoning", text: "thinking", state: { id: "opaque" } }) + expect( + SessionMessage.AssistantTool.make({ + type: "tool", + id: "call_1", + name: "search", + executed: true, + providerState: { itemId: "item_1" }, + state: { status: "pending", input: "" }, + time: { created: DateTime.makeUnsafe(0) }, + }), + ).not.toHaveProperty("provider") + }) }) diff --git a/packages/schema/test/event-manifest.test.ts b/packages/schema/test/event-manifest.test.ts index 51400b2e39..1a5cbe7fa7 100644 --- a/packages/schema/test/event-manifest.test.ts +++ b/packages/schema/test/event-manifest.test.ts @@ -17,6 +17,8 @@ import { IdeEvent } from "../src/ide-event.js" import { McpEvent } from "../src/mcp-event.js" import { Plugin } from "../src/plugin.js" import { SessionEvent } from "../src/session-event.js" +import { SessionID } from "../src/session-id.js" +import { SessionMessage } from "../src/session-message.js" import { SessionTodo } from "../src/session-todo.js" import { SessionV1 } from "../src/session-v1.js" import { WorkspaceEvent } from "../src/workspace-event.js" @@ -44,11 +46,12 @@ describe("public event manifest", () => { SessionV1.Event.Error, ]) expect(Array.from(EventManifest.Latest.keys())).toEqual( - EventManifest.Definitions.map((definition) => definition.type), + Array.from(new Set(EventManifest.Definitions.map((definition) => definition.type))), ) expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated) expect(EventManifest.Latest.get("plugin.updated")).toBe(Plugin.Event.Updated) expect(EventManifest.Server.get("mcp.status.changed")).toBe(McpEvent.StatusChanged) + expect(EventManifest.Server.get("session.deleted")).toBe(SessionEvent.Deleted) expect(EventManifest.Server.has("mcp.tools.changed")).toBe(false) expect(Agent.Event.Updated.durable).toBeUndefined() expect(EventManifest.Durable.has("agent.updated")).toBe(false) @@ -93,6 +96,7 @@ describe("public event manifest", () => { "session.created.1", "session.updated.1", "session.deleted.1", + "session.deleted.2", "message.updated.1", "message.removed.1", "message.part.updated.1", @@ -104,7 +108,10 @@ describe("public event manifest", () => { "session.forked.1", "session.prompt.promoted.1", "session.prompt.admitted.1", - + "session.execution.started.1", + "session.execution.succeeded.1", + "session.execution.failed.1", + "session.execution.interrupted.1", "session.instructions.updated.1", "session.synthetic.1", "session.skill.activated.1", @@ -123,9 +130,11 @@ describe("public event manifest", () => { "session.tool.failed.1", "session.reasoning.started.1", "session.reasoning.ended.1", - "session.retried.1", + "session.retry.scheduled.1", + "session.compaction.admitted.1", "session.compaction.started.1", "session.compaction.ended.1", + "session.compaction.failed.1", "session.revert.staged.1", "session.revert.cleared.1", "session.revert.committed.1", @@ -136,4 +145,40 @@ describe("public event manifest", () => { ) expect(EventManifest.Definitions.every((definition) => definition.durability !== undefined)).toBe(true) }) + + test("keeps simplified session fragment and tool payloads on durable version 1", () => { + const sessionID = SessionID.make("ses_test") + const assistantMessageID = SessionMessage.ID.make("msg_test") + const text = SessionEvent.Text.Started.data.make({ sessionID, assistantMessageID, ordinal: 0 }) + const reasoning = SessionEvent.Reasoning.Ended.data.make({ + sessionID, + assistantMessageID, + ordinal: 0, + text: "thought", + state: { signature: "sig" }, + }) + const tool = SessionEvent.Tool.Called.data.make({ + sessionID, + assistantMessageID, + callID: "call_test", + input: {}, + executed: true, + state: { itemId: "item_test" }, + }) + + expect(text).not.toHaveProperty("textID") + expect(reasoning).not.toHaveProperty("reasoningID") + expect(reasoning).not.toHaveProperty("providerMetadata") + expect(tool).not.toHaveProperty("tool") + expect(tool).not.toHaveProperty("provider") + expect(SessionEvent.Text.Started.durable?.version).toBe(1) + expect(SessionEvent.Tool.Called.durable?.version).toBe(1) + }) + + test("keeps current session deletion minimal", () => { + const sessionID = SessionID.make("ses_test") + + expect(SessionEvent.Deleted.data.make({ sessionID })).toEqual({ sessionID }) + expect(SessionEvent.Deleted.durable?.version).toBe(2) + }) }) diff --git a/packages/schema/test/session-error.test.ts b/packages/schema/test/session-error.test.ts new file mode 100644 index 0000000000..de53e1fd5f --- /dev/null +++ b/packages/schema/test/session-error.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from "bun:test" +import { Schema } from "effect" +import { LLM, SessionError } from "../src/index.js" + +describe("SessionError", () => { + test("exports one identified open envelope", () => { + expect(SessionError.Error.ast.annotations?.identifier).toBe("Session.StructuredError") + expect(Object.keys(SessionError).filter((key) => key !== "SessionError")).toEqual(["Error"]) + }) + + test("round trips current and future error types through JSON", () => { + const values: SessionError.Error[] = [ + { type: "provider.rate-limit", message: "Slow down" }, + { type: "provider.auth", message: "Authentication failed" }, + { type: "provider.future-condition", message: "A future provider failure" }, + { type: "unknown", message: "Unexpected" }, + ] + const codec = Schema.fromJsonString(SessionError.Error) + + for (const value of values) { + const encoded = Schema.encodeSync(codec)(value) + expect(Schema.decodeUnknownSync(codec)(encoded)).toEqual(value) + } + }) + + test("accepts future fields while exposing only the stable envelope", () => { + expect( + Schema.decodeUnknownSync(SessionError.Error)({ + type: "provider.timeout", + message: "Timeout", + retryAfterMs: 2_500, + }), + ).toEqual({ type: "provider.timeout", message: "Timeout" }) + }) + + test("rejects missing envelope fields", () => { + expect(() => Schema.decodeUnknownSync(SessionError.Error)({ type: "provider.auth" })).toThrow() + expect(() => Schema.decodeUnknownSync(SessionError.Error)({ message: "Missing type" })).toThrow() + }) +}) + +test("FinishReason is the closed browser-safe provider set", () => { + const reasons = ["stop", "length", "tool-calls", "content-filter", "error", "unknown"] as const + expect(reasons.map((reason) => Schema.decodeUnknownSync(LLM.FinishReason)(reason))).toEqual([...reasons]) + expect(() => Schema.decodeUnknownSync(LLM.FinishReason)("other")).toThrow() +}) diff --git a/packages/sdk/js/script/build.ts b/packages/sdk/js/script/build.ts index fdc1d76bc6..4101e0751f 100755 --- a/packages/sdk/js/script/build.ts +++ b/packages/sdk/js/script/build.ts @@ -30,6 +30,8 @@ type OpenApiDocument = { const document = (await Bun.file("./openapi.json").json()) as OpenApiDocument const v2Document = (await Bun.file("./openapi-v2.json").json()) as OpenApiDocument +normalizeComponentNames(v2Document) +deduplicateEquivalentComponent(v2Document, "Shell", "Shell1") renameCollidingComponents(document, v2Document) document.paths = { ...document.paths, ...v2Document.paths } document.components = { @@ -60,7 +62,7 @@ if (schemas) { visit({ ...document, components: { ...document.components, schemas: undefined } }) for (const name of Object.keys(schemas)) { if ( - /^(SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionSettled|SessionInstructionsUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetried|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)1$/.test( + /^(SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionStarted|SessionExecutionSucceeded|SessionExecutionFailed|SessionExecutionInterrupted|SessionInstructionsUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetryScheduled|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)\d+$/.test( name, ) && !reachable.has(name) @@ -100,17 +102,28 @@ await createClient({ const generatedTypesPath = "./src/v2/gen/types.gen.ts" const generatedTypes = await Bun.file(generatedTypesPath).text() if ( - /export type (SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionSettled|SessionInstructionsUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetried|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)1 =/.test( + /export type (SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionStarted|SessionExecutionSucceeded|SessionExecutionFailed|SessionExecutionInterrupted|SessionInstructionsUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetryScheduled|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)\d+ =/.test( generatedTypes, ) ) { throw new Error("Session history generated duplicate Session event variants") } -const logTypesPatched = generatedTypes.replace( +const sessionErrorTypesPatched = deduplicateEquivalentGeneratedTypes( + generatedTypes, + "SessionStructuredError", + /^SessionStructuredError\d+$/, +) +const obsoleteSessionNext = [...sessionErrorTypesPatched.matchAll(/export type (SessionNext\w*) =/g)].map( + (match) => match[1], +) +if (obsoleteSessionNext.length > 0) { + throw new Error(`Obsolete SessionNext generated type noise reintroduced: ${obsoleteSessionNext.join(", ")}`) +} +const logTypesPatched = sessionErrorTypesPatched.replace( /(export type V2SessionLogData = \{[\s\S]*?query\?: \{\s*after\?: )string/, "$1number", ) -if (logTypesPatched === generatedTypes) { +if (logTypesPatched === sessionErrorTypesPatched) { throw new Error("Session log numeric query patch did not apply") } const sessionListTypesPatched = logTypesPatched.replace( @@ -128,12 +141,21 @@ if (sessionMessagesTypesPatched === sessionListTypesPatched) { throw new Error("Session messages numeric query patch did not apply") } const eventSubscribeTypesPatched = sessionMessagesTypesPatched.replace( - /(export type V2EventSubscribeResponses = \{\s*\/\*\*[\s\S]*?\*\/\s*200: )\{\s*id: string \| null;?\s*event: string;?\s*data: V2EventStreamV2;?\s*\};?/, + /(export type V2EventSubscribeResponses = \{\s*\/\*\*[\s\S]*?\*\/\s*200: )\{\s*id: string \| null;?\s*event: string;?\s*data: V2EventStream(?:V2)?;?\s*\};?/, "$1V2Event", ) if (eventSubscribeTypesPatched === sessionMessagesTypesPatched) { throw new Error("Event subscribe response patch did not apply") } +if (/SessionStructuredError\d/.test(eventSubscribeTypesPatched)) { + throw new Error("Session structured error generated a name-mangled duplicate") +} +if (/\bSessionNext\w*\b/.test(eventSubscribeTypesPatched)) { + throw new Error("Obsolete SessionNext generated type noise reintroduced") +} +if (/export type Shell\d+V2 =/.test(eventSubscribeTypesPatched)) { + throw new Error("Shell generated a name-mangled duplicate") +} await Bun.write(generatedTypesPath, eventSubscribeTypesPatched) const querySerializerPath = "./src/v2/gen/client/utils.gen.ts" @@ -206,6 +228,10 @@ function renameCollidingComponents(target: OpenApiDocument, source: OpenApiDocum const renames = new Map() for (const name of Object.keys(sourceSchemas)) { if (!Object.hasOwn(targetSchemas, name)) continue + if (JSON.stringify(normalizeSchema(sourceSchemas[name])) === JSON.stringify(normalizeSchema(targetSchemas[name]))) { + delete sourceSchemas[name] + continue + } let renamed = `${name}V2` let index = 2 while (Object.hasOwn(targetSchemas, renamed) || Object.hasOwn(sourceSchemas, renamed)) { @@ -225,6 +251,136 @@ function renameCollidingComponents(target: OpenApiDocument, source: OpenApiDocum source.paths = rewriteRefs(source.paths, renames) as Record | undefined } +function normalizeComponentNames(document: OpenApiDocument) { + const schemas = document.components?.schemas + if (!schemas) return + + const canonical = new Map(Object.entries(schemas)) + const renames = new Map() + for (const name of Object.keys(schemas)) { + const next = componentTypeName(name) + if (next === name) continue + const existing = canonical.get(next) + if (existing !== undefined) { + if (JSON.stringify(normalizeSchema(schemas[name])) !== JSON.stringify(normalizeSchema(existing))) continue + renames.set(name, next) + continue + } + renames.set(name, next) + canonical.set(next, schemas[name]) + } + if (renames.size === 0) return + + const renamed = new Set() + document.components = { + ...document.components, + schemas: Object.fromEntries( + [ + ...Object.entries(schemas).filter(([name]) => !renames.has(name)), + ...Object.entries(schemas).flatMap(([name, schema]) => { + const next = renames.get(name) + if (!next || Object.hasOwn(schemas, next) || renamed.has(next)) return [] + renamed.add(next) + return [[next, schema] as const] + }), + ].map(([name, schema]) => [name, rewriteRefs(schema, renames)]), + ), + } + document.paths = rewriteRefs(document.paths, renames) as Record | undefined +} + +function componentTypeName(name: string) { + if (!name.includes(".")) return name + return name + .split(".") + .filter((part) => !/^\d+$/.test(part)) + .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)) + .join("") +} + +function deduplicateEquivalentComponent(document: OpenApiDocument, canonical: string, duplicate: string) { + const schemas = document.components?.schemas + if (!schemas?.[canonical] || !schemas[duplicate]) return + if (JSON.stringify(normalizeSchema(schemas[canonical])) !== JSON.stringify(normalizeSchema(schemas[duplicate]))) { + throw new Error(`${duplicate} no longer has the same wire shape as ${canonical}`) + } + + const renames = new Map([[duplicate, canonical]]) + const rewritten = rewriteRefs(schemas, renames) as Record + delete rewritten[duplicate] + document.components = { ...document.components, schemas: rewritten } + document.paths = rewriteRefs(document.paths, renames) as Record | undefined +} + +function deduplicateEquivalentGeneratedTypes(source: string, canonical: string, duplicates: RegExp) { + const canonicalType = generatedType(source, canonical) + if (!canonicalType) throw new Error(`Generated canonical type missing: ${canonical}`) + const names = [...source.matchAll(/export type (\w+) =/g)] + .map((match) => match[1]) + .filter((name): name is string => name !== undefined && duplicates.test(name)) + + return names.reduce((patched, name) => { + const duplicate = generatedType(patched, name) + const currentCanonical = generatedType(patched, canonical) + if (!duplicate || !currentCanonical) throw new Error(`Generated type declaration missing while comparing ${name}`) + if (normalizeGeneratedType(currentCanonical.shape) !== normalizeGeneratedType(duplicate.shape)) { + throw new Error(`${name} no longer has the same generated type shape as ${canonical}`) + } + return (patched.slice(0, duplicate.start) + patched.slice(duplicate.end)).replaceAll(name, canonical) + }, source) +} + +function generatedType(source: string, name: string) { + const start = source.indexOf(`export type ${name} =`) + if (start === -1) return undefined + const next = source.indexOf("\n\nexport type ", start + 1) + const shapeEnd = next === -1 ? source.length : next + return { + start, + end: next === -1 ? source.length : next + 2, + shape: source.slice(source.indexOf("=", start) + 1, shapeEnd), + } +} + +function normalizeGeneratedType(shape: string) { + return shape.replaceAll(/\s/g, "") +} + +function normalizeSchema(value: unknown, key?: string): unknown { + if (Array.isArray(value)) { + const flattened = + key === "anyOf" + ? value.flatMap((item) => + typeof item === "object" && item !== null && Object.keys(item).length === 1 && "anyOf" in item + ? Array.isArray(item.anyOf) + ? item.anyOf + : [item] + : [item], + ) + : value + const expanded = + key === "anyOf" + ? flattened.flatMap((item) => { + if (typeof item !== "object" || item === null || !("type" in item) || !("enum" in item)) return [item] + if (Object.keys(item).some((property) => property !== "type" && property !== "enum")) return [item] + if (!Array.isArray(item.enum)) return [item] + return item.enum.map((member) => ({ type: item.type, enum: [member] })) + }) + : flattened + const normalized = expanded.map((item) => normalizeSchema(item)) + if (key !== "anyOf" && key !== "required" && key !== "enum") return normalized + return [...new Map(normalized.map((item) => [JSON.stringify(item), item])).values()].sort((a, b) => + JSON.stringify(a).localeCompare(JSON.stringify(b)), + ) + } + if (typeof value !== "object" || value === null) return value + return Object.fromEntries( + Object.entries(value) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([property, child]) => [property, normalizeSchema(child, property)]), + ) +} + function rewriteRefs(value: unknown, renames: Map): unknown { if (Array.isArray(value)) return value.map((item) => rewriteRefs(item, renames)) if (typeof value !== "object" || value === null) return value diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 67fd49d378..458f9c2d2e 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -76,8 +76,8 @@ import type { FindTextResponses, FormatterStatusErrors, FormatterStatusResponses, - FormCreatePayload2, - FormReply2, + FormCreatePayloadV2, + FormReply, GlobalConfigGetErrors, GlobalConfigGetResponses, GlobalConfigUpdateErrors, @@ -92,8 +92,8 @@ import type { GlobalUpgradeResponses, InstanceDisposeErrors, InstanceDisposeResponses, - InstructionEntryKey2, - LocationRef2, + InstructionEntryKeyV2, + LocationRefV2, LspStatusErrors, LspStatusResponses, McpAddErrors, @@ -114,7 +114,7 @@ import type { McpRemoteConfig, McpStatusErrors, McpStatusResponses, - ModelRef2, + ModelRef, MoveSessionDestination, OutputFormat, Part as Part2, @@ -131,8 +131,8 @@ import type { PermissionRespondErrors, PermissionRespondResponses, PermissionRuleset, - PermissionV2Reply2, - PermissionV2Source2, + PermissionV2Reply, + PermissionV2SourceV2, ProjectCommands, ProjectCurrentErrors, ProjectCurrentResponses, @@ -145,9 +145,9 @@ import type { ProjectListResponses, ProjectUpdateErrors, ProjectUpdateResponses, - PromptAgentAttachment2, - PromptInputFileAttachment2, - PromptInputV2, + PromptAgentAttachment, + PromptInput, + PromptInputFileAttachment, ProviderAuthErrors, ProviderAuthResponses, ProviderListErrors, @@ -179,7 +179,7 @@ import type { QuestionRejectResponses, QuestionReplyErrors, QuestionReplyResponses, - QuestionV2Reply2, + QuestionV2Reply, SessionAbortErrors, SessionAbortResponses, SessionChildrenErrors, @@ -416,6 +416,8 @@ import type { V2SessionQuestionRejectResponses, V2SessionQuestionReplyErrors, V2SessionQuestionReplyResponses, + V2SessionRemoveErrors, + V2SessionRemoveResponses, V2SessionRenameErrors, V2SessionRenameResponses, V2SessionRevertClearErrors, @@ -446,6 +448,8 @@ import type { V2ShellOutputResponses, V2ShellRemoveErrors, V2ShellRemoveResponses, + V2ShellTimeoutErrors, + V2ShellTimeoutResponses, V2SkillListErrors, V2SkillListResponses, V2VcsDiffErrors, @@ -460,7 +464,7 @@ import type { VcsDiffResponses, VcsGetErrors, VcsGetResponses, - VcsMode2, + VcsMode, VcsStatusErrors, VcsStatusResponses, WorktreeCreateErrors, @@ -5292,7 +5296,7 @@ export class Entry extends HeyApiClient { public remove( parameters: { sessionID: string - key: InstructionEntryKey2 + key: InstructionEntryKeyV2 }, options?: Options, ) { @@ -5326,7 +5330,7 @@ export class Entry extends HeyApiClient { public put( parameters: { sessionID: string - key: InstructionEntryKey2 + key: InstructionEntryKeyV2 value?: unknown }, options?: Options, @@ -5395,7 +5399,7 @@ export class Form extends HeyApiClient { public create( parameters: { sessionID: string - formCreatePayload: FormCreatePayload2 + formCreatePayloadV2: FormCreatePayloadV2 }, options?: Options, ) { @@ -5405,7 +5409,7 @@ export class Form extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { key: "formCreatePayload", map: "body" }, + { key: "formCreatePayloadV2", map: "body" }, ], }, ], @@ -5493,7 +5497,7 @@ export class Form extends HeyApiClient { parameters: { sessionID: string formID: string - formReply: FormReply2 + formReply: FormReply }, options?: Options, ) { @@ -5593,7 +5597,7 @@ export class Permission2 extends HeyApiClient { metadata?: { [key: string]: unknown } - source?: PermissionV2Source2 + source?: PermissionV2SourceV2 agent?: string | null }, options?: Options, @@ -5674,7 +5678,7 @@ export class Permission2 extends HeyApiClient { parameters: { sessionID: string requestID: string - reply?: PermissionV2Reply2 + reply?: PermissionV2Reply message?: string | null }, options?: Options, @@ -5742,7 +5746,7 @@ export class Question2 extends HeyApiClient { parameters: { sessionID: string requestID: string - questionV2Reply: QuestionV2Reply2 + questionV2Reply: QuestionV2Reply }, options?: Options, ) { @@ -5863,8 +5867,8 @@ export class Session3 extends HeyApiClient { parameters?: { id?: string | null agent?: string | null - model?: ModelRef2 | null - location?: LocationRef2 | null + model?: ModelRef | null + location?: LocationRefV2 | null }, options?: Options, ) { @@ -5905,6 +5909,25 @@ export class Session3 extends HeyApiClient { }) } + /** + * Delete session + * + * Delete a session and its child sessions. + */ + public remove( + parameters: { + sessionID: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).delete({ + url: "/api/session/{sessionID}", + ...options, + ...params, + }) + } + /** * Get session * @@ -6006,7 +6029,7 @@ export class Session3 extends HeyApiClient { public switchModel( parameters: { sessionID: string - model?: ModelRef2 + model?: ModelRef }, options?: Options, ) { @@ -6081,7 +6104,7 @@ export class Session3 extends HeyApiClient { parameters: { sessionID: string id?: string | null - prompt?: PromptInputV2 + prompt?: PromptInput delivery?: "steer" | "queue" | null resume?: boolean | null }, @@ -6125,9 +6148,9 @@ export class Session3 extends HeyApiClient { command?: string arguments?: string | null agent?: string | null - model?: ModelRef2 | null - files?: Array - agents?: Array + model?: ModelRef | null + files?: Array + agents?: Array delivery?: "steer" | "queue" | null resume?: boolean | null }, @@ -6284,19 +6307,35 @@ export class Session3 extends HeyApiClient { /** * Compact session * - * Compact a session conversation. + * Queue a durable session compaction request. */ public compact( parameters: { sessionID: string + id?: string | null }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "body", key: "id" }, + ], + }, + ], + ) return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/compact", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } @@ -6559,7 +6598,7 @@ export class Generate extends HeyApiClient { workspace?: string | null } | null prompt?: string - model?: ModelRef2 | null + model?: ModelRef | null }, options?: Options, ) { @@ -7766,6 +7805,46 @@ export class Shell extends HeyApiClient { }) } + /** + * Update shell timeout + * + * Replace a running shell command's timeout from now, or clear it with zero. + */ + public timeout( + parameters: { + id: string + location?: { + directory?: string | null + workspace?: string | null + } | null + timeout?: number + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "id" }, + { in: "query", key: "location" }, + { in: "body", key: "timeout" }, + ], + }, + ], + ) + return (options?.client ?? this.client).patch({ + url: "/api/shell/{id}/timeout", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + /** * Read shell output * @@ -8013,7 +8092,7 @@ export class Vcs2 extends HeyApiClient { directory?: string | null workspace?: string | null } | null - mode: VcsMode2 + mode: VcsMode context?: string | null }, options?: Options, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 70b85f2e0b..87aa5ddbe1 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -24,7 +24,10 @@ export type Event = | EventSessionForked | EventSessionPromptPromoted | EventSessionPromptAdmitted - | EventSessionExecutionSettled + | EventSessionExecutionStarted + | EventSessionExecutionSucceeded + | EventSessionExecutionFailed + | EventSessionExecutionInterrupted | EventSessionInstructionsUpdated | EventSessionSynthetic | EventSessionSkillActivated @@ -46,10 +49,12 @@ export type Event = | EventSessionToolProgress | EventSessionToolSuccess | EventSessionToolFailed - | EventSessionRetried + | EventSessionRetryScheduled + | EventSessionCompactionAdmitted | EventSessionCompactionStarted | EventSessionCompactionDelta | EventSessionCompactionEnded + | EventSessionCompactionFailed | EventSessionRevertStaged | EventSessionRevertCleared | EventSessionRevertCommitted @@ -821,7 +826,6 @@ export type GlobalEvent = { type: "session.deleted" properties: { sessionID: string - info: Session } } | { @@ -920,11 +924,32 @@ export type GlobalEvent = { } | { id: string - type: "session.execution.settled" + type: "session.execution.started" properties: { sessionID: string - outcome: "success" | "failure" | "interrupted" - error?: SessionErrorUnknown + } + } + | { + id: string + type: "session.execution.succeeded" + properties: { + sessionID: string + } + } + | { + id: string + type: "session.execution.failed" + properties: { + sessionID: string + error: SessionStructuredError + } + } + | { + id: string + type: "session.execution.interrupted" + properties: { + sessionID: string + reason: "user" | "shutdown" | "superseded" } } | { @@ -995,7 +1020,7 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - finish: string + finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" cost: number tokens: { input: number @@ -1016,7 +1041,7 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionStructuredError } } | { @@ -1025,7 +1050,7 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - textID: string + ordinal: number } } | { @@ -1034,7 +1059,7 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - textID: string + ordinal: number delta: string } } @@ -1044,7 +1069,7 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - textID: string + ordinal: number text: string } } @@ -1054,8 +1079,8 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata + ordinal: number + state?: SessionMessageProviderState } } | { @@ -1064,7 +1089,7 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number delta: string } } @@ -1074,9 +1099,9 @@ export type GlobalEvent = { properties: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number text: string - providerMetadata?: LlmProviderMetadata + state?: SessionMessageProviderState } } | { @@ -1116,14 +1141,11 @@ export type GlobalEvent = { sessionID: string assistantMessageID: string callID: string - tool: string input: { [key: string]: unknown } - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + state?: SessionMessageProviderState } } | { @@ -1152,10 +1174,8 @@ export type GlobalEvent = { content: Array outputPaths?: Array result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } | { @@ -1165,21 +1185,29 @@ export type GlobalEvent = { sessionID: string assistantMessageID: string callID: string - error: SessionErrorUnknown + error: SessionStructuredError result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } | { id: string - type: "session.retried" + type: "session.retry.scheduled" properties: { sessionID: string + assistantMessageID: string attempt: number - error: SessionRetryError + at: number + error: SessionStructuredError + } + } + | { + id: string + type: "session.compaction.admitted" + properties: { + sessionID: string + inputID: string } } | { @@ -1208,6 +1236,13 @@ export type GlobalEvent = { recent: string } } + | { + id: string + type: "session.compaction.failed" + properties: { + sessionID: string + } + } | { id: string type: "session.revert.staged" @@ -1228,7 +1263,7 @@ export type GlobalEvent = { type: "session.revert.committed" properties: { sessionID: string - messageID: string + to: string } } | { @@ -1731,6 +1766,10 @@ export type GlobalEvent = { | SyncEventSessionForked | SyncEventSessionPromptPromoted | SyncEventSessionPromptAdmitted + | SyncEventSessionExecutionStarted + | SyncEventSessionExecutionSucceeded + | SyncEventSessionExecutionFailed + | SyncEventSessionExecutionInterrupted | SyncEventSessionInstructionsUpdated | SyncEventSessionSynthetic | SyncEventSessionSkillActivated @@ -1749,9 +1788,11 @@ export type GlobalEvent = { | SyncEventSessionToolProgress | SyncEventSessionToolSuccess | SyncEventSessionToolFailed - | SyncEventSessionRetried + | SyncEventSessionRetryScheduled + | SyncEventSessionCompactionAdmitted | SyncEventSessionCompactionStarted | SyncEventSessionCompactionEnded + | SyncEventSessionCompactionFailed | SyncEventSessionRevertStaged | SyncEventSessionRevertCleared | SyncEventSessionRevertCommitted @@ -2891,9 +2932,14 @@ export type SessionDurableEvent = | SessionModelSelected | SessionMoved | SessionRenamed + | SessionDeleted | SessionForked | SessionPromptPromoted | SessionPromptAdmitted + | SessionExecutionStarted + | SessionExecutionSucceeded + | SessionExecutionFailed + | SessionExecutionInterrupted | SessionInstructionsUpdated | SessionSynthetic | SessionSkillActivated @@ -2912,9 +2958,11 @@ export type SessionDurableEvent = | SessionToolProgress | SessionToolSuccess | SessionToolFailed - | SessionRetried + | SessionRetryScheduled + | SessionCompactionAdmitted | SessionCompactionStarted | SessionCompactionEnded + | SessionCompactionFailed | SessionRevertStaged | SessionRevertCleared | SessionRevertCommitted @@ -3034,7 +3082,10 @@ export type V2Event = | SessionForked | SessionPromptPromoted | SessionPromptAdmitted - | SessionExecutionSettled + | SessionExecutionStarted + | SessionExecutionSucceeded + | SessionExecutionFailed + | SessionExecutionInterrupted | SessionInstructionsUpdated | SessionSynthetic | SessionSkillActivated @@ -3056,10 +3107,12 @@ export type V2Event = | SessionToolProgress | SessionToolSuccess | SessionToolFailed - | SessionRetried + | SessionRetryScheduled + | SessionCompactionAdmitted | SessionCompactionStarted | SessionCompactionDelta | SessionCompactionEnded + | SessionCompactionFailed | SessionRevertStaged | SessionRevertCleared | SessionRevertCommitted @@ -3286,15 +3339,13 @@ export type PromptAgentAttachment = { mention?: PromptMention } -export type SessionErrorUnknown = { - type: "unknown" +export type SessionStructuredError = { + type: string message: string } -export type LlmProviderMetadata = { - [key: string]: { - [key: string]: unknown - } +export type SessionMessageProviderState = { + [key: string]: unknown } export type ToolTextContent = { @@ -3311,19 +3362,6 @@ export type ToolFileContent = { export type LlmToolContent = ToolTextContent | ToolFileContent -export type SessionRetryError = { - message: string - statusCode?: number - isRetryable: boolean - responseHeaders?: { - [key: string]: string - } - responseBody?: string - metadata?: { - [key: string]: string - } -} - export type FileDiff = { path: string status: "added" | "modified" | "deleted" @@ -3561,13 +3599,12 @@ export type SyncEventSessionDeleted = { type: "sync" id: string syncEvent: { - type: "session.deleted.1" + type: "session.deleted.2" id: string seq: number aggregateID: string data: { sessionID: string - info: Session } } } @@ -3743,6 +3780,64 @@ export type SyncEventSessionPromptAdmitted = { } } +export type SyncEventSessionExecutionStarted = { + type: "sync" + id: string + syncEvent: { + type: "session.execution.started.1" + id: string + seq: number + aggregateID: string + data: { + sessionID: string + } + } +} + +export type SyncEventSessionExecutionSucceeded = { + type: "sync" + id: string + syncEvent: { + type: "session.execution.succeeded.1" + id: string + seq: number + aggregateID: string + data: { + sessionID: string + } + } +} + +export type SyncEventSessionExecutionFailed = { + type: "sync" + id: string + syncEvent: { + type: "session.execution.failed.1" + id: string + seq: number + aggregateID: string + data: { + sessionID: string + error: SessionStructuredError + } + } +} + +export type SyncEventSessionExecutionInterrupted = { + type: "sync" + id: string + syncEvent: { + type: "session.execution.interrupted.1" + id: string + seq: number + aggregateID: string + data: { + sessionID: string + reason: "user" | "shutdown" | "superseded" + } + } +} + export type SyncEventSessionInstructionsUpdated = { type: "sync" id: string @@ -3858,7 +3953,7 @@ export type SyncEventSessionStepEnded = { data: { sessionID: string assistantMessageID: string - finish: string + finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" cost: number tokens: { input: number @@ -3886,7 +3981,7 @@ export type SyncEventSessionStepFailed = { data: { sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionStructuredError } } } @@ -3902,7 +3997,7 @@ export type SyncEventSessionTextStarted = { data: { sessionID: string assistantMessageID: string - textID: string + ordinal: number } } } @@ -3918,7 +4013,7 @@ export type SyncEventSessionTextEnded = { data: { sessionID: string assistantMessageID: string - textID: string + ordinal: number text: string } } @@ -3935,8 +4030,8 @@ export type SyncEventSessionReasoningStarted = { data: { sessionID: string assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata + ordinal: number + state?: SessionMessageProviderState } } } @@ -3952,9 +4047,9 @@ export type SyncEventSessionReasoningEnded = { data: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number text: string - providerMetadata?: LlmProviderMetadata + state?: SessionMessageProviderState } } } @@ -4005,14 +4100,11 @@ export type SyncEventSessionToolCalled = { sessionID: string assistantMessageID: string callID: string - tool: string input: { [key: string]: unknown } - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + state?: SessionMessageProviderState } } } @@ -4055,10 +4147,8 @@ export type SyncEventSessionToolSuccess = { content: Array outputPaths?: Array result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } } @@ -4075,28 +4165,43 @@ export type SyncEventSessionToolFailed = { sessionID: string assistantMessageID: string callID: string - error: SessionErrorUnknown + error: SessionStructuredError result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } } -export type SyncEventSessionRetried = { +export type SyncEventSessionRetryScheduled = { type: "sync" id: string syncEvent: { - type: "session.retried.1" + type: "session.retry.scheduled.1" id: string seq: number aggregateID: string data: { sessionID: string + assistantMessageID: string attempt: number - error: SessionRetryError + at: number + error: SessionStructuredError + } + } +} + +export type SyncEventSessionCompactionAdmitted = { + type: "sync" + id: string + syncEvent: { + type: "session.compaction.admitted.1" + id: string + seq: number + aggregateID: string + data: { + sessionID: string + inputID: string } } } @@ -4133,6 +4238,20 @@ export type SyncEventSessionCompactionEnded = { } } +export type SyncEventSessionCompactionFailed = { + type: "sync" + id: string + syncEvent: { + type: "session.compaction.failed.1" + id: string + seq: number + aggregateID: string + data: { + sessionID: string + } + } +} + export type SyncEventSessionRevertStaged = { type: "sync" id: string @@ -4172,7 +4291,7 @@ export type SyncEventSessionRevertCommitted = { aggregateID: string data: { sessionID: string - messageID: string + to: string } } } @@ -4262,6 +4381,10 @@ export type PluginInfo = { export type SessionV2Info = { id: string parentID?: string + fork?: { + sessionID: string + messageID?: string + } projectID: string agent?: string model?: ModelRef @@ -4303,6 +4426,15 @@ export type SessionInputAdmitted = { promotedSeq?: number } +export type SessionInputCompaction = { + type: "compaction" + admittedSeq: number + id: string + sessionID: string + timeCreated: number + handledSeq?: number +} + export type SessionMessageAgentSelected = { id: string metadata?: { @@ -4402,15 +4534,13 @@ export type SessionMessageShell = { export type SessionMessageAssistantText = { type: "text" - id: string text: string } export type SessionMessageAssistantReasoning = { type: "reasoning" - id: string text: string - providerMetadata?: LlmProviderMetadata + state?: SessionMessageProviderState time?: { created: number completed?: number @@ -4456,7 +4586,7 @@ export type SessionMessageToolStateError = { structured: { [key: string]: unknown } - error: SessionErrorUnknown + error: SessionStructuredError result?: unknown } @@ -4464,11 +4594,9 @@ export type SessionMessageAssistantTool = { type: "tool" id: string name: string - provider?: { - executed: boolean - metadata?: LlmProviderMetadata - resultMetadata?: LlmProviderMetadata - } + executed?: boolean + providerState?: SessionMessageProviderState + providerResultState?: SessionMessageProviderState state: | SessionMessageToolStatePending | SessionMessageToolStateRunning @@ -4482,6 +4610,12 @@ export type SessionMessageAssistantTool = { } } +export type SessionMessageAssistantRetry = { + attempt: number + at: number + error: SessionStructuredError +} + export type SessionMessageAssistant = { id: string metadata?: { @@ -4500,7 +4634,7 @@ export type SessionMessageAssistant = { end?: string files?: Array } - finish?: string + finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" cost?: number tokens?: { input: number @@ -4511,11 +4645,13 @@ export type SessionMessageAssistant = { write: number } } - error?: SessionErrorUnknown + error?: SessionStructuredError + retry?: SessionMessageAssistantRetry } export type SessionMessageCompaction = { type: "compaction" + status: "queued" | "running" | "completed" | "failed" reason: "auto" | "manual" summary: string recent: string @@ -4623,6 +4759,24 @@ export type SessionRenamed = { } } +export type SessionDeleted = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.deleted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + } +} + export type SessionForked = { id: string created: number @@ -4683,6 +4837,80 @@ export type SessionPromptAdmitted = { } } +export type SessionExecutionStarted = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + } +} + +export type SessionExecutionSucceeded = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.succeeded" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + } +} + +export type SessionExecutionFailed = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.failed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + error: SessionStructuredError + } +} + +export type SessionExecutionInterrupted = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.interrupted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + reason: "user" | "shutdown" | "superseded" + } +} + export type SessionInstructionsUpdated = { id: string created: number @@ -4827,7 +5055,7 @@ export type SessionStepEnded = { data: { sessionID: string assistantMessageID: string - finish: string + finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" cost: number tokens: { input: number @@ -4859,7 +5087,7 @@ export type SessionStepFailed = { data: { sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionStructuredError } } @@ -4879,7 +5107,7 @@ export type SessionTextStarted = { data: { sessionID: string assistantMessageID: string - textID: string + ordinal: number } } @@ -4899,7 +5127,7 @@ export type SessionTextEnded = { data: { sessionID: string assistantMessageID: string - textID: string + ordinal: number text: string } } @@ -4920,8 +5148,8 @@ export type SessionReasoningStarted = { data: { sessionID: string assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata + ordinal: number + state?: SessionMessageProviderState } } @@ -4941,9 +5169,9 @@ export type SessionReasoningEnded = { data: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number text: string - providerMetadata?: LlmProviderMetadata + state?: SessionMessageProviderState } } @@ -5006,14 +5234,11 @@ export type SessionToolCalled = { sessionID: string assistantMessageID: string callID: string - tool: string input: { [key: string]: unknown } - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + state?: SessionMessageProviderState } } @@ -5064,10 +5289,8 @@ export type SessionToolSuccess = { content: Array outputPaths?: Array result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } @@ -5088,22 +5311,20 @@ export type SessionToolFailed = { sessionID: string assistantMessageID: string callID: string - error: SessionErrorUnknown + error: SessionStructuredError result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } -export type SessionRetried = { +export type SessionRetryScheduled = { id: string created: number metadata?: { [key: string]: unknown } - type: "session.retried" + type: "session.retry.scheduled" durable: { aggregateID: string seq: number @@ -5112,8 +5333,29 @@ export type SessionRetried = { location?: LocationRef data: { sessionID: string + assistantMessageID: string attempt: number - error: SessionRetryError + at: number + error: SessionStructuredError + } +} + +export type SessionCompactionAdmitted = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.admitted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + inputID: string } } @@ -5157,6 +5399,24 @@ export type SessionCompactionEnded = { } } +export type SessionCompactionFailed = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.failed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRef + data: { + sessionID: string + } +} + export type SessionRevertStaged = { id: string created: number @@ -5209,7 +5469,7 @@ export type SessionRevertCommitted = { location?: LocationRef data: { sessionID: string - messageID: string + to: string } } @@ -5617,25 +5877,6 @@ export type SessionUpdated = { } } -export type SessionDeleted = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.deleted" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef - data: { - sessionID: string - info: Session - } -} - export type MessageUpdated = { id: string created: number @@ -5714,21 +5955,6 @@ export type MessagePartRemoved = { } } -export type SessionExecutionSettled = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.execution.settled" - location?: LocationRef - data: { - sessionID: string - outcome: "success" | "failure" | "interrupted" - error?: SessionErrorUnknown - } -} - export type SessionTextDelta = { id: string created: number @@ -5740,7 +5966,7 @@ export type SessionTextDelta = { data: { sessionID: string assistantMessageID: string - textID: string + ordinal: number delta: string } } @@ -5756,7 +5982,7 @@ export type SessionReasoningDelta = { data: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number delta: string } } @@ -6706,7 +6932,6 @@ export type EventSessionDeleted = { type: "session.deleted" properties: { sessionID: string - info: Session } } @@ -6815,13 +7040,37 @@ export type EventSessionPromptAdmitted = { } } -export type EventSessionExecutionSettled = { +export type EventSessionExecutionStarted = { id: string - type: "session.execution.settled" + type: "session.execution.started" properties: { sessionID: string - outcome: "success" | "failure" | "interrupted" - error?: SessionErrorUnknown + } +} + +export type EventSessionExecutionSucceeded = { + id: string + type: "session.execution.succeeded" + properties: { + sessionID: string + } +} + +export type EventSessionExecutionFailed = { + id: string + type: "session.execution.failed" + properties: { + sessionID: string + error: SessionStructuredError + } +} + +export type EventSessionExecutionInterrupted = { + id: string + type: "session.execution.interrupted" + properties: { + sessionID: string + reason: "user" | "shutdown" | "superseded" } } @@ -6899,7 +7148,7 @@ export type EventSessionStepEnded = { properties: { sessionID: string assistantMessageID: string - finish: string + finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" cost: number tokens: { input: number @@ -6921,7 +7170,7 @@ export type EventSessionStepFailed = { properties: { sessionID: string assistantMessageID: string - error: SessionErrorUnknown + error: SessionStructuredError } } @@ -6931,7 +7180,7 @@ export type EventSessionTextStarted = { properties: { sessionID: string assistantMessageID: string - textID: string + ordinal: number } } @@ -6941,7 +7190,7 @@ export type EventSessionTextDelta = { properties: { sessionID: string assistantMessageID: string - textID: string + ordinal: number delta: string } } @@ -6952,7 +7201,7 @@ export type EventSessionTextEnded = { properties: { sessionID: string assistantMessageID: string - textID: string + ordinal: number text: string } } @@ -6963,8 +7212,8 @@ export type EventSessionReasoningStarted = { properties: { sessionID: string assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata + ordinal: number + state?: SessionMessageProviderState } } @@ -6974,7 +7223,7 @@ export type EventSessionReasoningDelta = { properties: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number delta: string } } @@ -6985,9 +7234,9 @@ export type EventSessionReasoningEnded = { properties: { sessionID: string assistantMessageID: string - reasoningID: string + ordinal: number text: string - providerMetadata?: LlmProviderMetadata + state?: SessionMessageProviderState } } @@ -7031,14 +7280,11 @@ export type EventSessionToolCalled = { sessionID: string assistantMessageID: string callID: string - tool: string input: { [key: string]: unknown } - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + state?: SessionMessageProviderState } } @@ -7069,10 +7315,8 @@ export type EventSessionToolSuccess = { content: Array outputPaths?: Array result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } @@ -7083,22 +7327,31 @@ export type EventSessionToolFailed = { sessionID: string assistantMessageID: string callID: string - error: SessionErrorUnknown + error: SessionStructuredError result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata - } + executed: boolean + resultState?: SessionMessageProviderState } } -export type EventSessionRetried = { +export type EventSessionRetryScheduled = { id: string - type: "session.retried" + type: "session.retry.scheduled" properties: { sessionID: string + assistantMessageID: string attempt: number - error: SessionRetryError + at: number + error: SessionStructuredError + } +} + +export type EventSessionCompactionAdmitted = { + id: string + type: "session.compaction.admitted" + properties: { + sessionID: string + inputID: string } } @@ -7131,6 +7384,14 @@ export type EventSessionCompactionEnded = { } } +export type EventSessionCompactionFailed = { + id: string + type: "session.compaction.failed" + properties: { + sessionID: string + } +} + export type EventSessionRevertStaged = { id: string type: "session.revert.staged" @@ -7153,7 +7414,7 @@ export type EventSessionRevertCommitted = { type: "session.revert.committed" properties: { sessionID: string - messageID: string + to: string } } @@ -7707,11 +7968,6 @@ export type BadRequestError = { } } -export type UnauthorizedErrorV2 = { - _tag: "UnauthorizedError" - message: string -} - export type InvalidRequestErrorV2 = { _tag: "InvalidRequestError" message: string @@ -7719,125 +7975,14 @@ export type InvalidRequestErrorV2 = { field?: string | null } -export type LocationInfo2 = { - directory: string - workspaceID?: string - project: { - id: string - directory: string - } -} - -export type ModelRef2 = { - id: string - providerID: string - variant?: string -} - -export type ProviderSettings2 = { - [key: string]: unknown -} - -export type ProviderRequest2 = { - settings: ProviderSettings2 - headers: { - [key: string]: string - } - body: { - [key: string]: unknown - } -} - -export type AgentColor2 = string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info" - -export type PermissionV2Effect2 = "allow" | "deny" | "ask" - -export type PermissionV2Rule2 = { - action: string - resource: string - effect: PermissionV2Effect2 -} - -export type PermissionV2Ruleset2 = Array - -export type AgentV2Info2 = { - id: string - model?: ModelRef2 - request: ProviderRequest2 - system?: string - description?: string - mode: "subagent" | "primary" | "all" - hidden: boolean - color?: AgentColor2 - steps?: number - permissions: PermissionV2Ruleset2 -} - -export type PluginInfo2 = { - id: string -} - -export type LocationRef2 = { - directory: string - workspaceID?: string -} - -export type FileDiff2 = { - path: string - status: "added" | "modified" | "deleted" - additions: number - deletions: number - patch: string -} - -export type RevertState2 = { - messageID: string - partID?: string - snapshot?: string - diff?: string - files?: Array -} - -export type SessionV2Info2 = { - id: string - parentID?: string - projectID: string - agent?: string - model?: ModelRef2 - cost: number - tokens: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number - } - } - time: { - created: number - updated: number - archived?: number - } - title: string - location: LocationRef2 - subpath?: string - revert?: RevertState2 -} - export type SessionsResponseV2 = { - data: Array + data: Array cursor: { previous?: string | null next?: string | null } } -export type InvalidCursorErrorV2 = { - _tag: "InvalidCursorError" - message: string -} - export type InvalidRequestError1 = { _tag: "InvalidRequestError" message: string @@ -7845,113 +7990,12 @@ export type InvalidRequestError1 = { field?: string | null } -export type SessionActiveV2 = { - type: "running" -} - -export type SessionNotFoundErrorV2 = { - _tag: "SessionNotFoundError" - sessionID: string - message: string -} - -export type MessageNotFoundErrorV2 = { - _tag: "MessageNotFoundError" - sessionID: string - messageID: string - message: string -} - -export type PromptMention2 = { - start: number - end: number - text: string -} - -export type PromptInputFileAttachment2 = { - uri: string - name?: string - description?: string - mention?: PromptMention2 -} - -export type PromptAgentAttachment2 = { - name: string - mention?: PromptMention2 -} - -export type PromptInputV2 = { - text: string - files?: Array - agents?: Array -} - -export type PromptBase642 = string - -export type PromptFileSource2 = - | { - type: "inline" - } - | { - type: "uri" - uri: string - } - -export type PromptFileAttachment2 = { - data: PromptBase642 - mime: string - source: PromptFileSource2 - name?: string - description?: string - mention?: PromptMention2 -} - -export type PromptV2 = { - text: string - files?: Array - agents?: Array -} - -export type SessionInputAdmitted2 = { - admittedSeq: number - id: string - sessionID: string - prompt: PromptV2 - delivery: "steer" | "queue" - timeCreated: number - promotedSeq?: number -} - export type ConflictErrorV2 = { _tag: "ConflictError" message: string resource?: string | null } -export type CommandNotFoundErrorV2 = { - _tag: "CommandNotFoundError" - command: string - message: string -} - -export type CommandEvaluationErrorV2 = { - _tag: "CommandEvaluationError" - command: string - message: string -} - -export type SkillNotFoundErrorV2 = { - _tag: "SkillNotFoundError" - skill: string - message: string -} - -export type SessionBusyErrorV2 = { - _tag: "SessionBusyError" - sessionID: string - message: string -} - export type ServiceUnavailableErrorV2 = { _tag: "ServiceUnavailableError" message: string @@ -7964,84 +8008,6 @@ export type UnknownErrorV2 = { ref?: string | null } -export type SessionMessageAgentSelected2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - type: "agent-switched" - agent: string -} - -export type SessionMessageModelSelected2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - type: "model-switched" - model: ModelRef2 - previous?: ModelRef2 -} - -export type SessionMessageUser2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - text: string - files?: Array - agents?: Array - type: "user" -} - -export type SessionMessageSynthetic2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - sessionID: string - text: string - description?: string - type: "synthetic" -} - -export type SessionMessageSystem2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - type: "system" - text: string -} - -export type SessionMessageSkill2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } - type: "skill" - name: string - text: string -} - export type ShellV2 = { id: string status: "running" | "exited" | "timeout" | "killed" @@ -8060,1569 +8026,14 @@ export type ShellV2 = { } } -export type SessionMessageShell2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - completed?: number - } - type: "shell" - shell: ShellV2 - output?: { - output: string - cursor: number - size: number - truncated: boolean - } -} - -export type SessionMessageAssistantText2 = { - type: "text" - id: string - text: string -} - -export type LlmProviderMetadata2 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionMessageAssistantReasoning2 = { - type: "reasoning" - id: string - text: string - providerMetadata?: LlmProviderMetadata2 - time?: { - created: number - completed?: number - } -} - -export type SessionMessageToolStatePending2 = { - status: "pending" - input: string -} - -export type ToolTextContent2 = { - type: "text" - text: string -} - -export type ToolFileContent2 = { - type: "file" - uri: string - mime: string - name?: string -} - -export type LlmToolContent2 = ToolTextContent2 | ToolFileContent2 - -export type SessionMessageToolStateRunning2 = { - status: "running" - input: { - [key: string]: unknown - } - structured: { - [key: string]: unknown - } - content: Array -} - -export type SessionMessageToolStateCompleted2 = { - status: "completed" - input: { - [key: string]: unknown - } - attachments?: Array - content: Array - outputPaths?: Array - structured: { - [key: string]: unknown - } - result?: unknown -} - -export type SessionErrorUnknown2 = { - type: "unknown" - message: string -} - -export type SessionMessageToolStateError2 = { - status: "error" - input: { - [key: string]: unknown - } - content: Array - structured: { - [key: string]: unknown - } - error: SessionErrorUnknown2 - result?: unknown -} - -export type SessionMessageAssistantTool2 = { - type: "tool" - id: string - name: string - provider?: { - executed: boolean - metadata?: LlmProviderMetadata2 - resultMetadata?: LlmProviderMetadata2 - } - state: - | SessionMessageToolStatePending2 - | SessionMessageToolStateRunning2 - | SessionMessageToolStateCompleted2 - | SessionMessageToolStateError2 - time: { - created: number - ran?: number - completed?: number - pruned?: number - } -} - -export type SessionMessageAssistant2 = { - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - completed?: number - } - type: "assistant" - agent: string - model: ModelRef2 - content: Array - snapshot?: { - start?: string - end?: string - files?: Array - } - finish?: string - cost?: number - tokens?: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number - } - } - error?: SessionErrorUnknown2 -} - -export type SessionMessageCompaction2 = { - type: "compaction" - reason: "auto" | "manual" - summary: string - recent: string - id: string - metadata?: { - [key: string]: unknown - } - time: { - created: number - } -} - -export type SessionMessage2 = - | SessionMessageAgentSelected2 - | SessionMessageModelSelected2 - | SessionMessageUser2 - | SessionMessageSynthetic2 - | SessionMessageSystem2 - | SessionMessageSkill2 - | SessionMessageShell2 - | SessionMessageAssistant2 - | SessionMessageCompaction2 - -/** - * Instruction entry key (lowercase alphanumerics plus . _ -) - */ -export type InstructionEntryKey2 = string - -export type InstructionEntryInfo2 = { - key: InstructionEntryKey2 - value: unknown -} - -export type SessionAgentSelected2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.agent.selected" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - agent: string - } -} - -export type SessionModelSelected2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.model.selected" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - model: ModelRef2 - } -} - -export type SessionMoved2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.moved" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - location: LocationRef2 - subpath?: string - } -} - -export type SessionRenamed2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.renamed" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - title: string - } -} - -export type SessionForked2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.forked" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - parentID: string - from?: string - } -} - -export type SessionPromptPromoted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.prompt.promoted" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - inputID: string - } -} - -export type SessionPromptAdmitted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.prompt.admitted" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - inputID: string - prompt: PromptV2 - delivery: "steer" | "queue" - } -} - -export type SessionInstructionsUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.instructions.updated" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - text: string - } -} - -export type SessionSynthetic2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.synthetic" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - text: string - description?: string - metadata?: { - [key: string]: unknown - } - } -} - -export type SessionSkillActivated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.skill.activated" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - name: string - text: string - } -} - -export type Shell1V2 = { - id: string - status: "running" | "exited" | "timeout" | "killed" - command: string - cwd: string - shell: string - file: string - pid?: number - exit?: number | "NaN" | "Infinity" | "-Infinity" - metadata: { - [key: string]: unknown - } - time: { - started: number | "NaN" | "Infinity" | "-Infinity" - completed?: number | "NaN" | "Infinity" | "-Infinity" - } -} - -export type SessionShellStarted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.shell.started" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - shell: Shell1V2 - } -} - -export type SessionShellEnded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.shell.ended" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - shell: Shell1V2 - output: { - output: string - cursor: number - size: number - truncated: boolean - } - } -} - -export type SessionStepStarted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.step.started" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - agent: string - model: ModelRef2 - snapshot?: string - } -} - -export type SessionStepEnded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.step.ended" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - finish: string - cost: number - tokens: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number - } - } - snapshot?: string - files?: Array - } -} - -export type SessionStepFailed2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.step.failed" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - error: SessionErrorUnknown2 - } -} - -export type SessionTextStarted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.text.started" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - textID: string - } -} - -export type SessionTextEnded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.text.ended" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - textID: string - text: string - } -} - -export type LlmProviderMetadata3 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionReasoningStarted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.reasoning.started" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - reasoningID: string - providerMetadata?: LlmProviderMetadata3 - } -} - -export type LlmProviderMetadata4 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionReasoningEnded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.reasoning.ended" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - reasoningID: string - text: string - providerMetadata?: LlmProviderMetadata4 - } -} - -export type SessionToolInputStarted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.input.started" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - name: string - } -} - -export type SessionToolInputEnded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.input.ended" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - text: string - } -} - -export type LlmProviderMetadata5 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionToolCalled2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.called" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - tool: string - input: { - [key: string]: unknown - } - provider: { - executed: boolean - metadata?: LlmProviderMetadata5 - } - } -} - -export type SessionToolProgress2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.progress" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - structured: { - [key: string]: unknown - } - content: Array - } -} - -export type LlmProviderMetadata6 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionToolSuccess2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.success" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - structured: { - [key: string]: unknown - } - content: Array - outputPaths?: Array - result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata6 - } - } -} - -export type LlmProviderMetadata7 = { - [key: string]: { - [key: string]: unknown - } -} - -export type SessionToolFailed2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.failed" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - error: SessionErrorUnknown2 - result?: unknown - provider: { - executed: boolean - metadata?: LlmProviderMetadata7 - } - } -} - -export type SessionRetryError2 = { - message: string - statusCode?: number - isRetryable: boolean - responseHeaders?: { - [key: string]: string - } - responseBody?: string - metadata?: { - [key: string]: string - } -} - -export type SessionRetried2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.retried" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - attempt: number - error: SessionRetryError2 - } -} - -export type SessionCompactionStarted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.compaction.started" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - reason: "auto" | "manual" - } -} - -export type SessionCompactionEnded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.compaction.ended" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - reason: "auto" | "manual" - text: string - recent: string - } -} - -export type SessionRevertStaged2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.revert.staged" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - revert: RevertState2 - } -} - -export type SessionRevertCleared2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.revert.cleared" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - } -} - -export type SessionRevertCommitted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.revert.committed" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - messageID: string - } -} - -export type SessionDurableEventV2 = - | SessionAgentSelected2 - | SessionModelSelected2 - | SessionMoved2 - | SessionRenamed2 - | SessionForked2 - | SessionPromptPromoted2 - | SessionPromptAdmitted2 - | SessionInstructionsUpdated2 - | SessionSynthetic2 - | SessionSkillActivated2 - | SessionShellStarted2 - | SessionShellEnded2 - | SessionStepStarted2 - | SessionStepEnded2 - | SessionStepFailed2 - | SessionTextStarted2 - | SessionTextEnded2 - | SessionReasoningStarted2 - | SessionReasoningEnded2 - | SessionToolInputStarted2 - | SessionToolInputEnded2 - | SessionToolCalled2 - | SessionToolProgress2 - | SessionToolSuccess2 - | SessionToolFailed2 - | SessionRetried2 - | SessionCompactionStarted2 - | SessionCompactionEnded2 - | SessionRevertStaged2 - | SessionRevertCleared2 - | SessionRevertCommitted2 - -/** - * Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq. - */ -export type EventLogSynced2 = { - type: "log.synced" - aggregateID: string - seq?: number -} - -export type SessionLogItemV2 = SessionDurableEventV2 | EventLogSynced2 - -export type SessionLogItemStreamV2 = string - export type SessionMessagesResponseV2 = { - data: Array + data: Array cursor: { previous?: string | null next?: string | null } } -export type ModelCapabilities2 = { - tools: boolean - input: Array - output: Array -} - -export type ModelVariant2 = { - id: string - settings?: { - [key: string]: unknown - } - headers?: { - [key: string]: string - } - body?: { - [key: string]: unknown - } -} - -export type ModelCost2 = { - tier?: { - type: "context" - size: number - } - input: number - output: number - cache: { - read: number - write: number - } -} - -export type ModelV2Info2 = { - id: string - modelID: string - providerID: string - family?: string - name: string - package?: string - settings?: { - [key: string]: unknown - } - headers?: { - [key: string]: string - } - body?: { - [key: string]: unknown - } - capabilities: ModelCapabilities2 - variants: Array - time: { - released: number - } - cost: Array - status: "alpha" | "beta" | "deprecated" | "active" - enabled: boolean - limit: { - context: number - input?: number - output: number - } -} - -export type GenerateTextResponseV2 = { - data: { - text: string - } -} - -export type ProviderV2Info2 = { - id: string - integrationID?: string - name: string - disabled?: boolean - package: string - settings?: { - [key: string]: unknown - } - headers?: { - [key: string]: string - } - body?: { - [key: string]: unknown - } -} - -export type ProviderNotFoundErrorV2 = { - _tag: "ProviderNotFoundError" - providerID: string - message: string -} - -export type IntegrationWhen2 = { - key: string - op: "eq" | "neq" - value: string -} - -export type IntegrationTextPrompt2 = { - type: "text" - key: string - message: string - placeholder?: string - when?: IntegrationWhen2 -} - -export type IntegrationSelectPrompt2 = { - type: "select" - key: string - message: string - options: Array<{ - label: string - value: string - hint?: string - }> - when?: IntegrationWhen2 -} - -export type IntegrationOAuthMethod2 = { - id: string - type: "oauth" - label: string - prompts?: Array -} - -export type IntegrationKeyMethod2 = { - type: "key" - label?: string -} - -export type IntegrationEnvMethod2 = { - type: "env" - names: Array -} - -export type IntegrationMethod2 = IntegrationOAuthMethod2 | IntegrationKeyMethod2 | IntegrationEnvMethod2 - -export type ConnectionCredentialInfo2 = { - type: "credential" - id: string - label: string -} - -export type ConnectionEnvInfo2 = { - type: "env" - name: string -} - -export type ConnectionInfo2 = ConnectionCredentialInfo2 | ConnectionEnvInfo2 - -export type IntegrationInfo2 = { - id: string - name: string - methods: Array - connections: Array -} - -export type IntegrationAttempt2 = { - attemptID: string - url: string - instructions: string - mode: "auto" | "code" - time: { - created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } -} - -export type IntegrationAttemptStatus2 = - | { - status: "pending" - time: { - created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } - } - | { - status: "complete" - time: { - created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } - } - | { - status: "failed" - message: string - time: { - created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } - } - | { - status: "expired" - time: { - created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } - } - -export type McpStatusConnected3 = { - status: "connected" -} - -export type McpStatusPending2 = { - status: "pending" -} - -export type McpStatusDisabled3 = { - status: "disabled" -} - -export type McpStatusFailed3 = { - status: "failed" - error: string -} - -export type McpStatusNeedsAuth3 = { - status: "needs_auth" -} - -export type McpStatusNeedsClientRegistration3 = { - status: "needs_client_registration" - error: string -} - -export type McpServer2 = { - name: string - status: - | McpStatusConnected3 - | McpStatusPending2 - | McpStatusDisabled3 - | McpStatusFailed3 - | McpStatusNeedsAuth3 - | McpStatusNeedsClientRegistration3 - integrationID?: string -} - -export type ProjectVcs2 = "git" | "hg" - -export type ProjectIcon2 = { - url?: string - override?: string - color?: string -} - -export type ProjectCommands2 = { - /** - * Startup script to run when creating a new workspace (worktree) - */ - start?: string -} - -export type ProjectTime2 = { - created: number - updated: number - initialized?: number -} - -export type ProjectV2 = { - id: string - worktree: string - vcs?: ProjectVcs2 - name?: string - icon?: ProjectIcon2 - commands?: ProjectCommands2 - time: ProjectTime2 - sandboxes: Array -} - -export type ProjectCurrent2 = { - id: string - directory: string -} - -export type ProjectDirectory2 = { - directory: string - strategy?: string -} - -export type ProjectDirectories2 = Array - -export type FormMetadata2 = { - [key: string]: unknown -} - -export type FormWhen2 = { - key: string - op: "eq" | "neq" - value: string | number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" | boolean -} - -export type FormOption2 = { - value: string - label: string - description?: string -} - -export type FormStringField2 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "string" - format?: "email" | "uri" | "date" | "date-time" - minLength?: number - maxLength?: number - pattern?: string - placeholder?: string - default?: string - options?: Array - custom?: boolean -} - -export type FormNumberField3 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "number" - minimum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - maximum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - default?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" -} - -export type FormIntegerField3 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "integer" - minimum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - maximum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - default?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" -} - -export type FormBooleanField2 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "boolean" - default?: boolean -} - -export type FormMultiselectField2 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "multiselect" - options: Array - minItems?: number - maxItems?: number - custom?: boolean - default?: Array -} - -export type FormFormInfo2 = { - id: string - sessionID: string - title?: string - metadata?: FormMetadata2 - mode: "form" - fields: Array -} - -export type FormUrlInfo2 = { - id: string - sessionID: string - title?: string - metadata?: FormMetadata2 - mode: "url" - url: string -} - -export type FormCreatePayload2 = { - id?: string | null - title?: string - metadata?: FormMetadata2 - mode: "form" | "url" - fields?: Array< - FormStringField2 | FormNumberField3 | FormIntegerField3 | FormBooleanField2 | FormMultiselectField2 - > | null - url?: string | null -} - -export type FormNotFoundErrorV2 = { - _tag: "FormNotFoundError" - id: string - message: string -} - -export type FormValue2 = - | string - | number - | "NaN" - | "Infinity" - | "-Infinity" - | "Infinity" - | "-Infinity" - | "NaN" - | boolean - | Array - -export type FormAnswer2 = { - [key: string]: FormValue2 -} - -export type FormState2 = - | { - status: "pending" - } - | { - status: "answered" - answer: FormAnswer2 - } - | { - status: "cancelled" - } - -export type FormReply2 = { - answer: FormAnswer2 -} - -export type FormAlreadySettledErrorV2 = { - _tag: "FormAlreadySettledError" - id: string - message: string -} - -export type FormInvalidAnswerErrorV2 = { - _tag: "FormInvalidAnswerError" - id: string - message: string -} - -export type PermissionV2Source2 = { - type: "tool" - messageID: string - callID: string -} - -export type PermissionV2Request2 = { - id: string - sessionID: string - action: string - resources: Array - save?: Array - metadata?: { - [key: string]: unknown - } - source?: PermissionV2Source2 -} - -export type PermissionSavedInfo2 = { - id: string - projectID: string - action: string - resource: string -} - -export type PermissionNotFoundErrorV2 = { - _tag: "PermissionNotFoundError" - requestID: string - message: string -} - -export type PermissionV2Reply2 = "once" | "always" | "reject" - -export type FileSystemEntry2 = { - path: string - type: "file" | "directory" -} - -export type CommandV2Info2 = { - name: string - template: string - description?: string - agent?: string - model?: ModelRef2 - subtask?: boolean -} - -export type SkillV2Info2 = { - name: string - description?: string - slash?: boolean - autoinvoke?: boolean - location: string - content: string -} - -export type ModelsDevRefreshed2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "models-dev.refreshed" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type IntegrationUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "integration.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type IntegrationConnectionUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "integration.connection.updated" - location?: LocationRef2 - data: { - integrationID: string - } -} - -export type CatalogUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "catalog.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type AgentUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "agent.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type SnapshotFileDiffV2 = { - file?: string - patch?: string - additions: number - deletions: number - status?: "added" | "deleted" | "modified" -} - -export type PermissionActionV2 = "allow" | "deny" | "ask" - -export type PermissionRuleV2 = { - permission: string - pattern: string - action: PermissionActionV2 -} - -export type PermissionRulesetV2 = Array - export type SessionV2 = { id: string slug: string @@ -9635,7 +8046,7 @@ export type SessionV2 = { additions: number deletions: number files: number - diffs?: Array + diffs?: Array } cost?: number tokens?: { @@ -9667,7 +8078,7 @@ export type SessionV2 = { compacting?: number archived?: number } - permission?: PermissionRulesetV2 + permission?: PermissionRuleset revert?: { messageID: string partID?: string @@ -9676,74 +8087,13 @@ export type SessionV2 = { } } -export type SessionCreated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.created" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - info: SessionV2 - } -} - -export type SessionUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.updated" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - info: SessionV2 - } -} - -export type SessionDeleted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.deleted" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - info: SessionV2 - } -} - -export type JsonSchemaV2 = { - [key: string]: unknown -} - export type OutputFormatV2 = | { type: "text" } | { type: "json_schema" - schema: JsonSchemaV2 + schema: JsonSchema retryCount?: number | null | null } @@ -9758,7 +8108,7 @@ export type UserMessageV2 = { summary?: { title?: string | null body?: string | null - diffs: Array + diffs: Array } | null agent: string model: { @@ -9772,14 +8122,6 @@ export type UserMessageV2 = { } | null } -export type ProviderAuthErrorV2 = { - name: "ProviderAuthError" - data: { - providerID: string - message: string - } -} - export type UnknownError1V2 = { name: "UnknownError" data: { @@ -9797,13 +8139,6 @@ export type MessageOutputLengthErrorV2 = { | Array } -export type MessageAbortedErrorV2 = { - name: "MessageAbortedError" - data: { - message: string - } -} - export type StructuredOutputErrorV2 = { name: "StructuredOutputError" data: { @@ -9820,13 +8155,6 @@ export type ContextOverflowErrorV2 = { } } -export type ContentFilterErrorV2 = { - name: "ContentFilterError" - data: { - message: string - } -} - export type ApiErrorV2 = { name: "APIError" data: { @@ -9852,13 +8180,13 @@ export type AssistantMessageV2 = { completed?: number | null } error?: - | ProviderAuthErrorV2 + | ProviderAuthError | UnknownError1V2 | MessageOutputLengthErrorV2 - | MessageAbortedErrorV2 + | MessageAbortedError | StructuredOutputErrorV2 | ContextOverflowErrorV2 - | ContentFilterErrorV2 + | ContentFilterError | ApiErrorV2 | null parentID: string @@ -9887,46 +8215,6 @@ export type AssistantMessageV2 = { finish?: string | null } -export type MessageV2 = UserMessageV2 | AssistantMessageV2 - -export type MessageUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "message.updated" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - info: MessageV2 - } -} - -export type MessageRemoved2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "message.removed" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - messageID: string - } -} - export type TextPartV2 = { id: string sessionID: string @@ -9974,18 +8262,6 @@ export type ReasoningPartV2 = { } } -export type FilePartSourceTextV2 = { - value: string - start: number - end: number -} - -export type FileSourceV2 = { - text: FilePartSourceTextV2 - type: "file" - path: string -} - export type RangeV2 = { start: { line: number @@ -9998,7 +8274,7 @@ export type RangeV2 = { } export type SymbolSourceV2 = { - text: FilePartSourceTextV2 + text: FilePartSourceText type: "symbol" path: string range: RangeV2 @@ -10006,15 +8282,6 @@ export type SymbolSourceV2 = { kind: number } -export type ResourceSourceV2 = { - text: FilePartSourceTextV2 - type: "resource" - clientName: string - uri: string -} - -export type FilePartSourceV2 = FileSourceV2 | SymbolSourceV2 | ResourceSourceV2 - export type FilePartV2 = { id: string sessionID: string @@ -10023,15 +8290,7 @@ export type FilePartV2 = { mime: string filename?: string | null url: string - source?: FilePartSourceV2 | null -} - -export type ToolStatePendingV2 = { - status: "pending" - input: { - [key: string]: unknown - } - raw: string + source?: FilePartSource | null } export type ToolStateRunningV2 = { @@ -10081,8 +8340,6 @@ export type ToolStateErrorV2 = { } } -export type ToolStateV2 = ToolStatePendingV2 | ToolStateRunningV2 | ToolStateCompletedV2 | ToolStateErrorV2 - export type ToolPartV2 = { id: string sessionID: string @@ -10090,7 +8347,7 @@ export type ToolPartV2 = { type: "tool" callID: string tool: string - state: ToolStateV2 + state: ToolState metadata?: { [key: string]: unknown } | null @@ -10176,288 +8433,6 @@ export type CompactionPartV2 = { tail_start_id?: string | null } -export type PartV2 = - | TextPartV2 - | SubtaskPartV2 - | ReasoningPartV2 - | FilePartV2 - | ToolPartV2 - | StepStartPartV2 - | StepFinishPartV2 - | SnapshotPartV2 - | PatchPartV2 - | AgentPartV2 - | RetryPartV2 - | CompactionPartV2 - -export type MessagePartUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "message.part.updated" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - part: PartV2 - time: number - } -} - -export type MessagePartRemoved2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "message.part.removed" - durable: { - aggregateID: string - seq: number - version: number - } - location?: LocationRef2 - data: { - sessionID: string - messageID: string - partID: string - } -} - -export type SessionExecutionSettled2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.execution.settled" - location?: LocationRef2 - data: { - sessionID: string - outcome: "success" | "failure" | "interrupted" - error?: SessionErrorUnknown2 - } -} - -export type SessionTextDelta2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.text.delta" - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - textID: string - delta: string - } -} - -export type SessionReasoningDelta2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.reasoning.delta" - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - reasoningID: string - delta: string - } -} - -export type SessionToolInputDelta2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.tool.input.delta" - location?: LocationRef2 - data: { - sessionID: string - assistantMessageID: string - callID: string - delta: string - } -} - -export type SessionCompactionDelta2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "session.compaction.delta" - location?: LocationRef2 - data: { - sessionID: string - text: string - } -} - -export type FilesystemChanged2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "filesystem.changed" - location?: LocationRef2 - data: { - file: string - event: "add" | "change" | "unlink" - } -} - -export type ReferenceUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "reference.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type PermissionV2Asked2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "permission.v2.asked" - location?: LocationRef2 - data: { - id: string - sessionID: string - action: string - resources: Array - save?: Array - metadata?: { - [key: string]: unknown - } - source?: PermissionV2Source2 - } -} - -export type PermissionV2Replied2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "permission.v2.replied" - location?: LocationRef2 - data: { - sessionID: string - requestID: string - reply: PermissionV2Reply2 - } -} - -export type PluginAdded2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "plugin.added" - location?: LocationRef2 - data: { - id: string - } -} - -export type PluginUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "plugin.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type ProjectDirectoriesUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "project.directories.updated" - location?: LocationRef2 - data: { - projectID: string - } -} - -export type CommandUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "command.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type ConfigUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "config.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - -export type SkillUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "skill.updated" - location?: LocationRef2 - data: - | { - [key: string]: unknown - } - | Array -} - export type PtyV2 = { id: string title: string @@ -10469,353 +8444,6 @@ export type PtyV2 = { exitCode?: number } -export type PtyCreated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "pty.created" - location?: LocationRef2 - data: { - info: PtyV2 - } -} - -export type PtyUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "pty.updated" - location?: LocationRef2 - data: { - info: PtyV2 - } -} - -export type PtyExited2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "pty.exited" - location?: LocationRef2 - data: { - id: string - exitCode: number - } -} - -export type PtyDeleted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "pty.deleted" - location?: LocationRef2 - data: { - id: string - } -} - -export type ShellCreated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "shell.created" - location?: LocationRef2 - data: { - info: Shell1V2 - } -} - -export type ShellExited2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "shell.exited" - location?: LocationRef2 - data: { - id: string - exit?: number | "NaN" | "Infinity" | "-Infinity" - status: "running" | "exited" | "timeout" | "killed" - } -} - -export type ShellDeleted2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "shell.deleted" - location?: LocationRef2 - data: { - id: string - } -} - -export type QuestionV2Option2 = { - /** - * Display text (1-5 words, concise) - */ - label: string - /** - * Explanation of choice - */ - description: string -} - -export type QuestionV2Info2 = { - /** - * Complete question - */ - question: string - /** - * Very short label (max 30 chars) - */ - header: string - /** - * Available choices - */ - options: Array - multiple?: boolean - custom?: boolean -} - -export type QuestionV2Tool2 = { - messageID: string - callID: string -} - -export type QuestionV2Asked2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "question.v2.asked" - location?: LocationRef2 - data: { - id: string - sessionID: string - /** - * Questions to ask - */ - questions: Array - tool?: QuestionV2Tool2 - } -} - -export type QuestionV2Answer2 = Array - -export type QuestionV2Replied2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "question.v2.replied" - location?: LocationRef2 - data: { - sessionID: string - requestID: string - answers: Array - } -} - -export type QuestionV2Rejected2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "question.v2.rejected" - location?: LocationRef2 - data: { - sessionID: string - requestID: string - } -} - -export type FormMetadata1 = { - [key: string]: unknown -} - -export type FormWhen12 = { - key: string - op: "eq" | "neq" - value: string | number | "NaN" | "Infinity" | "-Infinity" | boolean -} - -export type FormStringField1 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "string" - format?: "email" | "uri" | "date" | "date-time" - minLength?: number - maxLength?: number - pattern?: string - placeholder?: string - default?: string - options?: Array - custom?: boolean -} - -export type FormNumberField12 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "number" - minimum?: number | "NaN" | "Infinity" | "-Infinity" - maximum?: number | "NaN" | "Infinity" | "-Infinity" - default?: number | "NaN" | "Infinity" | "-Infinity" -} - -export type FormIntegerField12 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "integer" - minimum?: number | "NaN" | "Infinity" | "-Infinity" - maximum?: number | "NaN" | "Infinity" | "-Infinity" - default?: number | "NaN" | "Infinity" | "-Infinity" -} - -export type FormBooleanField1 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "boolean" - default?: boolean -} - -export type FormMultiselectField1 = { - key: string - title?: string - description?: string - required?: boolean - when?: Array - type: "multiselect" - options: Array - minItems?: number - maxItems?: number - custom?: boolean - default?: Array -} - -export type FormFormInfo1 = { - id: string - sessionID: string - title?: string - metadata?: FormMetadata1 - mode: "form" - fields: Array -} - -export type FormUrlInfo1 = { - id: string - sessionID: string - title?: string - metadata?: FormMetadata1 - mode: "url" - url: string -} - -export type FormCreated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "form.created" - location?: LocationRef2 - data: { - form: FormFormInfo1 | FormUrlInfo1 - } -} - -export type FormValue12 = string | number | "NaN" | "Infinity" | "-Infinity" | boolean | Array - -export type FormAnswer1 = { - [key: string]: FormValue12 -} - -export type FormReplied2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "form.replied" - location?: LocationRef2 - data: { - id: string - sessionID: string - answer: FormAnswer1 - } -} - -export type FormCancelled2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "form.cancelled" - location?: LocationRef2 - data: { - id: string - sessionID: string - } -} - -export type TodoV2 = { - /** - * Brief description of the task - */ - content: string - /** - * Current status of the task: pending, in_progress, completed, cancelled - */ - status: string - /** - * Priority level of the task: high, medium, low - */ - priority: string -} - -export type TodoUpdated2 = { - id: string - created: number - metadata?: { - [key: string]: unknown - } - type: "todo.updated" - location?: LocationRef2 - data: { - sessionID: string - todos: Array - } -} - export type SessionStatusV2 = | { type: "idle" @@ -10845,47 +8473,2041 @@ export type SessionStatusV22 = { [key: string]: unknown } type: "session.status" - location?: LocationRef2 + location?: LocationRefV2 data: { sessionID: string status: SessionStatusV2 } } -export type SessionIdle2 = { +export type QuestionInfoV2 = { + /** + * Complete question + */ + question: string + /** + * Very short label (max 30 chars) + */ + header: string + /** + * Available choices + */ + options: Array + /** + * Allow selecting multiple choices + */ + multiple?: boolean | null + /** + * Allow typing a custom answer (default: true) + */ + custom?: boolean | null +} + +export type QuestionToolV2 = { + messageID: string + callID: string +} + +export type V2EventV2 = + | ModelsDevRefreshedV2 + | IntegrationUpdatedV2 + | IntegrationConnectionUpdatedV2 + | CatalogUpdatedV2 + | AgentUpdatedV2 + | SessionCreatedV2 + | SessionUpdatedV2 + | SessionDeleted1 + | MessageUpdatedV2 + | MessageRemovedV2 + | MessagePartUpdatedV2 + | MessagePartRemovedV2 + | SessionAgentSelectedV2 + | SessionModelSelectedV2 + | SessionMovedV2 + | SessionRenamedV2 + | SessionDeletedV2 + | SessionForkedV2 + | SessionPromptPromotedV2 + | SessionPromptAdmittedV2 + | SessionExecutionStartedV2 + | SessionExecutionSucceededV2 + | SessionExecutionFailedV2 + | SessionExecutionInterruptedV2 + | SessionInstructionsUpdatedV2 + | SessionSyntheticV2 + | SessionSkillActivatedV2 + | SessionShellStartedV2 + | SessionShellEndedV2 + | SessionStepStartedV2 + | SessionStepEndedV2 + | SessionStepFailedV2 + | SessionTextStartedV2 + | SessionTextDeltaV2 + | SessionTextEndedV2 + | SessionReasoningStartedV2 + | SessionReasoningDeltaV2 + | SessionReasoningEndedV2 + | SessionToolInputStartedV2 + | SessionToolInputDeltaV2 + | SessionToolInputEndedV2 + | SessionToolCalledV2 + | SessionToolProgressV2 + | SessionToolSuccessV2 + | SessionToolFailedV2 + | SessionRetryScheduledV2 + | SessionCompactionAdmittedV2 + | SessionCompactionStartedV2 + | SessionCompactionDeltaV2 + | SessionCompactionEndedV2 + | SessionCompactionFailedV2 + | SessionRevertStagedV2 + | SessionRevertClearedV2 + | SessionRevertCommittedV2 + | FilesystemChangedV2 + | ReferenceUpdatedV2 + | PermissionV2AskedV2 + | PermissionV2RepliedV2 + | PluginAddedV2 + | PluginUpdatedV2 + | ProjectDirectoriesUpdatedV2 + | CommandUpdatedV2 + | ConfigUpdatedV2 + | SkillUpdatedV2 + | PtyCreatedV2 + | PtyUpdatedV2 + | PtyExitedV2 + | PtyDeletedV2 + | ShellCreatedV2 + | ShellExitedV2 + | ShellDeletedV2 + | QuestionV2AskedV2 + | QuestionV2RepliedV2 + | QuestionV2RejectedV2 + | FormCreatedV2 + | FormRepliedV2 + | FormCancelledV2 + | TodoUpdatedV2 + | SessionStatusV22 + | SessionIdleV2 + | TuiPromptAppendV2 + | TuiCommandExecuteV2 + | TuiToastShowV2 + | TuiSessionSelectV2 + | InstallationUpdatedV2 + | InstallationUpdateAvailableV2 + | VcsBranchUpdatedV2 + | McpStatusChangedV2 + | PermissionAskedV2 + | PermissionRepliedV2 + | QuestionAskedV2 + | QuestionRepliedV2 + | QuestionRejectedV2 + | SessionErrorV2 + | V2EventServerConnected + +export type ProjectCopyErrorV2 = { + name: "ProjectCopyError" + data: { + message: string + forceRequired?: boolean | null + } +} + +export type LocationInfoV2 = { + directory: string + workspaceID?: string + project: { + id: string + directory: string + } +} + +export type AgentColorV2 = string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info" + +export type AgentV2InfoV2 = { + id: string + model?: ModelRef + request: ProviderRequest + system?: string + description?: string + mode: "subagent" | "primary" | "all" + hidden: boolean + color?: AgentColorV2 + steps?: number + permissions: PermissionV2Ruleset +} + +export type LocationRefV2 = { + directory: string + workspaceID?: string +} + +export type FileDiffV2 = { + path: string + status: "added" | "modified" | "deleted" + additions: number + deletions: number + patch: string +} + +export type RevertStateV2 = { + messageID: string + partID?: string + snapshot?: string + diff?: string + files?: Array +} + +export type SessionV2InfoV2 = { + id: string + parentID?: string + fork?: { + sessionID: string + messageID?: string + } + projectID: string + agent?: string + model?: ModelRef + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } + time: { + created: number + updated: number + archived?: number + } + title: string + location: LocationRefV2 + subpath?: string + revert?: RevertStateV2 +} + +export type PromptBase64V2 = string + +export type SessionInputAdmittedV2 = { + admittedSeq: number + id: string + sessionID: string + prompt: Prompt + delivery: "steer" | "queue" + timeCreated: number + promotedSeq?: number +} + +export type SessionInputCompactionV2 = { + type: "compaction" + admittedSeq: number + id: string + sessionID: string + timeCreated: number + handledSeq?: number +} + +export type SessionMessageAgentSelectedV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + type: "agent-switched" + agent: string +} + +export type SessionMessageModelSelectedV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + type: "model-switched" + model: ModelRef + previous?: ModelRef +} + +export type SessionMessageUserV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + text: string + files?: Array + agents?: Array + type: "user" +} + +export type SessionMessageSyntheticV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + sessionID: string + text: string + description?: string + type: "synthetic" +} + +export type SessionMessageSystemV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + type: "system" + text: string +} + +export type SessionMessageSkillV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } + type: "skill" + name: string + text: string +} + +export type SessionMessageShellV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + completed?: number + } + type: "shell" + shell: ShellV2 + output?: { + output: string + cursor: number + size: number + truncated: boolean + } +} + +export type SessionMessageAssistantRetryV2 = { + attempt: number + at: number + error: SessionStructuredError +} + +export type SessionMessageAssistantV2 = { + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + completed?: number + } + type: "assistant" + agent: string + model: ModelRef + content: Array + snapshot?: { + start?: string + end?: string + files?: Array + } + finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" + cost?: number + tokens?: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } + error?: SessionStructuredError + retry?: SessionMessageAssistantRetryV2 +} + +export type SessionMessageCompactionV2 = { + type: "compaction" + status: "queued" | "running" | "completed" | "failed" + reason: "auto" | "manual" + summary: string + recent: string + id: string + metadata?: { + [key: string]: unknown + } + time: { + created: number + } +} + +/** + * Instruction entry key (lowercase alphanumerics plus . _ -) + */ +export type InstructionEntryKeyV2 = string + +export type SessionAgentSelectedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.agent.selected" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + agent: string + } +} + +export type SessionModelSelectedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.model.selected" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + model: ModelRef + } +} + +export type SessionMovedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.moved" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + location: LocationRefV2 + subpath?: string + } +} + +export type SessionRenamedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.renamed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + title: string + } +} + +export type SessionDeletedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.deleted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + } +} + +export type SessionForkedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.forked" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + parentID: string + from?: string + } +} + +export type SessionPromptPromotedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.prompt.promoted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + inputID: string + } +} + +export type SessionPromptAdmittedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.prompt.admitted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + inputID: string + prompt: Prompt + delivery: "steer" | "queue" + } +} + +export type SessionExecutionStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + } +} + +export type SessionExecutionSucceededV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.succeeded" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + } +} + +export type SessionExecutionFailedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.failed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + error: SessionStructuredError + } +} + +export type SessionExecutionInterruptedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.execution.interrupted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + reason: "user" | "shutdown" | "superseded" + } +} + +export type SessionInstructionsUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.instructions.updated" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + text: string + } +} + +export type SessionSyntheticV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.synthetic" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + text: string + description?: string + metadata?: { + [key: string]: unknown + } + } +} + +export type SessionSkillActivatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.skill.activated" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + name: string + text: string + } +} + +export type SessionShellStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.shell.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + shell: ShellV2 + } +} + +export type SessionShellEndedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.shell.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + shell: ShellV2 + output: { + output: string + cursor: number + size: number + truncated: boolean + } + } +} + +export type SessionStepStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.step.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + agent: string + model: ModelRef + snapshot?: string + } +} + +export type SessionStepEndedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.step.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown" + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } + snapshot?: string + files?: Array + } +} + +export type SessionStepFailedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.step.failed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + error: SessionStructuredError + } +} + +export type SessionTextStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.text.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + ordinal: number + } +} + +export type SessionTextEndedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.text.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + ordinal: number + text: string + } +} + +export type SessionMessageProviderState3 = { + [key: string]: unknown +} + +export type SessionReasoningStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.reasoning.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + ordinal: number + state?: SessionMessageProviderState3 + } +} + +export type SessionMessageProviderState4 = { + [key: string]: unknown +} + +export type SessionReasoningEndedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.reasoning.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + ordinal: number + text: string + state?: SessionMessageProviderState4 + } +} + +export type SessionToolInputStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.input.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + name: string + } +} + +export type SessionToolInputEndedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.input.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + text: string + } +} + +export type SessionMessageProviderState5 = { + [key: string]: unknown +} + +export type SessionToolCalledV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.called" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + input: { + [key: string]: unknown + } + executed: boolean + state?: SessionMessageProviderState5 + } +} + +export type SessionToolProgressV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.progress" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + structured: { + [key: string]: unknown + } + content: Array + } +} + +export type SessionMessageProviderState6 = { + [key: string]: unknown +} + +export type SessionToolSuccessV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.success" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + structured: { + [key: string]: unknown + } + content: Array + outputPaths?: Array + result?: unknown + executed: boolean + resultState?: SessionMessageProviderState6 + } +} + +export type SessionMessageProviderState7 = { + [key: string]: unknown +} + +export type SessionToolFailedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.failed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + error: SessionStructuredError + result?: unknown + executed: boolean + resultState?: SessionMessageProviderState7 + } +} + +export type SessionRetryScheduledV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.retry.scheduled" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + attempt: number + at: number + error: SessionStructuredError + } +} + +export type SessionCompactionAdmittedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.admitted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + inputID: string + } +} + +export type SessionCompactionStartedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.started" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + reason: "auto" | "manual" + } +} + +export type SessionCompactionEndedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.ended" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + reason: "auto" | "manual" + text: string + recent: string + } +} + +export type SessionCompactionFailedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.failed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + } +} + +export type SessionRevertStagedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.revert.staged" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + revert: RevertStateV2 + } +} + +export type SessionRevertClearedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.revert.cleared" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + } +} + +export type SessionRevertCommittedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.revert.committed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + to: string + } +} + +/** + * Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq. + */ +export type EventLogSyncedV2 = { + type: "log.synced" + aggregateID: string + seq?: number +} + +export type ProjectTimeV2 = { + created: number + updated: number + initialized?: number +} + +export type FormWhenV2 = { + key: string + op: "eq" | "neq" + value: string | number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" | boolean +} + +export type FormStringFieldV2 = { + key: string + title?: string + description?: string + required?: boolean + when?: Array + type: "string" + format?: "email" | "uri" | "date" | "date-time" + minLength?: number + maxLength?: number + pattern?: string + placeholder?: string + default?: string + options?: Array + custom?: boolean +} + +export type FormMultiselectFieldV2 = { + key: string + title?: string + description?: string + required?: boolean + when?: Array + type: "multiselect" + options: Array + minItems?: number + maxItems?: number + custom?: boolean + default?: Array +} + +export type FormFormInfoV2 = { + id: string + sessionID: string + title?: string + metadata?: FormMetadata + mode: "form" + fields: Array +} + +export type FormUrlInfoV2 = { + id: string + sessionID: string + title?: string + metadata?: FormMetadata + mode: "url" + url: string +} + +export type FormCreatePayloadV2 = { + id?: string | null + title?: string + metadata?: FormMetadata + mode: "form" | "url" + fields?: Array< + FormStringFieldV2 | FormNumberField | FormIntegerField | FormBooleanField | FormMultiselectFieldV2 + > | null + url?: string | null +} + +export type FormValueV2 = + | string + | number + | "NaN" + | "Infinity" + | "-Infinity" + | "Infinity" + | "-Infinity" + | "NaN" + | boolean + | Array + +export type PermissionV2SourceV2 = { + type: "tool" + messageID: string + callID: string +} + +export type PermissionV2RequestV2 = { + id: string + sessionID: string + action: string + resources: Array + save?: Array + metadata?: { + [key: string]: unknown + } + source?: PermissionV2SourceV2 +} + +export type ModelsDevRefreshedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "models-dev.refreshed" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type IntegrationUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "integration.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type IntegrationConnectionUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "integration.connection.updated" + location?: LocationRefV2 + data: { + integrationID: string + } +} + +export type CatalogUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "catalog.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type AgentUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "agent.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type SessionCreatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.created" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + info: SessionV2 + } +} + +export type SessionUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.updated" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + info: SessionV2 + } +} + +export type SessionDeleted1 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.deleted" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + info: SessionV2 + } +} + +export type MessageUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "message.updated" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + info: Message + } +} + +export type MessageRemovedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "message.removed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + messageID: string + } +} + +export type MessagePartUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "message.part.updated" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + part: Part + time: number + } +} + +export type MessagePartRemovedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "message.part.removed" + durable: { + aggregateID: string + seq: number + version: number + } + location?: LocationRefV2 + data: { + sessionID: string + messageID: string + partID: string + } +} + +export type SessionTextDeltaV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.text.delta" + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + ordinal: number + delta: string + } +} + +export type SessionReasoningDeltaV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.reasoning.delta" + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + ordinal: number + delta: string + } +} + +export type SessionToolInputDeltaV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.tool.input.delta" + location?: LocationRefV2 + data: { + sessionID: string + assistantMessageID: string + callID: string + delta: string + } +} + +export type SessionCompactionDeltaV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "session.compaction.delta" + location?: LocationRefV2 + data: { + sessionID: string + text: string + } +} + +export type FilesystemChangedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "filesystem.changed" + location?: LocationRefV2 + data: { + file: string + event: "add" | "change" | "unlink" + } +} + +export type ReferenceUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "reference.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type PermissionV2AskedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "permission.v2.asked" + location?: LocationRefV2 + data: { + id: string + sessionID: string + action: string + resources: Array + save?: Array + metadata?: { + [key: string]: unknown + } + source?: PermissionV2SourceV2 + } +} + +export type PermissionV2RepliedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "permission.v2.replied" + location?: LocationRefV2 + data: { + sessionID: string + requestID: string + reply: PermissionV2Reply + } +} + +export type PluginAddedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "plugin.added" + location?: LocationRefV2 + data: { + id: string + } +} + +export type PluginUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "plugin.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type ProjectDirectoriesUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "project.directories.updated" + location?: LocationRefV2 + data: { + projectID: string + } +} + +export type CommandUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "command.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type ConfigUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "config.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type SkillUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "skill.updated" + location?: LocationRefV2 + data: + | { + [key: string]: unknown + } + | Array +} + +export type PtyCreatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "pty.created" + location?: LocationRefV2 + data: { + info: PtyV2 + } +} + +export type PtyUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "pty.updated" + location?: LocationRefV2 + data: { + info: PtyV2 + } +} + +export type PtyExitedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "pty.exited" + location?: LocationRefV2 + data: { + id: string + exitCode: number + } +} + +export type PtyDeletedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "pty.deleted" + location?: LocationRefV2 + data: { + id: string + } +} + +export type ShellCreatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "shell.created" + location?: LocationRefV2 + data: { + info: ShellV2 + } +} + +export type ShellExitedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "shell.exited" + location?: LocationRefV2 + data: { + id: string + exit?: number | "NaN" | "Infinity" | "-Infinity" + status: "running" | "exited" | "timeout" | "killed" + } +} + +export type ShellDeletedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "shell.deleted" + location?: LocationRefV2 + data: { + id: string + } +} + +export type QuestionV2AskedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "question.v2.asked" + location?: LocationRefV2 + data: { + id: string + sessionID: string + /** + * Questions to ask + */ + questions: Array + tool?: QuestionV2Tool + } +} + +export type QuestionV2RepliedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "question.v2.replied" + location?: LocationRefV2 + data: { + sessionID: string + requestID: string + answers: Array + } +} + +export type QuestionV2RejectedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "question.v2.rejected" + location?: LocationRefV2 + data: { + sessionID: string + requestID: string + } +} + +export type FormMetadata1 = { + [key: string]: unknown +} + +export type FormStringField1 = { + key: string + title?: string + description?: string + required?: boolean + when?: Array + type: "string" + format?: "email" | "uri" | "date" | "date-time" + minLength?: number + maxLength?: number + pattern?: string + placeholder?: string + default?: string + options?: Array + custom?: boolean +} + +export type FormBooleanField1 = { + key: string + title?: string + description?: string + required?: boolean + when?: Array + type: "boolean" + default?: boolean +} + +export type FormMultiselectField1 = { + key: string + title?: string + description?: string + required?: boolean + when?: Array + type: "multiselect" + options: Array + minItems?: number + maxItems?: number + custom?: boolean + default?: Array +} + +export type FormFormInfo1 = { + id: string + sessionID: string + title?: string + metadata?: FormMetadata1 + mode: "form" + fields: Array +} + +export type FormUrlInfo1 = { + id: string + sessionID: string + title?: string + metadata?: FormMetadata1 + mode: "url" + url: string +} + +export type FormCreatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "form.created" + location?: LocationRefV2 + data: { + form: FormFormInfo1 | FormUrlInfo1 + } +} + +export type FormAnswer1 = { + [key: string]: FormValue1 +} + +export type FormRepliedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "form.replied" + location?: LocationRefV2 + data: { + id: string + sessionID: string + answer: FormAnswer1 + } +} + +export type FormCancelledV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "form.cancelled" + location?: LocationRefV2 + data: { + id: string + sessionID: string + } +} + +export type TodoUpdatedV2 = { + id: string + created: number + metadata?: { + [key: string]: unknown + } + type: "todo.updated" + location?: LocationRefV2 + data: { + sessionID: string + todos: Array + } +} + +export type SessionIdleV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "session.idle" - location?: LocationRef2 + location?: LocationRefV2 data: { sessionID: string } } -export type TuiPromptAppend2 = { +export type TuiPromptAppendV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "tui.prompt.append" - location?: LocationRef2 + location?: LocationRefV2 data: { text: string } } -export type TuiCommandExecute2 = { +export type TuiCommandExecuteV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "tui.command.execute" - location?: LocationRef2 + location?: LocationRefV2 data: { command: | "session.list" @@ -10909,14 +10531,14 @@ export type TuiCommandExecute2 = { } } -export type TuiToastShow2 = { +export type TuiToastShowV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "tui.toast.show" - location?: LocationRef2 + location?: LocationRefV2 data: { title?: string message: string @@ -10925,14 +10547,14 @@ export type TuiToastShow2 = { } } -export type TuiSessionSelect2 = { +export type TuiSessionSelectV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "tui.session.select" - location?: LocationRef2 + location?: LocationRefV2 data: { /** * Session ID to navigate to @@ -10941,66 +10563,66 @@ export type TuiSessionSelect2 = { } } -export type InstallationUpdated2 = { +export type InstallationUpdatedV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "installation.updated" - location?: LocationRef2 + location?: LocationRefV2 data: { version: string } } -export type InstallationUpdateAvailable2 = { +export type InstallationUpdateAvailableV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "installation.update-available" - location?: LocationRef2 + location?: LocationRefV2 data: { version: string } } -export type VcsBranchUpdated2 = { +export type VcsBranchUpdatedV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "vcs.branch.updated" - location?: LocationRef2 + location?: LocationRefV2 data: { branch?: string } } -export type McpStatusChanged2 = { +export type McpStatusChangedV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "mcp.status.changed" - location?: LocationRef2 + location?: LocationRefV2 data: { server: string } } -export type PermissionAsked2 = { +export type PermissionAskedV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "permission.asked" - location?: LocationRef2 + location?: LocationRefV2 data: { id: string sessionID: string @@ -11017,14 +10639,14 @@ export type PermissionAsked2 = { } } -export type PermissionReplied2 = { +export type PermissionRepliedV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "permission.replied" - location?: LocationRef2 + location?: LocationRefV2 data: { sessionID: string requestID: string @@ -11032,53 +10654,14 @@ export type PermissionReplied2 = { } } -export type QuestionOptionV2 = { - /** - * Display text (1-5 words, concise) - */ - label: string - /** - * Explanation of choice - */ - description: string -} - -export type QuestionInfoV2 = { - /** - * Complete question - */ - question: string - /** - * Very short label (max 30 chars) - */ - header: string - /** - * Available choices - */ - options: Array - /** - * Allow selecting multiple choices - */ - multiple?: boolean | null - /** - * Allow typing a custom answer (default: true) - */ - custom?: boolean | null -} - -export type QuestionToolV2 = { - messageID: string - callID: string -} - -export type QuestionAsked2 = { +export type QuestionAskedV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "question.asked" - location?: LocationRef2 + location?: LocationRefV2 data: { id: string sessionID: string @@ -11090,8 +10673,6 @@ export type QuestionAsked2 = { } } -export type QuestionAnswerV2 = Array - export type QuestionRepliedV2 = { id: string created: number @@ -11099,11 +10680,11 @@ export type QuestionRepliedV2 = { [key: string]: unknown } type: "question.replied" - location?: LocationRef2 + location?: LocationRefV2 data: { sessionID: string requestID: string - answers: Array + answers: Array } } @@ -11114,31 +10695,31 @@ export type QuestionRejectedV2 = { [key: string]: unknown } type: "question.rejected" - location?: LocationRef2 + location?: LocationRefV2 data: { sessionID: string requestID: string } } -export type SessionError2 = { +export type SessionErrorV2 = { id: string created: number metadata?: { [key: string]: unknown } type: "session.error" - location?: LocationRef2 + location?: LocationRefV2 data: { sessionID?: string | null error?: - | ProviderAuthErrorV2 + | ProviderAuthError | UnknownError1V2 | MessageOutputLengthErrorV2 - | MessageAbortedErrorV2 + | MessageAbortedError | StructuredOutputErrorV2 | ContextOverflowErrorV2 - | ContentFilterErrorV2 + | ContentFilterError | ApiErrorV2 | null } @@ -11149,7 +10730,7 @@ export type V2EventServerConnected = { metadata?: { [key: string]: unknown } | null - location?: LocationRef2 | null + location?: LocationRefV2 | null type: "server.connected" data: | { @@ -11158,179 +10739,19 @@ export type V2EventServerConnected = { | Array } -export type V2EventV2 = - | ModelsDevRefreshed2 - | IntegrationUpdated2 - | IntegrationConnectionUpdated2 - | CatalogUpdated2 - | AgentUpdated2 - | SessionCreated2 - | SessionUpdated2 - | SessionDeleted2 - | MessageUpdated2 - | MessageRemoved2 - | MessagePartUpdated2 - | MessagePartRemoved2 - | SessionAgentSelected2 - | SessionModelSelected2 - | SessionMoved2 - | SessionRenamed2 - | SessionForked2 - | SessionPromptPromoted2 - | SessionPromptAdmitted2 - | SessionExecutionSettled2 - | SessionInstructionsUpdated2 - | SessionSynthetic2 - | SessionSkillActivated2 - | SessionShellStarted2 - | SessionShellEnded2 - | SessionStepStarted2 - | SessionStepEnded2 - | SessionStepFailed2 - | SessionTextStarted2 - | SessionTextDelta2 - | SessionTextEnded2 - | SessionReasoningStarted2 - | SessionReasoningDelta2 - | SessionReasoningEnded2 - | SessionToolInputStarted2 - | SessionToolInputDelta2 - | SessionToolInputEnded2 - | SessionToolCalled2 - | SessionToolProgress2 - | SessionToolSuccess2 - | SessionToolFailed2 - | SessionRetried2 - | SessionCompactionStarted2 - | SessionCompactionDelta2 - | SessionCompactionEnded2 - | SessionRevertStaged2 - | SessionRevertCleared2 - | SessionRevertCommitted2 - | FilesystemChanged2 - | ReferenceUpdated2 - | PermissionV2Asked2 - | PermissionV2Replied2 - | PluginAdded2 - | PluginUpdated2 - | ProjectDirectoriesUpdated2 - | CommandUpdated2 - | ConfigUpdated2 - | SkillUpdated2 - | PtyCreated2 - | PtyUpdated2 - | PtyExited2 - | PtyDeleted2 - | ShellCreated2 - | ShellExited2 - | ShellDeleted2 - | QuestionV2Asked2 - | QuestionV2Replied2 - | QuestionV2Rejected2 - | FormCreated2 - | FormReplied2 - | FormCancelled2 - | TodoUpdated2 - | SessionStatusV22 - | SessionIdle2 - | TuiPromptAppend2 - | TuiCommandExecute2 - | TuiToastShow2 - | TuiSessionSelect2 - | InstallationUpdated2 - | InstallationUpdateAvailable2 - | VcsBranchUpdated2 - | McpStatusChanged2 - | PermissionAsked2 - | PermissionReplied2 - | QuestionAsked2 - | QuestionRepliedV2 - | QuestionRejectedV2 - | SessionError2 - | V2EventServerConnected - -export type V2EventStreamV2 = string - -export type PtyNotFoundErrorV2 = { - _tag: "PtyNotFoundError" - ptyID: string - message: string -} - -export type PtyTicketConnectToken2 = { +export type PtyTicketConnectTokenV2 = { ticket: string expires_in: number } -export type ForbiddenErrorV2 = { - _tag: "ForbiddenError" - message: string -} - -export type ShellNotFoundErrorV2 = { - _tag: "ShellNotFoundError" - id: string - message: string -} - -export type QuestionV2Request2 = { +export type QuestionV2RequestV2 = { id: string sessionID: string /** * Questions to ask */ - questions: Array - tool?: QuestionV2Tool2 -} - -export type QuestionV2Reply2 = { - /** - * User answers in order of questions (each answer is an array of selected labels) - */ - answers: Array -} - -export type QuestionNotFoundErrorV2 = { - _tag: "QuestionNotFoundError" - requestID: string - message: string -} - -export type ReferenceLocalSource2 = { - type: "local" - path: string - description?: string - hidden?: boolean -} - -export type ReferenceGitSource2 = { - type: "git" - repository: string - branch?: string - description?: string - hidden?: boolean -} - -export type ReferenceSource2 = ReferenceLocalSource2 | ReferenceGitSource2 - -export type ReferenceInfo2 = { - name: string - path: string - description?: string - hidden?: boolean - source: ReferenceSource2 -} - -export type ProjectCopyCopy2 = { - directory: string -} - -export type ProjectCopyErrorV2 = { - name: "ProjectCopyError" - data: { - message: string - forceRequired?: boolean | null - } + questions: Array + tool?: QuestionV2Tool } export type VcsFileStatusV2 = { @@ -11340,8 +10761,6 @@ export type VcsFileStatusV2 = { status: "added" | "deleted" | "modified" } -export type VcsMode2 = "working" | "branch" - export type AuthRemoveData = { body?: never path: { @@ -15490,7 +14909,7 @@ export type V2HealthGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2HealthGetError = V2HealthGetErrors[keyof V2HealthGetErrors] @@ -15528,7 +14947,7 @@ export type V2LocationGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2LocationGetError = V2LocationGetErrors[keyof V2LocationGetErrors] @@ -15537,7 +14956,7 @@ export type V2LocationGetResponses = { /** * Location.Info */ - 200: LocationInfo2 + 200: LocationInfoV2 } export type V2LocationGetResponse = V2LocationGetResponses[keyof V2LocationGetResponses] @@ -15562,7 +14981,7 @@ export type V2AgentListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2AgentListError = V2AgentListErrors[keyof V2AgentListErrors] @@ -15572,8 +14991,8 @@ export type V2AgentListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -15599,7 +15018,7 @@ export type V2PluginListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2PluginListError = V2PluginListErrors[keyof V2PluginListErrors] @@ -15609,8 +15028,8 @@ export type V2PluginListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -15643,11 +15062,11 @@ export type V2SessionListErrors = { /** * InvalidCursorError | InvalidRequestError */ - 400: InvalidCursorErrorV2 | InvalidRequestError1 | InvalidRequestErrorV2 + 400: InvalidCursorError | InvalidRequestError1 | InvalidRequestErrorV2 /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2SessionListError = V2SessionListErrors[keyof V2SessionListErrors] @@ -15665,8 +15084,8 @@ export type V2SessionCreateData = { body: { id?: string | null agent?: string | null - model?: ModelRef2 | null - location?: LocationRef2 | null + model?: ModelRef | null + location?: LocationRefV2 | null } path?: never query?: never @@ -15681,7 +15100,7 @@ export type V2SessionCreateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2SessionCreateError = V2SessionCreateErrors[keyof V2SessionCreateErrors] @@ -15691,7 +15110,7 @@ export type V2SessionCreateResponses = { * Success */ 200: { - data: SessionV2Info2 + data: SessionV2InfoV2 } } @@ -15712,7 +15131,7 @@ export type V2SessionActiveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2SessionActiveError = V2SessionActiveErrors[keyof V2SessionActiveErrors] @@ -15723,13 +15142,48 @@ export type V2SessionActiveResponses = { */ 200: { data: { - [key: string]: unknown | SessionActiveV2 + [key: string]: unknown | SessionActive } } } export type V2SessionActiveResponse = V2SessionActiveResponses[keyof V2SessionActiveResponses] +export type V2SessionRemoveData = { + body?: never + path: { + sessionID: string + } + query?: never + url: "/api/session/{sessionID}" +} + +export type V2SessionRemoveErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestErrorV2 + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * SessionNotFoundError + */ + 404: SessionNotFoundError +} + +export type V2SessionRemoveError = V2SessionRemoveErrors[keyof V2SessionRemoveErrors] + +export type V2SessionRemoveResponses = { + /** + * + */ + 204: void +} + +export type V2SessionRemoveResponse = V2SessionRemoveResponses[keyof V2SessionRemoveResponses] + export type V2SessionGetData = { body?: never path: { @@ -15747,11 +15201,11 @@ export type V2SessionGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionGetError = V2SessionGetErrors[keyof V2SessionGetErrors] @@ -15761,7 +15215,7 @@ export type V2SessionGetResponses = { * Success */ 200: { - data: SessionV2Info2 + data: SessionV2InfoV2 } } @@ -15786,11 +15240,11 @@ export type V2SessionForkErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | MessageNotFoundError */ - 404: MessageNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: MessageNotFoundError | SessionNotFoundError } export type V2SessionForkError = V2SessionForkErrors[keyof V2SessionForkErrors] @@ -15800,7 +15254,7 @@ export type V2SessionForkResponses = { * Success */ 200: { - data: SessionV2Info2 + data: SessionV2InfoV2 } } @@ -15825,11 +15279,11 @@ export type V2SessionSwitchAgentErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionSwitchAgentError = V2SessionSwitchAgentErrors[keyof V2SessionSwitchAgentErrors] @@ -15845,7 +15299,7 @@ export type V2SessionSwitchAgentResponse = V2SessionSwitchAgentResponses[keyof V export type V2SessionSwitchModelData = { body: { - model: ModelRef2 + model: ModelRef } path: { sessionID: string @@ -15862,11 +15316,11 @@ export type V2SessionSwitchModelErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionSwitchModelError = V2SessionSwitchModelErrors[keyof V2SessionSwitchModelErrors] @@ -15899,11 +15353,11 @@ export type V2SessionRenameErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionRenameError = V2SessionRenameErrors[keyof V2SessionRenameErrors] @@ -15920,7 +15374,7 @@ export type V2SessionRenameResponse = V2SessionRenameResponses[keyof V2SessionRe export type V2SessionPromptData = { body: { id?: string | null - prompt: PromptInputV2 + prompt: PromptInput delivery?: "steer" | "queue" | null resume?: boolean | null } @@ -15939,11 +15393,11 @@ export type V2SessionPromptErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * ConflictError */ @@ -15957,7 +15411,7 @@ export type V2SessionPromptResponses = { * Success */ 200: { - data: SessionInputAdmitted2 + data: SessionInputAdmittedV2 } } @@ -15969,9 +15423,9 @@ export type V2SessionCommandData = { command: string arguments?: string | null agent?: string | null - model?: ModelRef2 | null - files?: Array - agents?: Array + model?: ModelRef | null + files?: Array + agents?: Array delivery?: "steer" | "queue" | null resume?: boolean | null } @@ -15990,11 +15444,11 @@ export type V2SessionCommandErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | CommandNotFoundError */ - 404: CommandNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: CommandNotFoundError | SessionNotFoundError /** * ConflictError */ @@ -16002,7 +15456,7 @@ export type V2SessionCommandErrors = { /** * CommandEvaluationError */ - 500: CommandEvaluationErrorV2 + 500: CommandEvaluationError } export type V2SessionCommandError = V2SessionCommandErrors[keyof V2SessionCommandErrors] @@ -16012,7 +15466,7 @@ export type V2SessionCommandResponses = { * Success */ 200: { - data: SessionInputAdmitted2 + data: SessionInputAdmittedV2 } } @@ -16039,11 +15493,11 @@ export type V2SessionSkillErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | SkillNotFoundError */ - 404: SkillNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: SkillNotFoundError | SessionNotFoundError } export type V2SessionSkillError = V2SessionSkillErrors[keyof V2SessionSkillErrors] @@ -16080,11 +15534,11 @@ export type V2SessionSyntheticErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionSyntheticError = V2SessionSyntheticErrors[keyof V2SessionSyntheticErrors] @@ -16118,11 +15572,11 @@ export type V2SessionShellErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionShellError = V2SessionShellErrors[keyof V2SessionShellErrors] @@ -16137,7 +15591,9 @@ export type V2SessionShellResponses = { export type V2SessionShellResponse = V2SessionShellResponses[keyof V2SessionShellResponses] export type V2SessionCompactData = { - body?: never + body: { + id?: string | null + } path: { sessionID: string } @@ -16153,32 +15609,26 @@ export type V2SessionCompactErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** - * SessionBusyError + * ConflictError */ - 409: SessionBusyErrorV2 - /** - * UnknownError - */ - 500: UnknownErrorV2 - /** - * ServiceUnavailableError - */ - 503: ServiceUnavailableErrorV2 + 409: ConflictErrorV2 } export type V2SessionCompactError = V2SessionCompactErrors[keyof V2SessionCompactErrors] export type V2SessionCompactResponses = { /** - * + * Success */ - 204: void + 200: { + data: SessionInputCompactionV2 + } } export type V2SessionCompactResponse = V2SessionCompactResponses[keyof V2SessionCompactResponses] @@ -16200,11 +15650,11 @@ export type V2SessionWaitErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * ServiceUnavailableError */ @@ -16242,15 +15692,15 @@ export type V2SessionRevertStageErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * MessageNotFoundError | SessionNotFoundError */ - 404: MessageNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: MessageNotFoundError | SessionNotFoundError /** * SessionBusyError */ - 409: SessionBusyErrorV2 + 409: SessionBusyError /** * UnknownError */ @@ -16264,7 +15714,7 @@ export type V2SessionRevertStageResponses = { * Success */ 200: { - data: RevertState2 + data: RevertStateV2 } } @@ -16287,15 +15737,15 @@ export type V2SessionRevertClearErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * SessionBusyError */ - 409: SessionBusyErrorV2 + 409: SessionBusyError /** * UnknownError */ @@ -16330,15 +15780,15 @@ export type V2SessionRevertCommitErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * SessionBusyError */ - 409: SessionBusyErrorV2 + 409: SessionBusyError } export type V2SessionRevertCommitError = V2SessionRevertCommitErrors[keyof V2SessionRevertCommitErrors] @@ -16369,11 +15819,11 @@ export type V2SessionContextErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * UnknownError */ @@ -16387,7 +15837,7 @@ export type V2SessionContextResponses = { * Success */ 200: { - data: Array + data: Array } } @@ -16410,11 +15860,11 @@ export type V2SessionInstructionsEntryListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionInstructionsEntryListError = @@ -16425,7 +15875,7 @@ export type V2SessionInstructionsEntryListResponses = { * Success */ 200: { - data: Array + data: Array } } @@ -16436,7 +15886,7 @@ export type V2SessionInstructionsEntryRemoveData = { body?: never path: { sessionID: string - key: InstructionEntryKey2 + key: InstructionEntryKeyV2 } query?: never url: "/api/session/{sessionID}/instructions/entries/{key}" @@ -16450,11 +15900,11 @@ export type V2SessionInstructionsEntryRemoveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionInstructionsEntryRemoveError = @@ -16476,7 +15926,7 @@ export type V2SessionInstructionsEntryPutData = { } path: { sessionID: string - key: InstructionEntryKey2 + key: InstructionEntryKeyV2 } query?: never url: "/api/session/{sessionID}/instructions/entries/{key}" @@ -16490,11 +15940,11 @@ export type V2SessionInstructionsEntryPutErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionInstructionsEntryPutError = @@ -16530,11 +15980,11 @@ export type V2SessionLogErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionLogError = V2SessionLogErrors[keyof V2SessionLogErrors] @@ -16546,7 +15996,7 @@ export type V2SessionLogResponses = { 200: { id: string | null event: string - data: SessionLogItemStreamV2 + data: SessionLogItemStream } } @@ -16569,11 +16019,11 @@ export type V2SessionInterruptErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionInterruptError = V2SessionInterruptErrors[keyof V2SessionInterruptErrors] @@ -16604,11 +16054,11 @@ export type V2SessionBackgroundErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionBackgroundError = V2SessionBackgroundErrors[keyof V2SessionBackgroundErrors] @@ -16640,11 +16090,11 @@ export type V2SessionMessageErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | MessageNotFoundError */ - 404: MessageNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: MessageNotFoundError | SessionNotFoundError } export type V2SessionMessageError = V2SessionMessageErrors[keyof V2SessionMessageErrors] @@ -16654,7 +16104,7 @@ export type V2SessionMessageResponses = { * Success */ 200: { - data: SessionMessage2 + data: SessionMessage } } @@ -16683,15 +16133,15 @@ export type V2SessionMessagesErrors = { /** * InvalidCursorError | InvalidRequestError */ - 400: InvalidCursorErrorV2 | InvalidRequestErrorV2 + 400: InvalidCursorError | InvalidRequestErrorV2 /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * UnknownError */ @@ -16729,7 +16179,7 @@ export type V2ModelListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ServiceUnavailableError */ @@ -16743,8 +16193,8 @@ export type V2ModelListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -16770,7 +16220,7 @@ export type V2ModelDefaultErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ServiceUnavailableError */ @@ -16784,8 +16234,8 @@ export type V2ModelDefaultResponses = { * Success */ 200: { - location: LocationInfo2 - data: ModelV2Info2 | null + location: LocationInfoV2 + data: ModelV2Info | null } } @@ -16794,7 +16244,7 @@ export type V2ModelDefaultResponse = V2ModelDefaultResponses[keyof V2ModelDefaul export type V2GenerateTextData = { body: { prompt: string - model?: ModelRef2 | null + model?: ModelRef | null } path?: never query?: { @@ -16814,7 +16264,7 @@ export type V2GenerateTextErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ServiceUnavailableError */ @@ -16827,7 +16277,7 @@ export type V2GenerateTextResponses = { /** * GenerateTextResponse */ - 200: GenerateTextResponseV2 + 200: GenerateTextResponse } export type V2GenerateTextResponse = V2GenerateTextResponses[keyof V2GenerateTextResponses] @@ -16852,7 +16302,7 @@ export type V2ProviderListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ServiceUnavailableError */ @@ -16866,8 +16316,8 @@ export type V2ProviderListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -16895,11 +16345,11 @@ export type V2ProviderGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ProviderNotFoundError */ - 404: ProviderNotFoundErrorV2 + 404: ProviderNotFoundError /** * ServiceUnavailableError */ @@ -16913,8 +16363,8 @@ export type V2ProviderGetResponses = { * Success */ 200: { - location: LocationInfo2 - data: ProviderV2Info2 + location: LocationInfoV2 + data: ProviderV2Info } } @@ -16940,7 +16390,7 @@ export type V2IntegrationListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationListError = V2IntegrationListErrors[keyof V2IntegrationListErrors] @@ -16950,8 +16400,8 @@ export type V2IntegrationListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -16979,7 +16429,7 @@ export type V2IntegrationGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationGetError = V2IntegrationGetErrors[keyof V2IntegrationGetErrors] @@ -16989,8 +16439,8 @@ export type V2IntegrationGetResponses = { * Success */ 200: { - location: LocationInfo2 - data: IntegrationInfo2 | null + location: LocationInfoV2 + data: IntegrationInfo | null } } @@ -17021,7 +16471,7 @@ export type V2IntegrationConnectKeyErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationConnectKeyError = V2IntegrationConnectKeyErrors[keyof V2IntegrationConnectKeyErrors] @@ -17063,7 +16513,7 @@ export type V2IntegrationConnectOauthErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationConnectOauthError = V2IntegrationConnectOauthErrors[keyof V2IntegrationConnectOauthErrors] @@ -17073,8 +16523,8 @@ export type V2IntegrationConnectOauthResponses = { * Success */ 200: { - location: LocationInfo2 - data: IntegrationAttempt2 + location: LocationInfoV2 + data: IntegrationAttempt } } @@ -17103,7 +16553,7 @@ export type V2IntegrationAttemptCancelErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationAttemptCancelError = V2IntegrationAttemptCancelErrors[keyof V2IntegrationAttemptCancelErrors] @@ -17140,7 +16590,7 @@ export type V2IntegrationAttemptStatusErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationAttemptStatusError = V2IntegrationAttemptStatusErrors[keyof V2IntegrationAttemptStatusErrors] @@ -17150,8 +16600,8 @@ export type V2IntegrationAttemptStatusResponses = { * Success */ 200: { - location: LocationInfo2 - data: IntegrationAttemptStatus2 + location: LocationInfoV2 + data: IntegrationAttemptStatus } } @@ -17182,7 +16632,7 @@ export type V2IntegrationAttemptCompleteErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2IntegrationAttemptCompleteError = @@ -17218,7 +16668,7 @@ export type V2McpListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2McpListError = V2McpListErrors[keyof V2McpListErrors] @@ -17228,8 +16678,8 @@ export type V2McpListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -17257,7 +16707,7 @@ export type V2CredentialRemoveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2CredentialRemoveError = V2CredentialRemoveErrors[keyof V2CredentialRemoveErrors] @@ -17295,7 +16745,7 @@ export type V2CredentialUpdateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2CredentialUpdateError = V2CredentialUpdateErrors[keyof V2CredentialUpdateErrors] @@ -17324,7 +16774,7 @@ export type V2ProjectListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ProjectListError = V2ProjectListErrors[keyof V2ProjectListErrors] @@ -17333,7 +16783,7 @@ export type V2ProjectListResponses = { /** * Success */ - 200: Array + 200: Array } export type V2ProjectListResponse = V2ProjectListResponses[keyof V2ProjectListResponses] @@ -17358,7 +16808,7 @@ export type V2ProjectCurrentErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ProjectCurrentError = V2ProjectCurrentErrors[keyof V2ProjectCurrentErrors] @@ -17367,7 +16817,7 @@ export type V2ProjectCurrentResponses = { /** * Project.Current */ - 200: ProjectCurrent2 + 200: ProjectCurrent } export type V2ProjectCurrentResponse = V2ProjectCurrentResponses[keyof V2ProjectCurrentResponses] @@ -17394,7 +16844,7 @@ export type V2ProjectDirectoriesErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ProjectDirectoriesError = V2ProjectDirectoriesErrors[keyof V2ProjectDirectoriesErrors] @@ -17403,7 +16853,7 @@ export type V2ProjectDirectoriesResponses = { /** * Project.Directories */ - 200: ProjectDirectories2 + 200: ProjectDirectories } export type V2ProjectDirectoriesResponse = V2ProjectDirectoriesResponses[keyof V2ProjectDirectoriesResponses] @@ -17428,7 +16878,7 @@ export type V2FormRequestListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2FormRequestListError = V2FormRequestListErrors[keyof V2FormRequestListErrors] @@ -17438,8 +16888,8 @@ export type V2FormRequestListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -17462,11 +16912,11 @@ export type V2SessionFormListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionFormListError = V2SessionFormListErrors[keyof V2SessionFormListErrors] @@ -17476,14 +16926,14 @@ export type V2SessionFormListResponses = { * Success */ 200: { - data: Array + data: Array } } export type V2SessionFormListResponse = V2SessionFormListResponses[keyof V2SessionFormListResponses] export type V2SessionFormCreateData = { - body: FormCreatePayload2 + body: FormCreatePayloadV2 path: { sessionID: string } @@ -17499,11 +16949,11 @@ export type V2SessionFormCreateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError /** * ConflictError */ @@ -17517,7 +16967,7 @@ export type V2SessionFormCreateResponses = { * Success */ 200: { - data: FormFormInfo2 | FormUrlInfo2 + data: FormFormInfoV2 | FormUrlInfoV2 } } @@ -17541,11 +16991,11 @@ export type V2SessionFormGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | FormNotFoundError */ - 404: FormNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: FormNotFoundError | SessionNotFoundError } export type V2SessionFormGetError = V2SessionFormGetErrors[keyof V2SessionFormGetErrors] @@ -17555,7 +17005,7 @@ export type V2SessionFormGetResponses = { * Success */ 200: { - data: FormFormInfo2 | FormUrlInfo2 + data: FormFormInfoV2 | FormUrlInfoV2 } } @@ -17579,11 +17029,11 @@ export type V2SessionFormStateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | FormNotFoundError */ - 404: FormNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: FormNotFoundError | SessionNotFoundError } export type V2SessionFormStateError = V2SessionFormStateErrors[keyof V2SessionFormStateErrors] @@ -17593,14 +17043,14 @@ export type V2SessionFormStateResponses = { * Success */ 200: { - data: FormState2 + data: FormState } } export type V2SessionFormStateResponse = V2SessionFormStateResponses[keyof V2SessionFormStateResponses] export type V2SessionFormReplyData = { - body: FormReply2 + body: FormReply path: { sessionID: string formID: string @@ -17613,19 +17063,19 @@ export type V2SessionFormReplyErrors = { /** * FormInvalidAnswerError | InvalidRequestError */ - 400: FormInvalidAnswerErrorV2 | InvalidRequestErrorV2 + 400: FormInvalidAnswerError | InvalidRequestErrorV2 /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | FormNotFoundError */ - 404: FormNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: FormNotFoundError | SessionNotFoundError /** * FormAlreadySettledError */ - 409: FormAlreadySettledErrorV2 + 409: FormAlreadySettledError } export type V2SessionFormReplyError = V2SessionFormReplyErrors[keyof V2SessionFormReplyErrors] @@ -17657,15 +17107,15 @@ export type V2SessionFormCancelErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | FormNotFoundError */ - 404: FormNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: FormNotFoundError | SessionNotFoundError /** * FormAlreadySettledError */ - 409: FormAlreadySettledErrorV2 + 409: FormAlreadySettledError } export type V2SessionFormCancelError = V2SessionFormCancelErrors[keyof V2SessionFormCancelErrors] @@ -17699,7 +17149,7 @@ export type V2PermissionRequestListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2PermissionRequestListError = V2PermissionRequestListErrors[keyof V2PermissionRequestListErrors] @@ -17709,8 +17159,8 @@ export type V2PermissionRequestListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -17733,7 +17183,7 @@ export type V2PermissionSavedListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2PermissionSavedListError = V2PermissionSavedListErrors[keyof V2PermissionSavedListErrors] @@ -17743,7 +17193,7 @@ export type V2PermissionSavedListResponses = { * Success */ 200: { - data: Array + data: Array } } @@ -17766,7 +17216,7 @@ export type V2PermissionSavedRemoveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2PermissionSavedRemoveError = V2PermissionSavedRemoveErrors[keyof V2PermissionSavedRemoveErrors] @@ -17797,11 +17247,11 @@ export type V2SessionPermissionListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionPermissionListError = V2SessionPermissionListErrors[keyof V2SessionPermissionListErrors] @@ -17811,7 +17261,7 @@ export type V2SessionPermissionListResponses = { * Success */ 200: { - data: Array + data: Array } } @@ -17826,7 +17276,7 @@ export type V2SessionPermissionCreateData = { metadata?: { [key: string]: unknown } - source?: PermissionV2Source2 + source?: PermissionV2SourceV2 agent?: string | null } path: { @@ -17844,11 +17294,11 @@ export type V2SessionPermissionCreateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionPermissionCreateError = V2SessionPermissionCreateErrors[keyof V2SessionPermissionCreateErrors] @@ -17860,7 +17310,7 @@ export type V2SessionPermissionCreateResponses = { 200: { data: { id: string - effect: PermissionV2Effect2 + effect: PermissionV2Effect } } } @@ -17886,11 +17336,11 @@ export type V2SessionPermissionGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | PermissionNotFoundError */ - 404: PermissionNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: PermissionNotFoundError | SessionNotFoundError } export type V2SessionPermissionGetError = V2SessionPermissionGetErrors[keyof V2SessionPermissionGetErrors] @@ -17900,7 +17350,7 @@ export type V2SessionPermissionGetResponses = { * Success */ 200: { - data: PermissionV2Request2 + data: PermissionV2RequestV2 } } @@ -17908,7 +17358,7 @@ export type V2SessionPermissionGetResponse = V2SessionPermissionGetResponses[key export type V2SessionPermissionReplyData = { body: { - reply: PermissionV2Reply2 + reply: PermissionV2Reply message?: string | null } path: { @@ -17927,11 +17377,11 @@ export type V2SessionPermissionReplyErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | PermissionNotFoundError */ - 404: PermissionNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: PermissionNotFoundError | SessionNotFoundError } export type V2SessionPermissionReplyError = V2SessionPermissionReplyErrors[keyof V2SessionPermissionReplyErrors] @@ -17966,7 +17416,7 @@ export type V2FsReadErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2FsReadError = V2FsReadErrors[keyof V2FsReadErrors] @@ -18001,7 +17451,7 @@ export type V2FsListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2FsListError = V2FsListErrors[keyof V2FsListErrors] @@ -18011,8 +17461,8 @@ export type V2FsListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -18041,7 +17491,7 @@ export type V2FsFindErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2FsFindError = V2FsFindErrors[keyof V2FsFindErrors] @@ -18051,8 +17501,8 @@ export type V2FsFindResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -18078,7 +17528,7 @@ export type V2CommandListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2CommandListError = V2CommandListErrors[keyof V2CommandListErrors] @@ -18088,8 +17538,8 @@ export type V2CommandListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -18115,7 +17565,7 @@ export type V2SkillListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2SkillListError = V2SkillListErrors[keyof V2SkillListErrors] @@ -18125,8 +17575,8 @@ export type V2SkillListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -18147,7 +17597,7 @@ export type V2EventSubscribeErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2EventSubscribeError = V2EventSubscribeErrors[keyof V2EventSubscribeErrors] @@ -18181,7 +17631,7 @@ export type V2PtyListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2PtyListError = V2PtyListErrors[keyof V2PtyListErrors] @@ -18191,7 +17641,7 @@ export type V2PtyListResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: Array } } @@ -18226,7 +17676,7 @@ export type V2PtyCreateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2PtyCreateError = V2PtyCreateErrors[keyof V2PtyCreateErrors] @@ -18236,7 +17686,7 @@ export type V2PtyCreateResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: PtyV2 } } @@ -18265,11 +17715,11 @@ export type V2PtyRemoveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * PtyNotFoundError */ - 404: PtyNotFoundErrorV2 + 404: PtyNotFoundError } export type V2PtyRemoveError = V2PtyRemoveErrors[keyof V2PtyRemoveErrors] @@ -18305,11 +17755,11 @@ export type V2PtyGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * PtyNotFoundError */ - 404: PtyNotFoundErrorV2 + 404: PtyNotFoundError } export type V2PtyGetError = V2PtyGetErrors[keyof V2PtyGetErrors] @@ -18319,7 +17769,7 @@ export type V2PtyGetResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: PtyV2 } } @@ -18354,11 +17804,11 @@ export type V2PtyUpdateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * PtyNotFoundError */ - 404: PtyNotFoundErrorV2 + 404: PtyNotFoundError } export type V2PtyUpdateError = V2PtyUpdateErrors[keyof V2PtyUpdateErrors] @@ -18368,7 +17818,7 @@ export type V2PtyUpdateResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: PtyV2 } } @@ -18397,15 +17847,15 @@ export type V2PtyConnectTokenErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ForbiddenError */ - 403: ForbiddenErrorV2 + 403: ForbiddenError /** * PtyNotFoundError */ - 404: PtyNotFoundErrorV2 + 404: PtyNotFoundError } export type V2PtyConnectTokenError = V2PtyConnectTokenErrors[keyof V2PtyConnectTokenErrors] @@ -18415,8 +17865,8 @@ export type V2PtyConnectTokenResponses = { * Success */ 200: { - location: LocationInfo2 - data: PtyTicketConnectToken2 + location: LocationInfoV2 + data: PtyTicketConnectTokenV2 } } @@ -18444,15 +17894,15 @@ export type V2PtyConnectErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ForbiddenError */ - 403: ForbiddenErrorV2 + 403: ForbiddenError /** * PtyNotFoundError */ - 404: PtyNotFoundErrorV2 + 404: PtyNotFoundError } export type V2PtyConnectError = V2PtyConnectErrors[keyof V2PtyConnectErrors] @@ -18486,7 +17936,7 @@ export type V2ShellListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ShellListError = V2ShellListErrors[keyof V2ShellListErrors] @@ -18496,7 +17946,7 @@ export type V2ShellListResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: Array } } @@ -18507,7 +17957,7 @@ export type V2ShellCreateData = { body: { command: string cwd?: string - timeout?: number + timeout: number metadata?: { [key: string]: unknown } @@ -18530,7 +17980,7 @@ export type V2ShellCreateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ShellCreateError = V2ShellCreateErrors[keyof V2ShellCreateErrors] @@ -18540,7 +17990,7 @@ export type V2ShellCreateResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: ShellV2 } } @@ -18569,11 +18019,11 @@ export type V2ShellRemoveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ShellNotFoundError */ - 404: ShellNotFoundErrorV2 + 404: ShellNotFoundError } export type V2ShellRemoveError = V2ShellRemoveErrors[keyof V2ShellRemoveErrors] @@ -18609,11 +18059,11 @@ export type V2ShellGetErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ShellNotFoundError */ - 404: ShellNotFoundErrorV2 + 404: ShellNotFoundError } export type V2ShellGetError = V2ShellGetErrors[keyof V2ShellGetErrors] @@ -18623,13 +18073,58 @@ export type V2ShellGetResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: ShellV2 } } export type V2ShellGetResponse = V2ShellGetResponses[keyof V2ShellGetResponses] +export type V2ShellTimeoutData = { + body: { + timeout: number + } + path: { + id: string + } + query?: { + location?: { + directory?: string | null + workspace?: string | null + } | null + } + url: "/api/shell/{id}/timeout" +} + +export type V2ShellTimeoutErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestErrorV2 + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * ShellNotFoundError + */ + 404: ShellNotFoundError +} + +export type V2ShellTimeoutError = V2ShellTimeoutErrors[keyof V2ShellTimeoutErrors] + +export type V2ShellTimeoutResponses = { + /** + * Success + */ + 200: { + location: LocationInfoV2 + data: ShellV2 + } +} + +export type V2ShellTimeoutResponse = V2ShellTimeoutResponses[keyof V2ShellTimeoutResponses] + export type V2ShellOutputData = { body?: never path: { @@ -18654,11 +18149,11 @@ export type V2ShellOutputErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * ShellNotFoundError */ - 404: ShellNotFoundErrorV2 + 404: ShellNotFoundError } export type V2ShellOutputError = V2ShellOutputErrors[keyof V2ShellOutputErrors] @@ -18668,7 +18163,7 @@ export type V2ShellOutputResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: { output: string cursor: number @@ -18700,7 +18195,7 @@ export type V2QuestionRequestListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2QuestionRequestListError = V2QuestionRequestListErrors[keyof V2QuestionRequestListErrors] @@ -18710,8 +18205,8 @@ export type V2QuestionRequestListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -18734,11 +18229,11 @@ export type V2SessionQuestionListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError */ - 404: SessionNotFoundErrorV2 + 404: SessionNotFoundError } export type V2SessionQuestionListError = V2SessionQuestionListErrors[keyof V2SessionQuestionListErrors] @@ -18748,14 +18243,14 @@ export type V2SessionQuestionListResponses = { * Success */ 200: { - data: Array + data: Array } } export type V2SessionQuestionListResponse = V2SessionQuestionListResponses[keyof V2SessionQuestionListResponses] export type V2SessionQuestionReplyData = { - body: QuestionV2Reply2 + body: QuestionV2Reply path: { sessionID: string requestID: string @@ -18772,11 +18267,11 @@ export type V2SessionQuestionReplyErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | QuestionNotFoundError */ - 404: QuestionNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: QuestionNotFoundError | SessionNotFoundError } export type V2SessionQuestionReplyError = V2SessionQuestionReplyErrors[keyof V2SessionQuestionReplyErrors] @@ -18808,11 +18303,11 @@ export type V2SessionQuestionRejectErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError /** * SessionNotFoundError | QuestionNotFoundError */ - 404: QuestionNotFoundErrorV2 | SessionNotFoundErrorV2 + 404: QuestionNotFoundError | SessionNotFoundError } export type V2SessionQuestionRejectError = V2SessionQuestionRejectErrors[keyof V2SessionQuestionRejectErrors] @@ -18846,7 +18341,7 @@ export type V2ReferenceListErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ReferenceListError = V2ReferenceListErrors[keyof V2ReferenceListErrors] @@ -18856,8 +18351,8 @@ export type V2ReferenceListResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -18888,7 +18383,7 @@ export type V2ProjectCopyRemoveErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ProjectCopyRemoveError = V2ProjectCopyRemoveErrors[keyof V2ProjectCopyRemoveErrors] @@ -18928,7 +18423,7 @@ export type V2ProjectCopyCreateErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ProjectCopyCreateError = V2ProjectCopyCreateErrors[keyof V2ProjectCopyCreateErrors] @@ -18937,7 +18432,7 @@ export type V2ProjectCopyCreateResponses = { /** * ProjectCopy.Copy */ - 200: ProjectCopyCopy2 + 200: ProjectCopyCopy } export type V2ProjectCopyCreateResponse = V2ProjectCopyCreateResponses[keyof V2ProjectCopyCreateResponses] @@ -18964,7 +18459,7 @@ export type V2ProjectCopyRefreshErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2ProjectCopyRefreshError = V2ProjectCopyRefreshErrors[keyof V2ProjectCopyRefreshErrors] @@ -18998,7 +18493,7 @@ export type V2VcsStatusErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2VcsStatusError = V2VcsStatusErrors[keyof V2VcsStatusErrors] @@ -19008,7 +18503,7 @@ export type V2VcsStatusResponses = { * Success */ 200: { - location: LocationInfo2 + location: LocationInfoV2 data: Array } } @@ -19023,7 +18518,7 @@ export type V2VcsDiffData = { directory?: string | null workspace?: string | null } | null - mode: VcsMode2 + mode: VcsMode context?: string | null } url: "/api/vcs/diff" @@ -19037,7 +18532,7 @@ export type V2VcsDiffErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2VcsDiffError = V2VcsDiffErrors[keyof V2VcsDiffErrors] @@ -19047,8 +18542,8 @@ export type V2VcsDiffResponses = { * Success */ 200: { - location: LocationInfo2 - data: Array + location: LocationInfoV2 + data: Array } } @@ -19069,7 +18564,7 @@ export type V2DebugLocationErrors = { /** * UnauthorizedError */ - 401: UnauthorizedErrorV2 + 401: UnauthorizedError } export type V2DebugLocationError = V2DebugLocationErrors[keyof V2DebugLocationErrors] @@ -19078,7 +18573,7 @@ export type V2DebugLocationResponses = { /** * Success */ - 200: Array + 200: Array } export type V2DebugLocationResponse = V2DebugLocationResponses[keyof V2DebugLocationResponses] diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index aa79b8bd51..ce3bc15f36 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -111,6 +111,22 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl } }), ) + .handle( + "session.remove", + Effect.fn(function* (ctx) { + yield* session.remove(ctx.params.sessionID).pipe( + Effect.catchTag( + "Session.NotFoundError", + (error) => + new SessionNotFoundError({ + sessionID: error.sessionID, + message: `Session not found: ${error.sessionID}`, + }), + ), + ) + return HttpApiSchema.NoContent.make() + }), + ) .handle( "session.fork", Effect.fn(function* (ctx) { @@ -348,44 +364,26 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl .handle( "session.compact", Effect.fn(function* (ctx) { - yield* session.compact({ sessionID: ctx.params.sessionID }).pipe( - Effect.catchTag("Session.NotFoundError", (error) => - Effect.fail( - new SessionNotFoundError({ - sessionID: error.sessionID, - message: `Session not found: ${error.sessionID}`, - }), - ), - ), - Effect.catchTag("Session.OperationUnavailableError", (error) => - Effect.fail( - new ServiceUnavailableError({ - message: `Session ${error.operation} is not available yet`, - service: `session.${error.operation}`, - }), - ), - ), - Effect.catchTag( - "Session.BusyError", - (error) => - new SessionBusyError({ - sessionID: error.sessionID, - message: `Session is busy: ${error.sessionID}`, - }), - ), - Effect.catchTag("Session.MessageDecodeError", (error) => { - const ref = `err_${crypto.randomUUID().slice(0, 8)}` - return Effect.logError("failed to decode session message during compaction").pipe( - Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), - Effect.andThen( - Effect.fail( - new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }), - ), + return { + data: yield* session.compact({ sessionID: ctx.params.sessionID, id: ctx.payload.id }).pipe( + Effect.catchTag("Session.NotFoundError", (error) => + Effect.fail( + new SessionNotFoundError({ + sessionID: error.sessionID, + message: `Session not found: ${error.sessionID}`, + }), ), - ) - }), - ) - return HttpApiSchema.NoContent.make() + ), + Effect.catchTag("Session.CompactionConflictError", (error) => + Effect.fail( + new ConflictError({ + message: `Compaction input ID conflicts with an existing durable record: ${error.inputID}`, + resource: error.inputID, + }), + ), + ), + ), + } }), ) .handle( diff --git a/packages/server/src/handlers/shell.ts b/packages/server/src/handlers/shell.ts index 6f4c57d538..ac1ae09a8c 100644 --- a/packages/server/src/handlers/shell.ts +++ b/packages/server/src/handlers/shell.ts @@ -38,6 +38,20 @@ export const ShellHandler = HttpApiBuilder.group(Api, "server.shell", (handlers) ) }), ) + .handle( + "shell.timeout", + Effect.fn(function* (ctx) { + const shell = yield* Shell.Service + return yield* response( + shell.timeout(ctx.params.id, ctx.payload.timeout).pipe( + Effect.catchTag( + "Shell.NotFoundError", + () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }), + ), + ), + ) + }), + ) .handle( "shell.output", Effect.fn(function* (ctx) { diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index 606fde0b84..1c5a57f62f 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -70,15 +70,17 @@ function makeRoutes( [PluginRuntime.providerNode, PluginRuntime.providerNodeWithCell(pluginRuntimeCell)], ...(sdkPlugins ? [[SdkPlugins.node, SdkPlugins.layerWithStore(sdkPlugins)] as const] : []), ] - // Simulation replacements are loaded via dynamic import so the simulation - // module is never eagerly loaded. Layer.unwrap defers both the import and - // the app-node build to layer-build time; when simulation is off the branch - // is byte-for-byte identical to a plain AppNodeBuilder.build call. - const serviceLayer = simulationEnabled() + const serviceLayer = simulateEnabled() ? Layer.unwrap( Effect.gen(function* () { - const { simulationReplacements } = yield* Effect.promise(() => import("@opencode-ai/simulation/backend")) - return AppNodeBuilder.build(applicationServices, [...replacements, ...simulationReplacements]) + const { simulationReplacements, startDriveServer } = yield* Effect.promise(() => + import("@opencode-ai/simulation/backend"), + ) + if (driveEnabled()) startDriveServer() + return AppNodeBuilder.build(applicationServices, [ + ...replacements, + ...(simulateEnabled() ? simulationReplacements : []), + ]) }), ) : AppNodeBuilder.build(applicationServices, replacements) @@ -96,8 +98,12 @@ function makeRoutes( ) } -function simulationEnabled() { - return !!process.env.OPENCODE_SIMULATION +function simulateEnabled() { + return !!process.env.OPENCODE_SIMULATE +} + +function driveEnabled() { + return !!process.env.OPENCODE_DRIVE } export const routes = createRoutes() diff --git a/packages/simulation/src/backend/control.ts b/packages/simulation/src/backend/control.ts index 28bb18c009..e52c8296f6 100644 --- a/packages/simulation/src/backend/control.ts +++ b/packages/simulation/src/backend/control.ts @@ -22,9 +22,6 @@ import { SimulationNetwork } from "./network" * - `network.log` simulated network request log */ -const DefaultPort = 40950 -const MaxPortAttempts = 100 - type ControlSocket = Bun.ServerWebSocket<{ unsubscribe?: () => void }> function parseRequest(input: string | Buffer) { @@ -68,46 +65,35 @@ async function handle(socket: ControlSocket, request: SimulationProtocol.JsonRpc throw new Error(`Unknown simulation control method: ${request.method}`) } -function serve(port = DefaultPort, attempts = MaxPortAttempts): Bun.Server<{ unsubscribe?: () => void }> { - try { - return Bun.serve<{ unsubscribe?: () => void }>({ - hostname: "127.0.0.1", - port, - fetch(request, server) { - if (server.upgrade(request, { data: {} })) return undefined - return new Response("opencode simulation control websocket", { status: 426 }) +export function start(endpoint: string) { + const url = new URL(endpoint) + const server = Bun.serve<{ unsubscribe?: () => void }>({ + hostname: url.hostname, + port: Number(url.port), + fetch(request, server) { + if (server.upgrade(request, { data: {} })) return undefined + return new Response("opencode drive backend websocket", { status: 426 }) + }, + websocket: { + close(socket) { + socket.data.unsubscribe?.() }, - websocket: { - close(socket) { - socket.data.unsubscribe?.() - }, - async message(socket, message) { - let request: SimulationProtocol.JsonRpc.Request | undefined - try { - request = parseRequest(message) - const result = await handle(socket, request) - const response = SimulationProtocol.JsonRpc.success(request.id, result) - if (response) socket.send(JSON.stringify(response)) - } catch (error) { - socket.send(JSON.stringify(SimulationProtocol.JsonRpc.failure(request?.id, error))) - } - }, + async message(socket, message) { + let request: SimulationProtocol.JsonRpc.Request | undefined + try { + request = parseRequest(message) + const result = await handle(socket, request) + const response = SimulationProtocol.JsonRpc.success(request.id, result) + if (response) socket.send(JSON.stringify(response)) + } catch (error) { + socket.send(JSON.stringify(SimulationProtocol.JsonRpc.failure(request?.id, error))) + } }, - }) - } catch (error) { - const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase() - const unavailable = message.includes("eaddrinuse") || message.includes("in use") - if (!unavailable || attempts <= 1 || port >= 65535) throw error - return serve(port + 1, attempts - 1) - } -} - -export function start() { - const server = serve() - const url = `ws://${server.hostname}:${server.port}` - process.stderr.write(`opencode simulation backend control websocket: ${url}\n`) + }, + }) + process.stderr.write(`opencode drive backend websocket: ${endpoint}\n`) return { - url, + url: endpoint, stop: () => { server.stop(true) }, diff --git a/packages/simulation/src/backend/filesystem.ts b/packages/simulation/src/backend/filesystem.ts index 01a7b00329..8405198b87 100644 --- a/packages/simulation/src/backend/filesystem.ts +++ b/packages/simulation/src/backend/filesystem.ts @@ -328,31 +328,31 @@ export function make(options: Options): FileSystem.FileSystem { * Lazily constructed layer so the root defaults to `process.cwd()` at * layer-build time (the simulation anchor directory), not at import time. * - * When `OPENCODE_SIMULATION_STATE` points at a snapshot directory, its - * `project/` contents are read from the host once at build time and seeded + * When `OPENCODE_SIMULATE_STATE` points at a snapshot directory, its + * `files/` contents are read from the host once at build time and seeded * into the in-memory tree, joined onto the anchor root. */ export const layer = (options?: Partial) => Layer.sync(FileSystem.FileSystem)(() => make({ root: options?.root ?? process.cwd(), - files: { ...loadSnapshotFiles(process.env.OPENCODE_SIMULATION_STATE), ...options?.files }, + files: { ...loadSnapshotFiles(process.env.OPENCODE_SIMULATE_STATE), ...options?.files }, }), ) function loadSnapshotFiles(stateDirectory: string | undefined) { if (!stateDirectory) return {} - const project = path.join(stateDirectory, "project") - if (!nodeFs.existsSync(project)) return {} + const snapshot = path.join(stateDirectory, "files") + if (!nodeFs.existsSync(snapshot)) return {} const files: Record = {} const walk = (dir: string) => { for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) { const file = path.join(dir, entry.name) if (entry.isDirectory()) walk(file) - if (entry.isFile()) files[path.relative(project, file)] = new Uint8Array(nodeFs.readFileSync(file)) + if (entry.isFile()) files[path.relative(snapshot, file)] = new Uint8Array(nodeFs.readFileSync(file)) } } - walk(project) + walk(snapshot) return files } diff --git a/packages/simulation/src/backend/index.ts b/packages/simulation/src/backend/index.ts index dd735d40dd..8610867a7a 100644 --- a/packages/simulation/src/backend/index.ts +++ b/packages/simulation/src/backend/index.ts @@ -1,6 +1,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { filesystem, httpClient } from "@opencode-ai/core/effect/app-node-platform" import { FSUtil } from "@opencode-ai/core/fs-util" +import { DriveManifest } from "../manifest" import { SimulationControl } from "./control" import { SimulationFileSystem } from "./filesystem" import { SimulationFSUtil } from "./fs-util" @@ -10,19 +11,15 @@ import { SimulationOpenAI } from "./openai" /** * Layer replacements applied when the server is built in simulation mode. * - * The server merges these into the app node build when `OPENCODE_SIMULATION` + * The server merges these into the app node build when `OPENCODE_SIMULATE` * is enabled, via a dynamic import so this module is never loaded eagerly. * - * - Filesystem: in-memory tree rooted at `OPENCODE_SIMULATION_ROOT` (the real, - * empty anchor directory the runner created and chdir'd into). Everything - * under the root lives in memory; paths outside it fail loudly. + * - Filesystem: in-memory tree rooted at the current working directory. + * Everything under the root lives in memory; paths outside it fail loudly. * - Network: all outbound HTTP resolves against the simulated route table; * unknown destinations are denied. The driver-answered OpenAI endpoint is * registered here as the first route. * - * Loading this module also starts the backend simulation control WebSocket, - * which drivers connect to directly for LLM exchange control and network - * inspection (standalone topology; also the headless-simulation interface). */ SimulationNetwork.register(SimulationOpenAI.route) @@ -30,10 +27,12 @@ SimulationNetwork.register(SimulationOpenAI.route) // an empty catalog; providers come from seeded config instead. SimulationNetwork.register(SimulationNetwork.json("GET", "https://models.dev/api.json", {})) -SimulationControl.start() +export function startDriveServer() { + return SimulationControl.start(DriveManifest.resolve().endpoints.backend) +} export const simulationReplacements: LayerNode.Replacements = [ - [filesystem, SimulationFileSystem.layer({ root: process.env.OPENCODE_SIMULATION_ROOT })], + [filesystem, SimulationFileSystem.layer()], [FSUtil.node, SimulationFSUtil.node], [httpClient, SimulationNetwork.layer], ] diff --git a/packages/simulation/src/frontend/renderer.ts b/packages/simulation/src/frontend/renderer.ts index 03e182cadc..168a8999b6 100644 --- a/packages/simulation/src/frontend/renderer.ts +++ b/packages/simulation/src/frontend/renderer.ts @@ -12,8 +12,8 @@ const setups = new WeakMap() export async function create(options: CliRendererConfig): Promise { const setup = await createTestRenderer({ ...options, - width: Number(process.env.OPENCODE_SIMULATION_TUI_WIDTH) || 100, - height: Number(process.env.OPENCODE_SIMULATION_TUI_HEIGHT) || 40, + width: 100, + height: 40, }) setups.set(setup.renderer, setup) return setup.renderer diff --git a/packages/simulation/src/frontend/server.ts b/packages/simulation/src/frontend/server.ts index c6a6c8784c..854b5e9846 100644 --- a/packages/simulation/src/frontend/server.ts +++ b/packages/simulation/src/frontend/server.ts @@ -2,23 +2,11 @@ import { SimulationProtocol } from "../protocol" import { SimulationActions, type Harness } from "./actions" import { SimulationTrace } from "./trace" -const DefaultPort = 40900 -const MaxPortAttempts = 100 - export interface Server { readonly url: string readonly stop: () => void } -function isEnabled() { - return process.env.OPENCODE_SIMULATION === "1" || process.env.OPENCODE_SIMULATION === "true" -} - -function isPortUnavailable(error: unknown) { - const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase() - return message.includes("eaddrinuse") || message.includes("address already in use") || message.includes(" in use") -} - function actionParam(params: unknown) { return SimulationProtocol.Frontend.decodeActionParams(params).action } @@ -53,54 +41,40 @@ async function handle(harness: Harness, request: SimulationProtocol.JsonRpc.Requ throw new Error(`Unknown simulation method: ${request.method}`) } -function serve( - harness: Harness, - port = DefaultPort, - attempts = MaxPortAttempts, -): Bun.Server<{ readonly simulation: true }> { - try { - return Bun.serve<{ readonly simulation: true }>({ - hostname: "127.0.0.1", - port, - fetch(request, server) { - if (server.upgrade(request, { data: { simulation: true } })) return undefined - return new Response("opencode simulation websocket", { status: 426 }) +export function start(harness: Harness, endpoint: string): Server { + const url = new URL(endpoint) + const server = Bun.serve<{ readonly drive: true }>({ + hostname: url.hostname, + port: Number(url.port), + fetch(request, server) { + if (server.upgrade(request, { data: { drive: true } })) return undefined + return new Response("opencode drive ui websocket", { status: 426 }) + }, + websocket: { + open() { + SimulationTrace.add("control.connect") }, - websocket: { - open() { - SimulationTrace.add("control.connect") - }, - close() { - SimulationTrace.add("control.disconnect") - }, - async message(socket, message) { - let request: SimulationProtocol.JsonRpc.Request | undefined - try { - request = parseRequest(message) - const result = await handle(harness, request) - const next = SimulationProtocol.JsonRpc.success(request.id, result) - if (next) socket.send(JSON.stringify(next)) - } catch (error) { - socket.send(JSON.stringify(SimulationProtocol.JsonRpc.failure(request?.id, error))) - } - }, + close() { + SimulationTrace.add("control.disconnect") }, - }) - } catch (error) { - if (!isPortUnavailable(error) || attempts <= 1 || port >= 65535) throw error - return serve(harness, port + 1, attempts - 1) - } -} - -export function start(harness: Harness): Server | undefined { - if (!isEnabled()) return - const server = serve(harness) - const url = `ws://${server.hostname}:${server.port}` - SimulationTrace.add("control.start", { url }) + async message(socket, message) { + let request: SimulationProtocol.JsonRpc.Request | undefined + try { + request = parseRequest(message) + const result = await handle(harness, request) + const next = SimulationProtocol.JsonRpc.success(request.id, result) + if (next) socket.send(JSON.stringify(next)) + } catch (error) { + socket.send(JSON.stringify(SimulationProtocol.JsonRpc.failure(request?.id, error))) + } + }, + }, + }) + SimulationTrace.add("control.start", { url: endpoint }) return { - url, + url: endpoint, stop: () => { - SimulationTrace.add("control.stop", { url }) + SimulationTrace.add("control.stop", { url: endpoint }) server.stop(true) }, } diff --git a/packages/simulation/src/frontend/simulation.ts b/packages/simulation/src/frontend/simulation.ts index ff24e0459d..96e68e8dca 100644 --- a/packages/simulation/src/frontend/simulation.ts +++ b/packages/simulation/src/frontend/simulation.ts @@ -1,27 +1,29 @@ import { createCliRenderer, type CliRenderer, type CliRendererConfig } from "@opentui/core" +import { DriveManifest } from "../manifest" import { SimulationActions } from "./actions" import { SimulationRenderer } from "./renderer" import { SimulationServer } from "./server" /** - * Simulation-mode renderer entry point. + * Drive-mode renderer entry point. * - * Creates the renderer (fake when OPENCODE_SIMULATION_RENDERER=fake, the - * normal visible renderer otherwise) and starts the simulation control + * Creates the renderer (fake when OPENCODE_DRIVE_RENDERER=fake, the normal + * visible renderer otherwise) and starts the UI control * server against it. The server stops when the renderer is destroyed, so the * caller only manages the renderer lifecycle. */ -export async function createSimulation(options: CliRendererConfig): Promise { +export async function create(options: CliRendererConfig): Promise { const renderer = - process.env.OPENCODE_SIMULATION_RENDERER === "fake" + process.env.OPENCODE_DRIVE_RENDERER === "fake" ? await SimulationRenderer.create(options) : await createCliRenderer(options) - const server = SimulationServer.start(SimulationActions.createHarness(renderer)) - if (server) { - process.stderr.write(`opencode simulation websocket: ${server.url}\n`) - renderer.once("destroy", () => server.stop()) - } + const server = SimulationServer.start( + SimulationActions.createHarness(renderer), + DriveManifest.resolve().endpoints.ui, + ) + process.stderr.write(`opencode drive ui websocket: ${server.url}\n`) + renderer.once("destroy", () => server.stop()) return renderer } -export * as Simulation from "./simulation" +export * as Drive from "./simulation" diff --git a/packages/simulation/src/manifest.ts b/packages/simulation/src/manifest.ts new file mode 100644 index 0000000000..0ef4530e48 --- /dev/null +++ b/packages/simulation/src/manifest.ts @@ -0,0 +1,57 @@ +import { existsSync, readFileSync } from "node:fs" +import { homedir } from "node:os" +import { join } from "node:path" + +export interface Manifest { + readonly endpoints: { + readonly ui: string + readonly backend: string + } +} + +export const defaults: Manifest = { + endpoints: { + ui: "ws://127.0.0.1:40900", + backend: "ws://127.0.0.1:40950", + }, +} + +export function resolve() { + const name = process.env.OPENCODE_DRIVE + if (!name) throw new Error("OPENCODE_DRIVE must contain a drive instance name") + if (name === "1") return defaults + if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(name)) throw new Error(`Invalid drive instance name: ${name}`) + + const directory = + process.env.DRIVE_REGISTRY_DIR ?? + join(process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state"), "opencode-drive", "instances") + const file = join(directory, `${name}.json`) + if (!existsSync(file)) throw new Error(`Drive manifest not found: ${file}`) + + const manifest: unknown = JSON.parse(readFileSync(file, "utf8")) + if (!isManifest(manifest)) throw new Error(`Invalid drive manifest: ${file}`) + validateEndpoint(manifest.endpoints.ui, "ui") + validateEndpoint(manifest.endpoints.backend, "backend") + return manifest +} + +function isManifest(value: unknown): value is Manifest { + if (typeof value !== "object" || value === null) return false + if (!("endpoints" in value) || typeof value.endpoints !== "object" || value.endpoints === null) return false + return ( + "ui" in value.endpoints && + typeof value.endpoints.ui === "string" && + "backend" in value.endpoints && + typeof value.endpoints.backend === "string" + ) +} + +function validateEndpoint(value: string, name: string) { + const endpoint = new URL(value) + const port = Number(endpoint.port) + if (endpoint.protocol !== "ws:" || endpoint.hostname !== "127.0.0.1" || !Number.isInteger(port) || port < 1) { + throw new Error(`Invalid drive ${name} endpoint: ${value}`) + } +} + +export * as DriveManifest from "./manifest" diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 67d16c1c48..8d924c9e9b 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -205,9 +205,9 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { }, } satisfies CliRendererConfig - if (!!process.env.OPENCODE_SIMULATION) { - const { Simulation } = await import("@opencode-ai/simulation/frontend") - return Simulation.createSimulation(options) + if (process.env.OPENCODE_DRIVE) { + const { Drive } = await import("@opencode-ai/simulation/frontend") + return Drive.create(options) } return createCliRenderer(options) @@ -500,7 +500,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi } if (route.data.type === "session") { - const session = sync.session.get(route.data.sessionID) + const session = data.session.get(route.data.sessionID) if (!session || isDefaultTitle(session.title)) { renderer.setTerminalTitle("OpenCode") return @@ -559,13 +559,10 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi route.navigate({ type: "session", sessionID: match }) return } - void sdk.client.session.fork({ sessionID: match }).then((result) => { - if (result.data?.id) { - route.navigate({ type: "session", sessionID: result.data.id }) - return - } - toast.show({ message: "Failed to fork session", variant: "error" }) - }) + void sdk.api.session + .fork({ sessionID: match }) + .then((result) => route.navigate({ type: "session", sessionID: result.id })) + .catch(toast.error) }) .catch(toast.error) }) @@ -577,13 +574,10 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi createEffect(() => { if (forked || sync.status !== "complete" || !args.sessionID || !args.fork) return forked = true - void sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => { - if (result.data?.id) { - route.navigate({ type: "session", sessionID: result.data.id }) - } else { - toast.show({ message: "Failed to fork session", variant: "error" }) - } - }) + void sdk.api.session + .fork({ sessionID: args.sessionID }) + .then((result) => route.navigate({ type: "session", sessionID: result.id })) + .catch(toast.error) }) const connected = useConnected() @@ -1074,7 +1068,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi }) event.on("session.deleted", (evt) => { - if (route.data.type === "session" && route.data.sessionID === evt.data.info.id) { + if (route.data.type === "session" && route.data.sessionID === evt.data.sessionID) { route.navigate({ type: "home" }) toast.show({ variant: "info", diff --git a/packages/tui/src/component/dialog-session-list.tsx b/packages/tui/src/component/dialog-session-list.tsx index 9232acf6d9..72de26d278 100644 --- a/packages/tui/src/component/dialog-session-list.tsx +++ b/packages/tui/src/component/dialog-session-list.tsx @@ -1,4 +1,4 @@ -import { createMemo, createResource, onMount } from "solid-js" +import { createMemo, createResource, createSignal, onMount } from "solid-js" import path from "path" import type { SessionV2Info } from "@opencode-ai/sdk/v2" import { useDialog } from "../ui/dialog" @@ -15,6 +15,7 @@ import { useToast } from "../ui/toast" import { useCommandShortcut } from "../keymap" import { DialogSessionRename } from "./dialog-session-rename" import { Spinner } from "./spinner" +import { errorMessage } from "../util/error" export function DialogSessionList() { const dialog = useDialog() @@ -26,8 +27,10 @@ export function DialogSessionList() { const local = useLocal() const toast = useToast() const [search, setSearch] = createDebouncedSignal("", 150) + const [toDelete, setToDelete] = createSignal() const quickSwitch1 = useCommandShortcut("session.quick_switch.1") const quickSwitch9 = useCommandShortcut("session.quick_switch.9") + const deleteHint = useCommandShortcut("session.delete") const [searchResults] = createResource(search, async (query) => { if (!query) return @@ -78,11 +81,13 @@ export function DialogSessionList() { const directory = session.location.directory const footer = directory !== project.data.project.mainDir ? Locale.truncate(path.basename(directory), 20) : "" const slot = slotByID.get(session.id) + const deleting = toDelete() === session.id return { - title: session.title, + title: deleting ? `Press ${deleteHint()} again to confirm` : session.title, value: session.id, category, footer, + bg: deleting ? theme.error : undefined, gutter: data.session.family(session.id).some((id) => data.session.status(id) === "running") ? () => @@ -104,9 +109,6 @@ export function DialogSessionList() { onMount(() => dialog.setSize("large")) - const unavailable = (feature: string) => - toast.show({ message: `${feature} is not implemented for V2 sessions yet`, variant: "error", duration: 5000 }) - return ( setToDelete(undefined)} onSelect={(option) => { route.navigate({ type: "session", sessionID: option.value }) dialog.clear() @@ -127,7 +130,22 @@ export function DialogSessionList() { { command: "session.delete", title: "delete", - onTrigger: () => unavailable("Deleting"), + onTrigger: (option: { value: string }) => { + if (toDelete() !== option.value) { + setToDelete(option.value) + return + } + void sdk.client.v2.session + .remove({ sessionID: option.value }, { throwOnError: true }) + .catch((error) => { + setToDelete(undefined) + toast.show({ + message: `Failed to delete session: ${errorMessage(error)}`, + variant: "error", + duration: 5000, + }) + }) + }, }, { command: "session.rename", diff --git a/packages/tui/src/context/data.tsx b/packages/tui/src/context/data.tsx index 8e3e8973f3..723f80633d 100644 --- a/packages/tui/src/context/data.tsx +++ b/packages/tui/src/context/data.tsx @@ -54,6 +54,8 @@ type Data = { // session ID in that family, including the key itself once its info arrives. family: Record status: Record + compaction: Partial> + compactionReason: Partial> message: Record input: Record permission: Record @@ -90,6 +92,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ info: {}, family: {}, status: {}, + compaction: {}, + compactionReason: {}, message: {}, input: {}, permission: {}, @@ -143,20 +147,24 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ const item = messages.findLast((item) => item.type === "shell" && item.shell.id === shellID) return item?.type === "shell" ? item : undefined }, + compaction(messages: SessionMessage[]) { + const item = messages.findLast( + (item) => item.type === "compaction" && (item.status === "queued" || item.status === "running"), + ) + return item?.type === "compaction" ? item : undefined + }, latestTool(assistant: SessionMessageAssistant | undefined, callID?: string) { return assistant?.content.findLast( (item): item is SessionMessageAssistantTool => item.type === "tool" && (callID === undefined || item.id === callID), ) }, - latestText(assistant: SessionMessageAssistant | undefined, textID: string) { - return assistant?.content.findLast( - (item): item is SessionMessageAssistantText => item.type === "text" && item.id === textID, - ) + latestText(assistant: SessionMessageAssistant | undefined) { + return assistant?.content.findLast((item): item is SessionMessageAssistantText => item.type === "text") }, - latestReasoning(assistant: SessionMessageAssistant | undefined, reasoningID: string) { + latestReasoning(assistant: SessionMessageAssistant | undefined) { return assistant?.content.findLast( - (item): item is SessionMessageAssistantReasoning => item.type === "reasoning" && item.id === reasoningID, + (item): item is SessionMessageAssistantReasoning => item.type === "reasoning" && !item.time?.completed, ) }, } @@ -213,11 +221,35 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ) } + function removeSession(sessionID: string) { + messageIndex.delete(sessionID) + setStore( + "session", + produce((draft) => { + delete draft.info[sessionID] + delete draft.status[sessionID] + delete draft.compaction[sessionID] + delete draft.message[sessionID] + delete draft.input[sessionID] + delete draft.permission[sessionID] + delete draft.form[sessionID] + for (const [rootID, family] of Object.entries(draft.family)) { + const next = family.filter((id) => id !== sessionID) + if (next.length === 0) delete draft.family[rootID] + else draft.family[rootID] = next + } + }), + ) + } + function handleEvent(event: V2Event) { switch (event.type) { case "session.created": void result.session.refresh(event.data.sessionID) break + case "session.deleted": + removeSession(event.data.sessionID) + break case "catalog.updated": void Promise.all([ result.location.model.refresh(event.location), @@ -273,7 +305,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ setStore("session", "info", event.data.sessionID, "title", event.data.title) break case "session.prompt.promoted": { - setSessionStatus(event.data.sessionID, "running") message.update(event.data.sessionID, (draft, index) => { const position = index.get(event.data.inputID) if (position === undefined) return @@ -335,7 +366,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ }) break case "session.shell.started": - setSessionStatus(event.data.sessionID, "running") message.update(event.data.sessionID, (draft, index) => { message.append(draft, index, { id: messageIDFromEvent(event.id), @@ -346,7 +376,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ }) break case "session.shell.ended": - setSessionStatus(event.data.sessionID, "idle") message.update(event.data.sessionID, (draft) => { const match = message.shell(draft, event.data.shell.id) if (!match) return @@ -356,11 +385,24 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ }) break case "session.step.started": - setSessionStatus(event.data.sessionID, "running") message.update(event.data.sessionID, (draft, index) => { - if (index.has(event.data.assistantMessageID)) return + const position = index.get(event.data.assistantMessageID) + const existing = position === undefined ? undefined : draft[position] + if (existing?.type === "assistant") { + existing.agent = event.data.agent + existing.model = event.data.model + existing.retry = undefined + existing.error = undefined + existing.finish = undefined + existing.time.completed = undefined + if (event.data.snapshot) existing.snapshot = { ...existing.snapshot, start: event.data.snapshot } + return + } const currentAssistant = message.activeAssistant(draft) - if (currentAssistant) currentAssistant.time.completed = event.created + if (currentAssistant) { + currentAssistant.retry = undefined + currentAssistant.time.completed = event.created + } message.append(draft, index, { id: event.data.assistantMessageID, type: "assistant", @@ -373,7 +415,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ }) break case "session.step.ended": - setSessionStatus(event.data.sessionID, "running") message.update(event.data.sessionID, (draft, index) => { const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID) if (!currentAssistant) return @@ -392,32 +433,26 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ currentAssistant.time.completed = event.created currentAssistant.finish = "error" currentAssistant.error = event.data.error + currentAssistant.retry = undefined }) break case "session.text.started": message.update(event.data.sessionID, (draft, index) => { message.assistant(draft, index, event.data.assistantMessageID)?.content.push({ type: "text", - id: event.data.textID, text: "", }) }) break case "session.text.delta": message.update(event.data.sessionID, (draft, index) => { - const match = message.latestText( - message.assistant(draft, index, event.data.assistantMessageID), - event.data.textID, - ) + const match = message.latestText(message.assistant(draft, index, event.data.assistantMessageID)) if (match) match.text += event.data.delta }) break case "session.text.ended": message.update(event.data.sessionID, (draft, index) => { - const match = message.latestText( - message.assistant(draft, index, event.data.assistantMessageID), - event.data.textID, - ) + const match = message.latestText(message.assistant(draft, index, event.data.assistantMessageID)) if (match) match.text = event.data.text }) break @@ -458,7 +493,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ) if (!match) return match.time.ran = event.created - match.provider = event.data.provider + match.executed = event.data.executed + match.providerState = event.data.state match.state = { status: "running", input: event.data.input, structured: {}, content: [] } }) break @@ -487,11 +523,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ content: [...event.data.content], result: event.data.result, } - match.provider = { - executed: event.data.provider.executed || match.provider?.executed === true, - metadata: match.provider?.metadata, - resultMetadata: event.data.provider.metadata, - } + match.executed = event.data.executed || match.executed === true + match.providerResultState = event.data.resultState match.time.completed = event.created }) break @@ -510,11 +543,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ content: match.state.status === "running" ? match.state.content : [], result: event.data.result, } - match.provider = { - executed: event.data.provider.executed || match.provider?.executed === true, - metadata: match.provider?.metadata, - resultMetadata: event.data.provider.metadata, - } + match.executed = event.data.executed || match.executed === true + match.providerResultState = event.data.resultState match.time.completed = event.created }) break @@ -522,41 +552,77 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ message.update(event.data.sessionID, (draft, index) => { message.assistant(draft, index, event.data.assistantMessageID)?.content.push({ type: "reasoning", - id: event.data.reasoningID, text: "", - providerMetadata: event.data.providerMetadata, + state: event.data.state, time: { created: event.created }, }) }) break case "session.reasoning.delta": message.update(event.data.sessionID, (draft, index) => { - const match = message.latestReasoning( - message.assistant(draft, index, event.data.assistantMessageID), - event.data.reasoningID, - ) + const match = message.latestReasoning(message.assistant(draft, index, event.data.assistantMessageID)) if (match) match.text += event.data.delta }) break case "session.reasoning.ended": message.update(event.data.sessionID, (draft, index) => { - const match = message.latestReasoning( - message.assistant(draft, index, event.data.assistantMessageID), - event.data.reasoningID, - ) + const match = message.latestReasoning(message.assistant(draft, index, event.data.assistantMessageID)) if (match) { match.text = event.data.text match.time = { created: match.time?.created ?? event.created, completed: event.created } - if (event.data.providerMetadata !== undefined) match.providerMetadata = event.data.providerMetadata + if (event.data.state !== undefined) match.state = event.data.state } }) break - case "session.retried": - case "session.compaction.started": + case "session.retry.scheduled": + message.update(event.data.sessionID, (draft, index) => { + const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID) + if (!currentAssistant) return + currentAssistant.retry = { + attempt: event.data.attempt, + at: event.data.at, + error: event.data.error, + } + }) + break + case "session.execution.started": setSessionStatus(event.data.sessionID, "running") break - case "session.execution.settled": + case "session.compaction.admitted": + message.update(event.data.sessionID, (draft, index) => { + if (message.compaction(draft)) return + message.append(draft, index, { + id: event.data.inputID, + type: "compaction", + status: "queued", + reason: "manual", + summary: "", + recent: "", + time: { created: event.created }, + }) + }) + break + case "session.compaction.started": + setStore("session", "compaction", event.data.sessionID, "") + setStore("session", "compactionReason", event.data.sessionID, event.data.reason) + if (event.data.reason === "manual") + message.update(event.data.sessionID, (draft) => { + const current = message.compaction(draft) + if (current) current.status = "running" + }) + break + case "session.execution.succeeded": + case "session.execution.failed": + case "session.execution.interrupted": setSessionStatus(event.data.sessionID, "idle") + if (store.session.compaction[event.data.sessionID] !== undefined) + setStore("session", "compaction", event.data.sessionID, undefined) + if (store.session.compactionReason[event.data.sessionID] !== undefined) + setStore("session", "compactionReason", event.data.sessionID, undefined) + message.update(event.data.sessionID, (draft) => { + const currentAssistant = message.activeAssistant(draft) + if (currentAssistant) currentAssistant.retry = undefined + }) break case "session.revert.staged": if (store.session.info[event.data.sessionID]) @@ -573,21 +639,38 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ "session", "input", event.data.sessionID, - (store.session.input[event.data.sessionID] ?? []).filter((id) => id < event.data.messageID), + (store.session.input[event.data.sessionID] ?? []).filter((id) => id < event.data.to), ) message.update(event.data.sessionID, (draft, index) => { - const position = draft.findIndex((item) => item.id >= event.data.messageID) + const position = draft.findIndex((item) => item.id >= event.data.to) if (position === -1) return for (const item of draft.splice(position)) index.delete(item.id) }) break case "session.compaction.delta": + setStore("session", "compaction", event.data.sessionID, (text) => (text ?? "") + event.data.text) + if (store.session.compactionReason[event.data.sessionID] === "manual") + message.update(event.data.sessionID, (draft) => { + const current = message.compaction(draft) + if (current) current.summary += event.data.text + }) break case "session.compaction.ended": + setStore("session", "compaction", event.data.sessionID, undefined) + setStore("session", "compactionReason", event.data.sessionID, undefined) message.update(event.data.sessionID, (draft, index) => { + const current = event.data.reason === "manual" ? message.compaction(draft) : undefined + if (current) { + current.status = "completed" + current.reason = event.data.reason + current.summary = event.data.text + current.recent = event.data.recent + return + } message.append(draft, index, { id: messageIDFromEvent(event.id), type: "compaction", + status: "completed", reason: event.data.reason, summary: event.data.text, recent: event.data.recent, @@ -595,6 +678,14 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ }) }) break + case "session.compaction.failed": + setStore("session", "compaction", event.data.sessionID, undefined) + setStore("session", "compactionReason", event.data.sessionID, undefined) + message.update(event.data.sessionID, (draft) => { + const current = message.compaction(draft) + if (current) current.status = "failed" + }) + break case "permission.v2.asked": if (store.session.permission[event.data.sessionID]?.some((request) => request.id === event.data.id)) break setStore("session", "permission", event.data.sessionID, [ @@ -710,6 +801,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ return store.session.input[sessionID]?.includes(inputID) ?? false }, }, + compaction(sessionID: string) { + return store.session.compaction[sessionID] + }, async refresh(sessionID: string) { setStore("session", "info", sessionID, mutable(await sdk.api.session.get({ sessionID }))) registerSession(sessionID) @@ -744,6 +838,14 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ ].toSorted((a, b) => a.time.created - b.time.created) messageIndex.set(sessionID, new Map(messages.map((message, index) => [message.id, index]))) setStore("session", "message", sessionID, messages) + const running = messages.find((message) => message.type === "compaction" && message.status === "running") + setStore("session", "compaction", sessionID, running?.type === "compaction" ? running.summary : undefined) + setStore( + "session", + "compactionReason", + sessionID, + running?.type === "compaction" ? running.reason : undefined, + ) }, }, permission: { @@ -790,15 +892,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({ shell: Object.fromEntries(mutable(result.data).map((info) => [info.id, info])), }) }, - async remove(id: string) { - await sdk.api.shell.remove({ id }) - setStore( - "location", - produce((draft) => { - for (const data of Object.values(draft)) delete data.shell?.[id] - }), - ) - }, }, location: { default() { diff --git a/packages/tui/src/context/local.tsx b/packages/tui/src/context/local.tsx index efcaa8a01d..5c16acdaf8 100644 --- a/packages/tui/src/context/local.tsx +++ b/packages/tui/src/context/local.tsx @@ -470,7 +470,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } event.on("session.deleted", (evt) => { - prune(evt.data.info.id) + prune(evt.data.sessionID) }) return { diff --git a/packages/tui/src/feature-plugins/system/notifications.ts b/packages/tui/src/feature-plugins/system/notifications.ts index a5033b62e0..b8f2727bae 100644 --- a/packages/tui/src/feature-plugins/system/notifications.ts +++ b/packages/tui/src/feature-plugins/system/notifications.ts @@ -27,8 +27,8 @@ function sessionErrorMessage(error: SessionError) { } const tui: TuiPlugin = async (api) => { - const active = new Set() const errored = new Set() + const terminal = new Set() const forms = new Set() const questions = new Set() const permissions = new Set() @@ -73,14 +73,13 @@ const tui: TuiPlugin = async (api) => { }) const started = (sessionID: string) => { - active.add(sessionID) errored.delete(sessionID) + terminal.delete(sessionID) } const ended = (sessionID: string) => { - if (!active.has(sessionID)) return - active.delete(sessionID) - + if (terminal.has(sessionID)) return + terminal.add(sessionID) if (errored.has(sessionID)) { errored.delete(sessionID) return @@ -90,28 +89,25 @@ const tui: TuiPlugin = async (api) => { notify(api, sessionID, "Session done", session?.parentID ? "subagent_done" : "done") } - api.event.on("session.prompt.promoted", (event) => started(event.data.sessionID)) - api.event.on("session.shell.started", (event) => started(event.data.sessionID)) - api.event.on("session.step.started", (event) => started(event.data.sessionID)) - api.event.on("session.retried", (event) => started(event.data.sessionID)) - api.event.on("session.compaction.started", (event) => started(event.data.sessionID)) - api.event.on("session.shell.ended", (event) => ended(event.data.sessionID)) - api.event.on("session.step.ended", (event) => { - if (event.data.finish === "tool-calls") return - ended(event.data.sessionID) - }) - api.event.on("session.step.failed", (event) => { + api.event.on("session.execution.started", (event) => started(event.data.sessionID)) + api.event.on("session.execution.succeeded", (event) => ended(event.data.sessionID)) + api.event.on("session.execution.interrupted", (event) => ended(event.data.sessionID)) + api.event.on("session.execution.failed", (event) => { const sessionID = event.data.sessionID - if (!active.has(sessionID)) return + if (errored.has(sessionID)) { + ended(sessionID) + return + } errored.add(sessionID) - notify(api, sessionID, "Session error", "error") + notify(api, sessionID, event.data.error.message, "error") ended(sessionID) }) api.event.on("session.error", (event) => { const sessionID = event.data.sessionID if (!sessionID) return - if (!active.has(sessionID)) return + if (api.state.session.status(sessionID)?.type !== "busy") return + if (errored.has(sessionID)) return errored.add(sessionID) notify(api, sessionID, sessionErrorMessage(event.data.error), "error") }) diff --git a/packages/tui/src/routes/home.tsx b/packages/tui/src/routes/home.tsx index d4074da3a5..1d5ba75c97 100644 --- a/packages/tui/src/routes/home.tsx +++ b/packages/tui/src/routes/home.tsx @@ -12,6 +12,8 @@ import { useEditorContext } from "../context/editor" import { useTerminalDimensions } from "@opentui/solid" import { useTuiConfig } from "../config" import { HomeSessionDestinationProvider } from "./home/session-destination" +import { useData } from "../context/data" +import { LocationProvider } from "../context/location" let once = false const placeholder = { @@ -30,6 +32,7 @@ export function Home() { const editor = useEditorContext() const dimensions = useTerminalDimensions() const tuiConfig = useTuiConfig() + const data = useData() const promptMaxWidth = createMemo(() => { const configured = tuiConfig.prompt?.max_width if (configured === "auto") return Math.max(75, Math.floor(dimensions().width * 0.7)) @@ -68,28 +71,30 @@ export function Home() { }) return ( - - - - - - - - + + + + + + + + + + + + + + } placeholders={placeholder} /> + + + + + - - - - } placeholders={placeholder} /> - + + - - - - - - - - + + ) } diff --git a/packages/tui/src/routes/session/composer/shell-tab.tsx b/packages/tui/src/routes/session/composer/shell-tab.tsx index 1ea9f73fd3..90e90a1785 100644 --- a/packages/tui/src/routes/session/composer/shell-tab.tsx +++ b/packages/tui/src/routes/session/composer/shell-tab.tsx @@ -2,12 +2,16 @@ import { createMemo, For, Show, createEffect, onMount, onCleanup } from "solid-j import { createStore } from "solid-js/store" import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core" import { useData } from "../../../context/data" +import { useLocation } from "../../../context/location" +import { useSDK } from "../../../context/sdk" import { useTheme, selectedForeground } from "../../../context/theme" import { useBindings, useCommandShortcut } from "../../../keymap" import { useComposerTab } from "./index" export function ShellTab(props: { sessionID: string }) { const data = useData() + const location = useLocation() + const sdk = useSDK() const { theme } = useTheme() const fg = selectedForeground(theme) const composer = useComposerTab() @@ -79,7 +83,11 @@ export function ShellTab(props: { sessionID: string }) { run() { const entry = selectedEntry() if (!entry) return - void data.shell.remove(entry.id) + const ref = location() + void sdk.api.shell.remove({ + id: entry.id, + location: ref ? { directory: ref.directory, workspace: ref.workspaceID } : undefined, + }) }, }, ], diff --git a/packages/tui/src/routes/session/dialog-fork-from-timeline.tsx b/packages/tui/src/routes/session/dialog-fork-from-timeline.tsx deleted file mode 100644 index d0952a907e..0000000000 --- a/packages/tui/src/routes/session/dialog-fork-from-timeline.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { createMemo, onMount } from "solid-js" -import { useSync } from "../../context/sync" -import { DialogSelect, type DialogSelectOption } from "../../ui/dialog-select" -import type { TextPart } from "@opencode-ai/sdk/v2" -import { Locale } from "../../util/locale" -import { useSDK } from "../../context/sdk" -import { useRoute } from "../../context/route" -import { useDialog, type DialogContext } from "../../ui/dialog" -import { emptyPrompt, type PromptInfo } from "../../component/prompt/history" - -export function DialogForkFromTimeline(props: { sessionID: string; onMove: (messageID?: string) => void }) { - const sync = useSync() - const dialog = useDialog() - const sdk = useSDK() - const route = useRoute() - - onMount(() => { - dialog.setSize("large") - }) - - const options = createMemo((): DialogSelectOption[] => { - const messages = sync.data.message[props.sessionID] ?? [] - const fullSession = { - title: "Full session", - value: undefined, - onSelect: async (dialog: DialogContext) => { - const forked = await sdk.client.session.fork({ sessionID: props.sessionID }) - route.navigate({ - sessionID: forked.data!.id, - type: "session", - }) - dialog.clear() - }, - } satisfies DialogSelectOption - const result = [] as DialogSelectOption[] - for (const message of messages) { - if (message.role !== "user") continue - const part = (sync.data.part[message.id] ?? []).find( - (x) => x.type === "text" && !x.synthetic && !x.ignored, - ) as TextPart - if (!part) continue - result.push({ - title: part.text.replace(/\n/g, " "), - value: message.id, - footer: Locale.time(message.time.created), - onSelect: async (dialog) => { - const forked = await sdk.client.session.fork({ - sessionID: props.sessionID, - messageID: message.id, - }) - const parts = sync.data.part[message.id] ?? [] - const prompt = parts.reduce( - (agg, part) => { - if (part.type === "text") { - if (!part.synthetic) agg.text += part.text - } - if (part.type === "file") { - const files = (agg.files ??= []) - files.push({ - uri: part.url, - name: part.filename, - mention: part.source?.text - ? { - start: part.source.text.start, - end: part.source.text.end, - text: part.source.text.value, - } - : undefined, - }) - } - return agg - }, - emptyPrompt() as PromptInfo, - ) - route.navigate({ - sessionID: forked.data!.id, - type: "session", - prompt, - }) - dialog.clear() - }, - }) - } - return [fullSession, ...result.reverse()] - }) - - return props.onMove(option.value)} title="Fork session" options={options()} /> -} diff --git a/packages/tui/src/routes/session/dialog-fork.tsx b/packages/tui/src/routes/session/dialog-fork.tsx new file mode 100644 index 0000000000..c6eb91594d --- /dev/null +++ b/packages/tui/src/routes/session/dialog-fork.tsx @@ -0,0 +1,89 @@ +import { createMemo, createSignal, onMount, Show } from "solid-js" +import { useData } from "../../context/data" +import { useRoute } from "../../context/route" +import { useSDK } from "../../context/sdk" +import { Spinner } from "../../component/spinner" +import { DialogSelect, type DialogSelectOption } from "../../ui/dialog-select" +import { useDialog } from "../../ui/dialog" +import { useToast } from "../../ui/toast" +import { errorMessage } from "../../util/error" +import { Locale } from "../../util/locale" + +export function DialogFork(props: { sessionID: string; messageID?: string; onMove?: (messageID?: string) => void }) { + const data = useData() + const dialog = useDialog() + const sdk = useSDK() + const route = useRoute() + const toast = useToast() + const [pending, setPending] = createSignal(false) + + const fork = async (messageID?: string) => { + setPending(true) + const result = await sdk.api.session.fork({ sessionID: props.sessionID, messageID }).catch((error) => { + toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }) + return undefined + }) + if (!result) return dialog.clear() + const message = messageID ? data.session.message.get(props.sessionID, messageID) : undefined + route.navigate({ + sessionID: result.id, + type: "session", + prompt: + message?.type === "user" + ? { + text: message.text, + files: message.files?.map((file) => ({ + uri: file.source.type === "uri" ? file.source.uri : `data:${file.mime};base64,${file.data}`, + name: file.name, + description: file.description, + mention: file.mention, + })), + agents: structuredClone(message.agents ?? []), + pasted: [], + } + : undefined, + }) + dialog.clear() + toast.show({ message: "Forked session", variant: "success", duration: 4000 }) + } + + onMount(() => { + dialog.setSize("large") + if (props.messageID) void fork(props.messageID) + }) + + const options = createMemo((): DialogSelectOption[] => [ + { + title: "Full session", + value: undefined, + onSelect: () => fork(), + }, + ...data.session.message + .list(props.sessionID) + .filter((message) => message.type === "user") + .toReversed() + .map((message) => ({ + title: message.text.replace(/\n/g, " "), + value: message.id, + footer: Locale.time(message.time.created), + onSelect: () => fork(message.id), + })), + ]) + + return ( + + Forking session... + + } + > + props.onMove?.(option.value)} + title="Fork session" + options={options()} + /> + + ) +} diff --git a/packages/tui/src/routes/session/dialog-message.tsx b/packages/tui/src/routes/session/dialog-message.tsx index 240be47408..f31b14868a 100644 --- a/packages/tui/src/routes/session/dialog-message.tsx +++ b/packages/tui/src/routes/session/dialog-message.tsx @@ -5,8 +5,9 @@ import { useClipboard } from "../../context/clipboard" import { useToast } from "../../ui/toast" import { useSDK } from "../../context/sdk" import { errorMessage } from "../../util/error" +import { DialogFork } from "./dialog-fork" -export function DialogMessage(props: { messageID: string; sessionID: string; setPrompt?: unknown }) { +export function DialogMessage(props: { messageID: string; sessionID: string }) { const data = useData() const clipboard = useClipboard() const toast = useToast() @@ -55,8 +56,9 @@ export function DialogMessage(props: { messageID: string; sessionID: string; set value: "session.fork", description: "create a new session", onSelect: (dialog) => { - toast.show({ message: "Forking is not implemented for V2 sessions yet", variant: "error", duration: 5000 }) - dialog.clear() + const value = message() + if (!value || value.type !== "user") return + dialog.replace(() => ) }, }, ]} diff --git a/packages/tui/src/routes/session/dialog-timeline.tsx b/packages/tui/src/routes/session/dialog-timeline.tsx index bda87119d4..b3e162e9ad 100644 --- a/packages/tui/src/routes/session/dialog-timeline.tsx +++ b/packages/tui/src/routes/session/dialog-timeline.tsx @@ -5,12 +5,10 @@ import type { TextPart } from "@opencode-ai/sdk/v2" import { Locale } from "../../util/locale" import { DialogMessage } from "./dialog-message" import { useDialog } from "../../ui/dialog" -import type { PromptInfo } from "../../component/prompt/history" export function DialogTimeline(props: { sessionID: string onMove: (messageID: string) => void - setPrompt?: (prompt: PromptInfo) => void }) { const sync = useSync() const dialog = useDialog() @@ -33,9 +31,7 @@ export function DialogTimeline(props: { value: message.id, footer: Locale.time(message.time.created), onSelect: (dialog) => { - dialog.replace(() => ( - - )) + dialog.replace(() => ) }, }) } diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 8ea661cced..d1ea5e1272 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -21,7 +21,7 @@ import { useProject } from "../../context/project" import { useData } from "../../context/data" import { SplitBorder } from "../../ui/border" import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime" -import { Spinner } from "../../component/spinner" +import { Spinner, SPINNER_FRAMES } from "../../component/spinner" import { createSyntaxStyleMemo, generateSubtleSyntax, useTheme } from "../../context/theme" import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core" import { Prompt, type PromptRef } from "../../component/prompt" @@ -46,6 +46,7 @@ import { useDialog } from "../../ui/dialog" import { DialogSessionRename } from "../../component/dialog-session-rename" import { TodoItem } from "../../component/todo-item" import { DialogMessage } from "./dialog-message" +import { DialogFork } from "./dialog-fork" import { Sidebar } from "./sidebar" import { Composer } from "./composer" import { filetype } from "../../util/filetype" @@ -70,7 +71,7 @@ import { usePluginRuntime } from "../../plugin/runtime" import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut } from "../../keymap" import { usePathFormatter } from "../../context/path-format" import { LocationProvider } from "../../context/location" -import { createSessionRows, type PartRef, type SessionRow } from "./rows" +import { createSessionRows, resolvePart, type PartRef, type SessionRow } from "./rows" import { switchLabel } from "../../util/model" addDefaultParsers(parsers.parsers) @@ -171,6 +172,16 @@ export function Session() { }) onCleanup(() => setEpilogue()) const messages = sessionMessages + const transientCompaction = createMemo(() => { + if ( + messages().some( + (message) => message.type === "compaction" && (message.status === "queued" || message.status === "running"), + ) + ) + return + const text = data.session.compaction(route.sessionID) + return text === undefined ? undefined : { text } + }) const descendantSessionIDs = createMemo(() => { if (session()?.parentID) return [] return data.session.family(route.sessionID).filter((id) => id !== route.sessionID) @@ -370,7 +381,18 @@ export function Session() { value: "session.fork", category: "Session", slash: { name: "fork" }, - run: () => unavailable("Forking"), + run: () => { + dialog.replace(() => ( + { + if (!messageID) return + const child = scroll.getChildren().find((child) => child.id === messageID) + if (child) scroll.scrollBy(child.y - scroll.y - 1) + }} + /> + )) + }, }, { title: "Compact session", @@ -914,6 +936,9 @@ export function Session() { /> )} + + {(compaction) => } + - + } /> ) @@ -1096,7 +1121,7 @@ function SessionPartView(props: { partRef: PartRef; message: (messageID: string) const part = createMemo(() => { const item = message() if (item?.type !== "assistant") return - return item.content.find((part) => part.id === props.partRef.partID) + return resolvePart(item, props.partRef.partID) }) return ( @@ -1136,7 +1161,7 @@ function SessionGroupView(props: { refs.flatMap((ref) => { const message = props.message(ref.messageID) if (message?.type !== "assistant") return [] - const part = message.content.find((part) => part.id === ref.partID) + const part = resolvePart(message, ref.partID) if (part?.type !== "tool") return [] return [part] }) @@ -1216,6 +1241,7 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) { {errorMessage(props.message.error)} + @@ -1243,7 +1269,11 @@ function SessionSwitchMessageV2(props: { message: SessionMessage }) { return switchLabel(props.message.model, ctx.models(), props.message.previous) return "" } - return {text()} + return ( + + {text()} + + ) } function SessionNoticeMessageV2(props: { message: SessionMessage }) { @@ -1269,9 +1299,59 @@ function SessionSkillMessage(props: { message: Extract +function CompactionMessage(props: { + message?: Extract + status?: "running" + text?: string +}) { + const ctx = use() + const kv = useKV() + const { theme, syntax } = useTheme() + const status = () => props.message?.status ?? props.status + const text = () => props.message?.summary ?? props.text ?? "" + const color = () => (status() === "failed" ? theme.error : status() === "completed" ? theme.success : theme.textMuted) + const border = () => (status() === "queued" ? theme.border : color()) + return ( + + + + + + + }> + + + + + + + + + + + + + + {status() === "queued" ? "Compaction queued" : "Compaction"} + + + + + + + + + + ) } function statusLabel(status: "added" | "modified" | "deleted") { @@ -1395,7 +1475,7 @@ function UserMessage(props: { message: SessionMessageUser }) { {(file) => { - const label = file.mime === "application/x-directory" ? "Directory" : file.mime + const label = file.mime === "application/x-directory" ? "dir" : "file" return ( - {` ${label} `} + {` ${label} `} {" "} {file.name ?? (file.source.type === "uri" ? file.source.uri : "attachment")}{" "} @@ -1547,6 +1627,7 @@ function AssistantMessage(props: { message: SessionMessageAssistant; last: boole {errorMessage(props.message.error)} + @@ -1566,6 +1647,21 @@ function AssistantMessage(props: { message: SessionMessageAssistant; last: boole ) } +function AssistantRetry(props: { retry: SessionMessageAssistant["retry"] }) { + const { theme } = useTheme() + return ( + + {(retry) => ( + + + Retry attempt {retry().attempt} scheduled: {retry().error.message} [{retry().error.type}] + + + )} + + ) +} + function ExplorationSummary(props: { parts: SessionMessageAssistantTool[]; active: boolean }) { const { theme } = useTheme() const pathFormatter = usePathFormatter() diff --git a/packages/tui/src/routes/session/rows.ts b/packages/tui/src/routes/session/rows.ts index ff8e2d5155..5d2a361452 100644 --- a/packages/tui/src/routes/session/rows.ts +++ b/packages/tui/src/routes/session/rows.ts @@ -74,19 +74,25 @@ export function createSessionRows(sessionID: Accessor) { createEffect( on( () => - data.session.message - .list(sessionID()) - .flatMap((message) => - message.type === "user" + data.session.message.list(sessionID()).flatMap((message) => + message.type === "user" + ? [ + { + id: message.id, + created: message.time.created, + input: data.session.input.has(sessionID(), message.id), + }, + ] + : message.type === "compaction" ? [ { id: message.id, created: message.time.created, - input: data.session.input.has(sessionID(), message.id), + input: message.status === "queued" || message.status === "running", }, ] : [], - ), + ), () => setRows(reconcile(reduce())), ), ) @@ -95,9 +101,11 @@ export function createSessionRows(sessionID: Accessor) { setRows( produce((draft) => { if (draft.some((row) => row.type === "message" && row.messageID === messageID)) return - const queued = isQueued(messageID) - const index = queued ? draft.length : queuedStart(draft) - if (!queued) completePrevious(draft, index) + const pending = isPending(messageID) + const message = data.session.message.get(sessionID(), messageID) + const index = + message?.type === "compaction" && pending ? queuedStart(draft) : pending ? draft.length : queuedStart(draft) + if (!pending) completePrevious(draft, index) draft.splice(index, 0, { type: "message", messageID }) }), ) @@ -138,12 +146,22 @@ export function createSessionRows(sessionID: Accessor) { }), ) - const isQueued = (messageID: string) => { - return data.session.input.has(sessionID(), messageID) + const removeFooter = (messageID: string) => + setRows( + produce((draft) => { + const index = draft.findIndex((row) => row.type === "assistant-footer" && row.messageID === messageID) + if (index !== -1) draft.splice(index, 1) + }), + ) + + const isPending = (messageID: string) => { + const message = data.session.message.get(sessionID(), messageID) + if (message?.type === "user") return data.session.input.has(sessionID(), messageID) + return message?.type === "compaction" && (message.status === "queued" || message.status === "running") } const queuedStart = (rows: SessionRow[]) => { - const index = rows.findIndex((row) => row.type === "message" && isQueued(row.messageID)) + const index = rows.findIndex((row) => row.type === "message" && isPending(row.messageID)) return index === -1 ? rows.length : index } @@ -155,6 +173,7 @@ export function createSessionRows(sessionID: Accessor) { } const subscriptions = [ data.on("session.prompt.admitted", input), + data.on("session.compaction.admitted", input), data.on("session.instructions.updated", message), data.on("session.synthetic", (event) => { if (event.data.sessionID === sessionID() && event.data.description?.trim()) @@ -163,27 +182,35 @@ export function createSessionRows(sessionID: Accessor) { data.on("session.shell.started", message), data.on("session.agent.selected", message), data.on("session.model.selected", message), - data.on("session.compaction.ended", message), + data.on("session.compaction.ended", (event) => { + if (event.data.reason !== "manual") message(event) + }), data.on("session.text.delta", (event) => { if (event.data.sessionID === sessionID()) - appendPart({ messageID: event.data.assistantMessageID, partID: event.data.textID }) + appendPart({ messageID: event.data.assistantMessageID, partID: `text:${event.data.ordinal}` }) }), data.on("session.text.ended", (event) => { if (event.data.sessionID === sessionID() && event.data.text.trim()) - appendPart({ messageID: event.data.assistantMessageID, partID: event.data.textID }) + appendPart({ messageID: event.data.assistantMessageID, partID: `text:${event.data.ordinal}` }) }), data.on("session.reasoning.delta", (event) => { if (event.data.sessionID === sessionID()) - appendPart({ messageID: event.data.assistantMessageID, partID: event.data.reasoningID }) + appendPart({ messageID: event.data.assistantMessageID, partID: `reasoning:${event.data.ordinal}` }) }), data.on("session.reasoning.ended", (event) => { if (event.data.sessionID === sessionID() && event.data.text.trim()) - appendPart({ messageID: event.data.assistantMessageID, partID: event.data.reasoningID }) + appendPart({ messageID: event.data.assistantMessageID, partID: `reasoning:${event.data.ordinal}` }) }), data.on("session.tool.input.started", (event) => { if (event.data.sessionID === sessionID()) appendPart({ messageID: event.data.assistantMessageID, partID: event.data.callID }, event.data.name) }), + data.on("session.retry.scheduled", (event) => { + if (event.data.sessionID === sessionID()) appendFooter(event.data.assistantMessageID) + }), + data.on("session.step.started", (event) => { + if (event.data.sessionID === sessionID()) removeFooter(event.data.assistantMessageID) + }), data.on("session.step.ended", (event) => { if (event.data.sessionID !== sessionID() || ["tool-calls", "unknown"].includes(event.data.finish)) return appendFooter(event.data.assistantMessageID) @@ -199,26 +226,42 @@ export function createSessionRows(sessionID: Accessor) { export function reduceSessionRows(messages: SessionMessage[], inputs = new Set()) { const isInput = (message: SessionMessage) => inputs.has(message.id) - return [...messages.filter((message) => !isInput(message)), ...messages.filter(isInput)].reduce( - (rows, message) => { - if (message.type !== "assistant") { - if (message.type === "synthetic" && !message.description?.trim()) return rows - if (!isInput(message)) completePrevious(rows) - rows.push({ type: "message", messageID: message.id }) - return rows - } - message.content.forEach((part) => { - if ((part.type === "text" || part.type === "reasoning") && !part.text.trim()) return - append(rows, { messageID: message.id, partID: part.id }, part) - }) - if ((message.finish && !["tool-calls", "unknown"].includes(message.finish)) || message.error) { - completePrevious(rows) - rows.push({ type: "assistant-footer", messageID: message.id }) - } - return rows - }, - [], + const pendingCompactions = messages.filter( + (message) => message.type === "compaction" && (message.status === "queued" || message.status === "running"), ) + const pending = new Set([...pendingCompactions.map((message) => message.id), ...inputs]) + return [ + ...messages.filter((message) => !pending.has(message.id)), + ...pendingCompactions, + ...messages.filter(isInput), + ].reduce((rows, message) => { + if (message.type !== "assistant") { + if (message.type === "synthetic" && !message.description?.trim()) return rows + if (!pending.has(message.id)) completePrevious(rows) + rows.push({ type: "message", messageID: message.id }) + return rows + } + const ordinals = { text: 0, reasoning: 0 } + message.content.forEach((part) => { + const partID = part.type === "tool" ? part.id : `${part.type}:${ordinals[part.type]++}` + if ((part.type === "text" || part.type === "reasoning") && !part.text.trim()) return + append(rows, { messageID: message.id, partID }, part) + }) + if ((message.finish && !["tool-calls", "unknown"].includes(message.finish)) || message.error || message.retry) { + completePrevious(rows) + rows.push({ type: "assistant-footer", messageID: message.id }) + } + return rows + }, []) +} + +export function resolvePart(message: SessionMessageAssistant, partID: string) { + const tool = message.content.find((part) => part.type === "tool" && part.id === partID) + if (tool) return tool + const match = /^(text|reasoning):(\d+)$/.exec(partID) + if (!match) return + const ordinal = Number(match[2]) + return message.content.filter((part) => part.type === match[1])[ordinal] } function append(rows: SessionRow[], ref: PartRef, part: SessionMessageAssistant["content"][number]) { diff --git a/packages/tui/test/app-lifecycle.test.tsx b/packages/tui/test/app-lifecycle.test.tsx index 30601bb528..c4302e2790 100644 --- a/packages/tui/test/app-lifecycle.test.tsx +++ b/packages/tui/test/app-lifecycle.test.tsx @@ -58,10 +58,24 @@ test("SIGHUP clears title and disposes scoped resources once", async () => { } }) -test("app.exit prints the session epilogue after scoped cleanup", async () => { +test("session lifecycle updates the terminal title and prints the epilogue after cleanup", async () => { const setup = await createTestRenderer({ width: 80, height: 24, useThread: false }) const core = await import("@opentui/core") mock.module("@opentui/core", () => ({ ...core, createCliRenderer: async () => setup.renderer })) + let initialTitle!: () => void + const initialTitleSet = new Promise((resolve) => { + initialTitle = resolve + }) + let renamedTitle!: () => void + const renamedTitleSet = new Promise((resolve) => { + renamedTitle = resolve + }) + const setTitle = setup.renderer.setTerminalTitle.bind(setup.renderer) + setup.renderer.setTerminalTitle = (title) => { + if (title === "OC | Demo session") initialTitle() + if (title === "OC | Renamed session") renamedTitle() + setTitle(title) + } const events = createEventStream() const calls = createFetch((url) => { if (url.pathname === "/api/session") @@ -100,7 +114,7 @@ test("app.exit prints the session epilogue after scoped cleanup", async () => { client: createClient(calls.fetch), api: createApi(calls.fetch), config: createTuiResolvedConfig({ plugin_enabled: {} }), - args: { continue: true }, + args: { sessionID: "dummy" }, pluginHost: { async start(input) { api = input.api @@ -112,12 +126,19 @@ test("app.exit prints the session epilogue after scoped cleanup", async () => { ) await ready - await setup.renderOnce() - await setup.renderOnce() + await initialTitleSet + events.emit({ + id: "evt_renamed", + created: 1, + type: "session.renamed", + durable: { aggregateID: "dummy", seq: 1, version: 1 }, + data: { sessionID: "dummy", title: "Renamed session" }, + }) + await renamedTitleSet api?.keymap.dispatchCommand("app.exit") await task - expect(stdout).toContain("Demo session") + expect(stdout).toContain("Renamed session") expect(stdout).toContain("opencode -s dummy") } finally { process.stdout.write = originalWrite diff --git a/packages/tui/test/cli/cmd/tui/notifications.test.ts b/packages/tui/test/cli/cmd/tui/notifications.test.ts index 0014292d23..c08b557eee 100644 --- a/packages/tui/test/cli/cmd/tui/notifications.test.ts +++ b/packages/tui/test/cli/cmd/tui/notifications.test.ts @@ -49,6 +49,7 @@ async function setup() { state: { session: { get: (sessionID: string) => sessions[sessionID], + status: () => ({ type: "busy" }), }, }, }), @@ -96,46 +97,34 @@ function durable(sessionID: string) { return { aggregateID: sessionID, seq: 0, version: 1 } } -function stepStarted(id: string, sessionID = "session"): V2Event { +function executionStarted(id: string, sessionID = "session"): V2Event { return { id, created: 0, - type: "session.step.started", + type: "session.execution.started", durable: durable(sessionID), - data: { - sessionID, - assistantMessageID: `msg_${id}`, - agent: "build", - model: { id: "model", providerID: "provider" }, - }, + data: { sessionID }, } } -function stepEnded(id: string, sessionID = "session", finish = "stop"): V2Event { +function executionSucceeded(id: string, sessionID = "session"): V2Event { return { id, created: 0, - type: "session.step.ended", + type: "session.execution.succeeded", durable: durable(sessionID), - data: { - sessionID, - assistantMessageID: `msg_${id}`, - finish, - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - }, + data: { sessionID }, } } -function stepFailed(id: string, sessionID = "session"): V2Event { +function executionFailed(id: string, sessionID = "session"): V2Event { return { id, created: 0, - type: "session.step.failed", + type: "session.execution.failed", durable: durable(sessionID), data: { sessionID, - assistantMessageID: `msg_${id}`, error: { type: "unknown", message: "boom" }, }, } @@ -224,12 +213,12 @@ describe("internal notifications TUI plugin", () => { ]) }) - test("notifies when an active session becomes idle and suppresses no-op idle", async () => { + test("notifies for terminal lifecycle events even when attached after execution started", async () => { const harness = await setup() - harness.emit(stepEnded("event-1")) - harness.emit(stepStarted("event-2")) - harness.emit(stepEnded("event-3")) + harness.emit(executionSucceeded("event-1")) + harness.emit(executionStarted("event-2")) + harness.emit(executionSucceeded("event-3")) expect(harness.notifications).toEqual([ { @@ -238,6 +227,12 @@ describe("internal notifications TUI plugin", () => { notification: { when: "blurred" }, sound: { name: "done", when: "always" }, }, + { + title: "Demo session", + message: "Session done", + notification: { when: "blurred" }, + sound: { name: "done", when: "always" }, + }, ]) }) @@ -250,8 +245,8 @@ describe("internal notifications TUI plugin", () => { type: "form.created", data: { form: form("form-1", "subagent") }, }) - harness.emit(stepStarted("event-2", "subagent")) - harness.emit(stepEnded("event-3", "subagent")) + harness.emit(executionStarted("event-2", "subagent")) + harness.emit(executionSucceeded("event-3", "subagent")) expect(harness.notifications).toEqual([ { @@ -272,14 +267,14 @@ describe("internal notifications TUI plugin", () => { test("notifies session errors once and suppresses the following idle done notification", async () => { const harness = await setup() - harness.emit(stepStarted("event-1")) - harness.emit(stepFailed("event-2")) - harness.emit(stepEnded("event-3")) + harness.emit(executionStarted("event-1")) + harness.emit(executionFailed("event-2")) + harness.emit(executionSucceeded("event-3")) expect(harness.notifications).toEqual([ { title: "Demo session", - message: "Session error", + message: "boom", notification: { when: "blurred" }, sound: { name: "error", when: "always" }, }, @@ -289,20 +284,21 @@ describe("internal notifications TUI plugin", () => { test("special-cases aborts and model response timeouts", async () => { const harness = await setup() - harness.emit(stepStarted("event-1", "abort")) + harness.emit(executionStarted("event-1", "abort")) harness.emit({ id: "event-2", created: 0, type: "session.error", data: { sessionID: "abort", error: { name: "MessageAbortedError", data: { message: "Aborted" } } }, }) - harness.emit(stepStarted("event-3", "timeout")) + harness.emit(executionStarted("event-3", "timeout")) harness.emit({ id: "event-4", created: 0, type: "session.error", data: { sessionID: "timeout", error: { name: "UnknownError", data: { message: "SSE read timed out" } } }, }) + harness.emit(executionFailed("event-5", "timeout")) expect(harness.notifications).toEqual([ { diff --git a/packages/tui/test/cli/tui/data.test.tsx b/packages/tui/test/cli/tui/data.test.tsx index 6bd413b39d..b7b349a196 100644 --- a/packages/tui/test/cli/tui/data.test.tsx +++ b/packages/tui/test/cli/tui/data.test.tsx @@ -8,7 +8,7 @@ import { onMount } from "solid-js" import { ProjectProvider } from "../../../src/context/project" import { SDKProvider } from "../../../src/context/sdk" import { DataProvider, useData } from "../../../src/context/data" -import { createSessionRows } from "../../../src/routes/session/rows" +import { createSessionRows, type SessionRow } from "../../../src/routes/session/rows" import { createApi, createClient, createEventStream, createFetch, directory, json } from "../../fixture/tui-sdk" import { TestTuiContexts } from "../../fixture/tui-environment" @@ -108,6 +108,64 @@ test("refreshes resources into reactive getters", async () => { } }) +test("restores running manual compaction before applying live deltas", async () => { + const events = createEventStream() + const calls = createFetch((url) => { + if (url.pathname === "/api/session/session-compaction/message") + return json({ + data: [ + { + id: "message-compaction", + type: "compaction", + status: "running", + reason: "manual", + summary: "Existing ", + recent: "", + time: { created: 1 }, + }, + ], + cursor: {}, + }) + }, events) + let data!: ReturnType + + function Probe() { + data = useData() + return + } + + const app = await testRender(() => ( + + + + + + + + + + )) + + try { + await data.session.message.refresh("session-compaction") + expect(data.session.compaction("session-compaction")).toBe("Existing ") + + emitEvent(events, { + id: "evt_compaction_delta", + created: 2, + type: "session.compaction.delta", + data: { sessionID: "session-compaction", text: "summary" }, + }) + + await wait(() => { + const message = data.session.message.get("session-compaction", "message-compaction") + return message?.type === "compaction" && message.summary === "Existing summary" + }) + } finally { + app.renderer.destroy() + } +}) + test("reconnects the event stream and bootstraps fresh data", async () => { const events = createEventStream() const requests = { active: 0, event: 0, model: 0 } @@ -177,16 +235,11 @@ test("reconnects the event stream and bootstraps fresh data", async () => { await wait(() => requests.active === 2 && data.connection.status() === "connected", 4000) emitEvent(events, { - id: "evt_step_started_after_reconnect", + id: "evt_execution_started_after_reconnect", created: 1, - type: "session.step.started", + type: "session.execution.started", durable: durable("session-new"), - data: { - sessionID: "session-new", - assistantMessageID: "message-new", - agent: "build", - model: { id: "model", providerID: "provider" }, - }, + data: { sessionID: "session-new" }, }) await wait(() => data.session.status("session-new") === "running") resolveActive(json({ data: {} })) @@ -326,7 +379,7 @@ test("removes committed revert messages from local state", async () => { created: 3, type: "session.revert.committed", durable: durable(sessionID, 3), - data: { sessionID, messageID: "msg_002" }, + data: { sessionID, to: "msg_002" }, }) await wait(() => data.session.message.ids(sessionID).length === 1) @@ -406,9 +459,13 @@ test("tracks session status from active sessions and execution events", async () if (url.pathname === "/api/session/active") return json({ data: { "session-active": { type: "running" } } }) }, events) let data!: ReturnType + let rows!: SessionRow[] + let manualRows!: SessionRow[] function Probe() { data = useData() + rows = createSessionRows(() => "session-retry") + manualRows = createSessionRows(() => "session-manual") return } @@ -428,6 +485,15 @@ test("tracks session status from active sessions and execution events", async () await wait(() => data.session.status("session-active") === "running") expect(data.session.status("session-idle")).toBe("idle") + emitEvent(events, { + id: "evt_execution_started", + created: 0, + type: "session.execution.started", + durable: durable("session-live"), + data: { sessionID: "session-live" }, + }) + await wait(() => data.session.status("session-live") === "running") + emitEvent(events, { id: "evt_step_started", created: 0, @@ -440,8 +506,6 @@ test("tracks session status from active sessions and execution events", async () model: { id: "model", providerID: "provider" }, }, }) - await wait(() => data.session.status("session-live") === "running") - emitEvent(events, { id: "evt_step_ended", created: 0, @@ -462,16 +526,23 @@ test("tracks session status from active sessions and execution events", async () expect(data.session.status("session-live")).toBe("running") emitEvent(events, { - id: "evt_execution_settled", + id: "evt_execution_succeeded", created: 0, - type: "session.execution.settled", - data: { - sessionID: "session-live", - outcome: "success", - }, + type: "session.execution.succeeded", + durable: durable("session-live", 1, 3), + data: { sessionID: "session-live" }, }) await wait(() => data.session.status("session-live") === "idle") + emitEvent(events, { + id: "evt_failed_execution_started", + created: 0, + type: "session.execution.started", + durable: durable("session-failed"), + data: { sessionID: "session-failed" }, + }) + await wait(() => data.session.status("session-failed") === "running") + emitEvent(events, { id: "evt_failed_step_started", created: 0, @@ -484,8 +555,6 @@ test("tracks session status from active sessions and execution events", async () model: { id: "model", providerID: "provider" }, }, }) - await wait(() => data.session.status("session-failed") === "running") - emitEvent(events, { id: "evt_step_failed", created: 0, @@ -494,26 +563,189 @@ test("tracks session status from active sessions and execution events", async () data: { sessionID: "session-failed", assistantMessageID: "message-failed", - error: { type: "unknown", message: "Provider unavailable" }, + error: { type: "provider.content-filter", message: "Provider blocked the response" }, }, }) await wait(() => { const assistant = data.session.message.get("session-failed", "message-failed") - return assistant?.type === "assistant" && assistant.finish === "error" + return ( + assistant?.type === "assistant" && + assistant.finish === "error" && + assistant.error?.type === "provider.content-filter" + ) }) expect(data.session.status("session-failed")).toBe("running") emitEvent(events, { - id: "evt_failed_execution_settled", + id: "evt_failed_execution_failed", created: 0, - type: "session.execution.settled", + type: "session.execution.failed", + durable: durable("session-failed", 1, 3), data: { sessionID: "session-failed", - outcome: "failure", - error: { type: "unknown", message: "Provider unavailable" }, + error: { type: "provider.content-filter", message: "Provider blocked the response" }, }, }) await wait(() => data.session.status("session-failed") === "idle") + + emitEvent(events, { + id: "evt_retry_execution_started", + created: 0, + type: "session.execution.started", + durable: durable("session-retry"), + data: { sessionID: "session-retry" }, + }) + emitEvent(events, { + id: "evt_retry_step_started", + created: 0, + type: "session.step.started", + durable: durable("session-retry", 1, 2), + data: { + sessionID: "session-retry", + assistantMessageID: "message-retry", + agent: "build", + model: { id: "model", providerID: "provider" }, + }, + }) + emitEvent(events, { + id: "evt_retry_scheduled", + created: 0, + type: "session.retry.scheduled", + durable: durable("session-retry", 1, 3), + data: { + sessionID: "session-retry", + assistantMessageID: "message-retry", + attempt: 2, + at: 2_000, + error: { type: "provider.transport", message: "Disconnected" }, + }, + }) + await wait(() => { + const assistant = data.session.message.get("session-retry", "message-retry") + return assistant?.type === "assistant" && assistant.retry?.attempt === 2 + }) + await wait(() => rows.some((row) => row.type === "assistant-footer" && row.messageID === "message-retry")) + emitEvent(events, { + id: "evt_retry_next_step", + created: 2_000, + type: "session.step.started", + durable: durable("session-retry", 1, 4), + data: { + sessionID: "session-retry", + assistantMessageID: "message-retry", + agent: "build", + model: { id: "model", providerID: "provider" }, + }, + }) + await wait(() => { + const assistant = data.session.message.get("session-retry", "message-retry") + return assistant?.type === "assistant" && assistant.retry === undefined + }) + await wait(() => !rows.some((row) => row.type === "assistant-footer" && row.messageID === "message-retry")) + expect(data.session.message.list("session-retry").filter((message) => message.type === "assistant")).toHaveLength(1) + emitEvent(events, { + id: "evt_retry_scheduled_again", + created: 2_000, + type: "session.retry.scheduled", + durable: durable("session-retry", 1, 5), + data: { + sessionID: "session-retry", + assistantMessageID: "message-retry", + attempt: 3, + at: 6_000, + error: { type: "provider.transport", message: "Disconnected again" }, + }, + }) + await wait(() => { + const assistant = data.session.message.get("session-retry", "message-retry") + return assistant?.type === "assistant" && assistant.retry?.attempt === 3 + }) + emitEvent(events, { + id: "evt_retry_interrupted", + created: 2_000, + type: "session.execution.interrupted", + durable: durable("session-retry", 1, 6), + data: { sessionID: "session-retry", reason: "shutdown" }, + }) + await wait(() => data.session.status("session-retry") === "idle") + expect(data.session.message.get("session-retry", "message-retry")).not.toHaveProperty("retry") + + emitEvent(events, { + id: "evt_compaction_admitted", + created: 0, + type: "session.compaction.admitted", + durable: durable("session-manual", 1), + data: { sessionID: "session-manual", inputID: "message-compaction" }, + }) + await wait(() => { + const message = data.session.message.get("session-manual", "message-compaction") + return message?.type === "compaction" && message.status === "queued" + }) + emitEvent(events, { + id: "evt_manual_compaction_started", + created: 1, + type: "session.compaction.started", + durable: durable("session-manual", 2), + data: { sessionID: "session-manual", reason: "manual" }, + }) + emitEvent(events, { + id: "evt_manual_compaction_delta", + created: 2, + type: "session.compaction.delta", + data: { sessionID: "session-manual", text: "Streamed summary" }, + }) + await wait(() => { + const message = data.session.message.get("session-manual", "message-compaction") + return message?.type === "compaction" && message.summary === "Streamed summary" + }) + emitEvent(events, { + id: "evt_manual_compaction_ended", + created: 3, + type: "session.compaction.ended", + durable: durable("session-manual", 3), + data: { sessionID: "session-manual", reason: "manual", text: "Streamed summary", recent: "recent" }, + }) + await wait(() => { + const message = data.session.message.get("session-manual", "message-compaction") + return message?.type === "compaction" && message.status === "completed" + }) + expect(manualRows.filter((row) => row.type === "message")).toEqual([ + { type: "message", messageID: "message-compaction" }, + ]) + + emitEvent(events, { + id: "evt_compaction_started", + created: 0, + type: "session.compaction.started", + durable: durable("session-live", 2), + data: { sessionID: "session-live", reason: "auto" }, + }) + emitEvent(events, { + id: "evt_compaction_delta_1", + created: 0, + type: "session.compaction.delta", + data: { sessionID: "session-live", text: "Live " }, + }) + emitEvent(events, { + id: "evt_compaction_delta_2", + created: 0, + type: "session.compaction.delta", + data: { sessionID: "session-live", text: "summary" }, + }) + await wait(() => data.session.compaction("session-live") === "Live summary") + + emitEvent(events, { + id: "evt_compaction_ended", + created: 0, + type: "session.compaction.ended", + durable: durable("session-live", 3), + data: { sessionID: "session-live", reason: "auto", text: "Live summary", recent: "recent" }, + }) + await wait(() => data.session.compaction("session-live") === undefined) + expect(data.session.message.get("session-live", "msg_compaction_ended")).toMatchObject({ + type: "compaction", + summary: "Live summary", + }) } finally { app.renderer.destroy() } @@ -1020,9 +1252,9 @@ test("settles pending tools when a live failure arrives", async () => { sessionID: "session-1", assistantMessageID: "msg_explicit_assistant_9", callID: "call-1", - tool: "bash", input: {}, - provider: { executed: false, metadata: { fake: { call: true } } }, + executed: false, + state: { call: true }, }, }) emitEvent(events, { @@ -1035,7 +1267,8 @@ test("settles pending tools when a live failure arrives", async () => { assistantMessageID: "msg_explicit_assistant_9", callID: "call-1", error: { type: "unknown", message: "aborted" }, - provider: { executed: false, metadata: { fake: { result: true } } }, + executed: false, + resultState: { result: true }, }, }) @@ -1061,11 +1294,9 @@ test("settles pending tools when a live failure arrives", async () => { expect(tool.state.input).toEqual({}) expect(tool.state.structured).toEqual({}) expect(tool.state.content).toEqual([]) - expect(tool.provider).toEqual({ - executed: false, - metadata: { fake: { call: true } }, - resultMetadata: { fake: { result: true } }, - }) + expect(tool.executed).toBe(false) + expect(tool.providerState).toEqual({ call: true }) + expect(tool.providerResultState).toEqual({ result: true }) expect(sync.session.message.list("session-1").map((message) => message.type)).toEqual([ "agent-switched", "model-switched", diff --git a/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx b/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx index f9a657ecc4..402db8b5fe 100644 --- a/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx +++ b/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx @@ -89,6 +89,19 @@ function FailedCompleteToolFixture() { ) } +function ReminderAlignmentFixture() { + return ( + + + Switched variant to medium + + + Instructions updated + + + ) +} + async function renderFrame(component: () => JSX.Element, options: { width: number; height: number }) { testSetup = await testRender(component, options) await testSetup.renderOnce() @@ -124,6 +137,12 @@ describe("TUI inline tool wrapping", () => { expect(frame).not.toContain("Read failed") }) + test("aligns switch reminders with instruction reminders", async () => { + expect(await renderFrame(() => , { width: 35, height: 2 })).toBe( + " Switched variant to medium\n ◈ Instructions updated", + ) + }) + test("filters malformed nested tool wire data", () => { expect( parseApplyPatchFiles([ diff --git a/packages/tui/test/cli/tui/session-rows.test.ts b/packages/tui/test/cli/tui/session-rows.test.ts index fec6f5eb87..362ab40369 100644 --- a/packages/tui/test/cli/tui/session-rows.test.ts +++ b/packages/tui/test/cli/tui/session-rows.test.ts @@ -6,19 +6,19 @@ test("groups exploration parts across assistant messages until a delimiter", () const messages: SessionMessage[] = [ { type: "user", id: "user-1", text: "Explore", time: { created: 0 } }, assistant("assistant-1", [ - { type: "text", id: "text-1", text: "Looking" }, + { type: "text", text: "Looking" }, { type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 2 } }, { type: "tool", id: "glob-1", name: "glob", state: pending(), time: { created: 3 } }, ]), assistant("assistant-2", [ { type: "tool", id: "grep-1", name: "grep", state: pending(), time: { created: 5 } }, - { type: "text", id: "text-2", text: "Done" }, + { type: "text", text: "Done" }, ]), ] expect(reduceSessionRows(messages)).toEqual([ { type: "message", messageID: "user-1" }, - { type: "part", ref: { messageID: "assistant-1", partID: "text-1" } }, + { type: "part", ref: { messageID: "assistant-1", partID: "text:0" } }, { type: "group", kind: "exploration", @@ -30,7 +30,7 @@ test("groups exploration parts across assistant messages until a delimiter", () { messageID: "assistant-2", partID: "grep-1" }, ], }, - { type: "part", ref: { messageID: "assistant-2", partID: "text-2" } }, + { type: "part", ref: { messageID: "assistant-2", partID: "text:0" } }, ]) }) @@ -62,20 +62,38 @@ test("keeps non-exploration tools as individual part rows", () => { ]) }) +test("assigns stable kind ordinals within an assistant message", () => { + const messages: SessionMessage[] = [ + assistant("assistant-1", [ + { type: "text", text: "First" }, + { type: "reasoning", text: "Think" }, + { type: "text", text: "Second" }, + { type: "reasoning", text: "Check" }, + ]), + ] + + expect(reduceSessionRows(messages)).toEqual([ + { type: "part", ref: { messageID: "assistant-1", partID: "text:0" } }, + { type: "part", ref: { messageID: "assistant-1", partID: "reasoning:0" } }, + { type: "part", ref: { messageID: "assistant-1", partID: "text:1" } }, + { type: "part", ref: { messageID: "assistant-1", partID: "reasoning:1" } }, + ]) +}) + test("groups across empty assistant reasoning parts", () => { const messages: SessionMessage[] = [ assistant("assistant-1", [ - { type: "reasoning", id: "reasoning-1", text: "Looking" }, + { type: "reasoning", text: "Looking" }, { type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 2 } }, ]), assistant("assistant-2", [ - { type: "reasoning", id: "reasoning-2", text: "" }, + { type: "reasoning", text: "" }, { type: "tool", id: "grep-1", name: "grep", state: pending(), time: { created: 3 } }, ]), ] expect(reduceSessionRows(messages)).toEqual([ - { type: "part", ref: { messageID: "assistant-1", partID: "reasoning-1" } }, + { type: "part", ref: { messageID: "assistant-1", partID: "reasoning:0" } }, { type: "group", kind: "exploration", @@ -95,9 +113,7 @@ test("completes exploration groups when another row follows", () => { ]) finished.finish = "stop" const messages: SessionMessage[] = [ - assistant("assistant-1", [ - { type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 1 } }, - ]), + assistant("assistant-1", [{ type: "tool", id: "read-1", name: "read", state: pending(), time: { created: 1 } }]), { type: "user", id: "user-1", text: "Continue", time: { created: 2 } }, finished, ] @@ -182,6 +198,45 @@ test("renders synthetic messages with descriptions", () => { ]) }) +test("renders a footer for a pre-output retry assistant after replay", () => { + const message = assistant("assistant-retry", []) + message.retry = { + attempt: 2, + at: 2_000, + error: { type: "provider.transport", message: "Disconnected" }, + } + + expect(reduceSessionRows([message])).toEqual([{ type: "assistant-footer", messageID: "assistant-retry" }]) +}) + +test("places a pending compaction barrier before every queued user message", () => { + const queued = (id: string, text: string, created: number): SessionMessage => ({ + type: "user", + id, + text, + time: { created }, + }) + const messages: SessionMessage[] = [ + queued("user-before", "Before", 1), + { + type: "compaction", + id: "compaction", + status: "queued", + reason: "manual", + summary: "", + recent: "", + time: { created: 2 }, + }, + queued("user-after", "After", 3), + ] + + expect(reduceSessionRows(messages, new Set(["user-before", "user-after"]))).toEqual([ + { type: "message", messageID: "compaction" }, + { type: "message", messageID: "user-before" }, + { type: "message", messageID: "user-after" }, + ]) +}) + function assistant(id: string, content: SessionMessageAssistant["content"]): SessionMessageAssistant { return { type: "assistant", diff --git a/script/profile-typecheck-packages.ts b/script/profile-typecheck-packages.ts new file mode 100644 index 0000000000..0d03b1c1bd --- /dev/null +++ b/script/profile-typecheck-packages.ts @@ -0,0 +1,66 @@ +#!/usr/bin/env bun + +import path from "path" + +const root = path.resolve(import.meta.dir, "..") +const proc = Bun.spawn( + [ + "bun", + "turbo", + "typecheck", + "--concurrency=1", + "--force", + "--continue=always", + "--summarize", + "--output-logs=errors-only", + ], + { + cwd: root, + stdout: "pipe", + stderr: "pipe", + }, +) +const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]) +const output = stdout + stderr +if (exitCode !== 0) { + process.stdout.write(stdout) + process.stderr.write(stderr) + process.exit(exitCode) +} + +const summary = output.match(/Summary:\s+(.+\.json)/)?.[1]?.trim() +if (!summary) { + process.stdout.write(stdout) + process.stderr.write(stderr) + throw new Error("Turbo did not report a run summary") +} + +const report = (await Bun.file(summary).json()) as { + tasks: Array<{ + taskId: string + execution: { startTime: number; endTime: number; exitCode: number } | null + }> +} +const tasks = report.tasks + .flatMap((task) => + task.execution + ? [ + { + task: task.taskId.replace(/#typecheck$/, ""), + durationMs: task.execution.endTime - task.execution.startTime, + }, + ] + : [], + ) + .sort((a, b) => b.durationMs - a.durationMs) +const total = tasks.reduce((duration, task) => duration + task.durationMs, 0) +const width = Math.max(...tasks.map((task) => task.task.length), "Package".length) + +console.log(`Package${" ".repeat(width - "Package".length)} Time Share`) +tasks.forEach((task) => { + const duration = `${(task.durationMs / 1000).toFixed(2)}s`.padStart(7) + const share = `${((task.durationMs / total) * 100).toFixed(1)}%`.padStart(6) + console.log(`${task.task.padEnd(width)} ${duration} ${share}`) +}) +console.log(`\nTotal serial task time: ${(total / 1000).toFixed(2)}s`) +console.log(`Turbo summary: ${path.relative(root, summary)}`) diff --git a/script/profile-typecheck.ts b/script/profile-typecheck.ts new file mode 100644 index 0000000000..ac093847bc --- /dev/null +++ b/script/profile-typecheck.ts @@ -0,0 +1,172 @@ +#!/usr/bin/env bun + +import { mkdir } from "fs/promises" +import path from "path" + +if (process.platform !== "darwin") throw new Error("System typecheck profiling currently supports macOS only") + +const root = path.resolve(import.meta.dir, "..") +const startedAt = new Date() +const args = Bun.argv.slice(2) +const command = [ + "bun", + "turbo", + "typecheck", + ...(args.some((arg) => arg.startsWith("--concurrency")) ? [] : ["--concurrency=3"]), + ...args, +] +const before = systemSnapshot() +const proc = Bun.spawn(command, { + cwd: root, + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", +}) +const samples = [processTreeSnapshot(proc.pid, startedAt)] +const timer = setInterval(() => samples.push(processTreeSnapshot(proc.pid, startedAt)), 200) +const exitCode = await proc.exited +clearInterval(timer) +samples.push(processTreeSnapshot(proc.pid, startedAt)) + +const finishedAt = new Date() +const after = systemSnapshot() +const active = samples.filter((sample) => sample.processes > 0) +const report = { + command, + cwd: root, + startedAt: startedAt.toISOString(), + finishedAt: finishedAt.toISOString(), + durationSeconds: (finishedAt.getTime() - startedAt.getTime()) / 1000, + exitCode, + summary: { + peakCpuPercent: Math.max(0, ...active.map((sample) => sample.cpuPercent)), + averageCpuPercent: average(active.map((sample) => sample.cpuPercent)), + peakAggregateRssMB: Math.max(0, ...active.map((sample) => sample.aggregateRssMB)), + peakProcesses: Math.max(0, ...active.map((sample) => sample.processes)), + peakTsgoRelatedProcesses: Math.max(0, ...active.map((sample) => sample.tsgoRelatedProcesses)), + swapDeltaMB: after.swapUsedMB - before.swapUsedMB, + compressedMemoryDeltaMB: after.compressedMemoryMB - before.compressedMemoryMB, + pageoutDelta: after.pageouts - before.pageouts, + }, + system: { before, after }, + samples, +} + +const directory = path.join(root, ".typecheck-profiles") +const file = path.join(directory, `${startedAt.toISOString().replaceAll(":", "-")}.json`) +await mkdir(directory, { recursive: true }) +await Bun.write(file, JSON.stringify(report, null, 2) + "\n") + +console.log(` +Typecheck profile + Duration: ${report.durationSeconds.toFixed(1)}s + Average CPU: ${report.summary.averageCpuPercent.toFixed(0)}% + Peak CPU: ${report.summary.peakCpuPercent.toFixed(0)}% + Aggregate RSS: ${report.summary.peakAggregateRssMB.toFixed(0)} MB + Peak processes: ${report.summary.peakProcesses} (${report.summary.peakTsgoRelatedProcesses} tsgo-related) + Swap delta: ${signed(report.summary.swapDeltaMB)} MB + Compressed: ${signed(report.summary.compressedMemoryDeltaMB)} MB + Pageouts: ${signed(report.summary.pageoutDelta)} + Report: ${path.relative(root, file)} +`) + +process.exit(exitCode) + +function processTreeSnapshot(rootPID: number, startedAt: Date) { + const processes = processList() + const pids = new Set([rootPID]) + const pending = [rootPID] + while (pending.length > 0) { + const parent = pending.shift() + processes + .filter((process) => process.ppid === parent && !pids.has(process.pid)) + .forEach((process) => { + pids.add(process.pid) + pending.push(process.pid) + }) + } + const tree = processes.filter((process) => pids.has(process.pid)) + return { + elapsedSeconds: (Date.now() - startedAt.getTime()) / 1000, + processes: tree.length, + tsgoRelatedProcesses: tree.filter((process) => /\btsgo\b/.test(process.command)).length, + cpuPercent: sum(tree.map((process) => process.cpuPercent)), + aggregateRssMB: sum(tree.map((process) => process.rssKB)) / 1024, + } +} + +function systemSnapshot() { + const vm = text(["vm_stat"]) + const pageSize = Number(vm.match(/page size of (\d+) bytes/)?.[1] ?? 4096) + const fields = Object.fromEntries( + vm + .split("\n") + .map((line) => line.match(/^([^:]+):\s+(\d+)\.?$/)) + .filter((match): match is RegExpMatchArray => match !== null) + .map((match) => [match[1], Number(match[2])]), + ) + const swap = text(["sysctl", "-n", "vm.swapusage"]) + return { + loadAverage: text(["sysctl", "-n", "vm.loadavg"]).trim(), + thermalState: text(["pmset", "-g", "therm"]).trim(), + swapUsedMB: Number(swap.match(/used = ([\d.]+)M/)?.[1] ?? 0), + freeMemoryMB: ((fields["Pages free"] ?? 0) * pageSize) / 1024 / 1024, + compressedMemoryMB: ((fields["Pages occupied by compressor"] ?? 0) * pageSize) / 1024 / 1024, + pageouts: fields.Pageouts ?? 0, + relevantProcesses: processList() + .filter((process) => + /opencode|tsgo|tsserver|vtsls|eslintServer|tailwindcss-language-server/.test(process.command), + ) + .sort((a, b) => b.rssKB - a.rssKB) + .map(processSummary), + topCpuProcesses: processList() + .sort((a, b) => b.cpuPercent - a.cpuPercent) + .slice(0, 15) + .map(processSummary), + topMemoryProcesses: processList() + .sort((a, b) => b.rssKB - a.rssKB) + .slice(0, 15) + .map(processSummary), + } +} + +function processSummary(process: ReturnType[number]) { + return { + pid: process.pid, + ppid: process.ppid, + rssMB: process.rssKB / 1024, + cpuPercent: process.cpuPercent, + command: process.command, + } +} + +function processList() { + return text(["ps", "-axo", "pid=,ppid=,rss=,%cpu=,command="]) + .split("\n") + .map((line) => line.trim().match(/^(\d+)\s+(\d+)\s+(\d+)\s+([\d.]+)\s+(.*)$/)) + .filter((match): match is RegExpMatchArray => match !== null) + .map((match) => ({ + pid: Number(match[1]), + ppid: Number(match[2]), + rssKB: Number(match[3]), + cpuPercent: Number(match[4]), + command: match[5], + })) +} + +function text(command: string[]) { + return Bun.spawnSync(command).stdout.toString() +} + +function sum(values: number[]) { + return values.reduce((total, value) => total + value, 0) +} + +function average(values: number[]) { + if (values.length === 0) return 0 + return sum(values) / values.length +} + +function signed(value: number) { + return `${value >= 0 ? "+" : ""}${value.toFixed(0)}` +} diff --git a/specs/v2/config.md b/specs/v2/config.md index 96b0f3ac90..9a7afee3a0 100644 --- a/specs/v2/config.md +++ b/specs/v2/config.md @@ -312,12 +312,12 @@ External protocol and server integration configuration. Keep the opencode MCP server entry format instead of adopting the common `mcpServers` copy/paste shape. Local servers remain explicit `type: "local"` entries with command arrays and `environment`; remote servers remain explicit `type: "remote"` entries with `url`, `headers`, and optional `oauth`. Nest the server map under `mcp.servers` so protocol-wide settings such as timeout defaults can live under the same subsystem. -MCP timeouts have separate startup and request budgets, expressed in milliseconds. `startup` covers establishing the transport and completing MCP initialization. `request` applies independently to each post-initialization MCP request. A server may override either default without repeating the other. +MCP timeouts have separate startup, catalog, and execution budgets, expressed in milliseconds. `startup` covers establishing the transport and completing MCP initialization. `catalog` applies independently to discovery requests such as listing tools and prompts. `execution` covers potentially interactive operations such as tool calls and prompt evaluation. A server may override any default without repeating the others. ```jsonc { "mcp": { - "timeout": { "startup": 30000, "request": 300000 }, + "timeout": { "startup": 30000, "catalog": 30000, "execution": 43200000 }, "servers": { "github": { "type": "local", @@ -338,7 +338,7 @@ MCP timeouts have separate startup and request budgets, expressed in millisecond "redirect_uri": "http://127.0.0.1:19876/mcp/oauth/callback", }, "disabled": false, - "timeout": { "request": 600000 }, + "timeout": { "execution": 600000 }, }, }, }, @@ -380,7 +380,7 @@ Fields that should not be ported by inertia; each needs an explicit justificatio | `experimental.openTelemetry` | Enable AI SDK telemetry spans | remove | Do not port; observability is process-level and should use standard OpenTelemetry environment or declarative configuration. | | `experimental.primary_tools` | Restrict tools to primary agents | remove | Do not port obsolete gating; agent tool access is configured through permissions. | | `experimental.continue_loop_on_deny` | Continue loop after denied tool call | remove | Do not port legacy denied-tool loop behavior. | -| `experimental.mcp_timeout` | MCP request timeout | redesign | Move to `mcp.timeout.request` for the default and `mcp.servers..timeout.request` for per-server overrides. | +| `experimental.mcp_timeout` | MCP request timeout | redesign | Migrate to both `mcp.timeout.catalog` and `mcp.timeout.execution`, with corresponding per-server overrides. | ## Review Order diff --git a/specs/v2/schema-changelog.md b/specs/v2/schema-changelog.md index 62c85ef3e8..fa7170ffbd 100644 --- a/specs/v2/schema-changelog.md +++ b/specs/v2/schema-changelog.md @@ -13,6 +13,28 @@ Compatibility: - Existing changelog entries retain the names that were accurate when those changes occurred. - Behavior is unchanged: this is a vocabulary and contract rename only. +## 2026-07-04: Canonicalize Generated Shell Type Name + +- Collapse the legacy JavaScript SDK generator's equivalent `Shell1V2` component into the canonical `ShellV2` contract. + +Compatibility: + +- The Shell wire shape is unchanged. Generated event and endpoint types now consistently reference `ShellV2`. + +## 2026-07-03: Add Execution Lifecycle, Retry, And Structured Session Errors + +- Replace live-only `session.execution.settled` and unused `session.retried` with durable v1 `session.execution.started`, `session.execution.succeeded`, `session.execution.failed`, `session.execution.interrupted`, and `session.retry.scheduled` events. +- Add an open `SessionError` wire envelope with dot-cased type values and the browser-safe `FinishReason` contract. +- Project retry state onto the current assistant and classify content-filter finishes as failed steps. +- Reuse one projected assistant across pre-output retry steps; each provider call remains a distinct step and consumes agent allowance. + +Compatibility: + +- Experimental V2 event, sequence, input, and message-projection rows are reset. Durable event contracts restart at v1. +- `SessionError.type` remains an open string so new error classifications do not require event-version or database migrations. Unknown fields are ignored by older decoders; richer public details require a separate compatibility design. +- Execution lifecycle events are historical observations of one process-local coordinator busy period. Unmatched starts never establish current liveness or recovery work; `/api/session/active` remains the current-process liveness authority. +- Scheduled retries are historical UI state after a crash and never trigger provider recovery. + ## 2026-07-03: Require Durable Envelope On Durable Events - Make the wire `durable` envelope required on durable event definitions. @@ -952,3 +974,17 @@ Compatibility: Compatibility: - V2 durable events and projections are experimental and are reset by `20260703190000_reset_v2_shell_event_payloads`; existing V2 event rows, event sequences, projected session messages, and admitted inputs are wiped. + +## 2026-07-03: Simplify Assistant Fragments And Provider State + +- Remove provider block IDs from current Session text and reasoning event payloads and projected content. A Session assistant step allows at most one open fragment of each kind; events carry a Session-assigned kind-specific ordinal so live updates and hydrated projections share the stable `(assistantMessageID, kind, ordinal)` reference. Tool correlation continues to use `callID`. +- Replace nested `providerMetadata` with opaque, provider-un-nested `state` at the Session boundary. Reasoning uses `state`; tool calls use `state`, settlements use `resultState`, and projected tools expose `providerState` and `providerResultState`. Replay re-nests state under the selected provider only for the same successful model. +- Flatten `executed` on tool events and projected tools, and remove the redundant tool name from `session.tool.called` because `session.tool.input.started` owns it. +- Rename `session.revert.committed.messageID` to `to`, paired with `session.forked.from`. +- Publish live-only `session.compaction.delta` events for accepted summary chunks while `session.compaction.ended.1` remains the durable full summary. +- OpenAI Responses closes output text on `response.output_text.done` or its message `response.output_item.done` boundary. Provider documentation and recorded stream shapes show sequential output items, not valid overlapping text or reasoning blocks of the same kind. + +Compatibility: + +- All changed durable definitions remain version 1. `20260703200000_reset_v2_session_events` performs the single reset for this Session event contract update, wiping experimental V2 events, sequences, projected messages, and admitted inputs. +- Promise, Effect, and legacy JavaScript SDK surfaces are regenerated from the simplified schemas. diff --git a/specs/v2/session.md b/specs/v2/session.md index 1fea99dcb6..511ece8cfc 100644 --- a/specs/v2/session.md +++ b/specs/v2/session.md @@ -21,7 +21,7 @@ sessions.prompt({ id?, sessionID, prompt, delivery?, resume? }) sessions.interrupt(sessionID) -> interrupts active execution on this process - -> waits for runner cleanup and settlement + -> waits for runner cleanup and a terminal lifecycle observation -> clears a coalesced follow-up wake already registered with this coordinator -> preserves durable inbox rows for a later wake or resume -> idle or missing Session is a no-op @@ -32,7 +32,7 @@ sessions.active() -> absence means inactive; activity is not durable across process restarts ``` -`session_input` is the durable admission inbox. `PromptAdmitted` records and projects accepted input so pending queue state can be replayed, replicated, and observed by clients. Admitted inputs remain outside model-visible Session history until the serialized runner publishes `Prompted`. Its projector atomically writes the visible user message and marks the inbox row promoted in the same event transaction. The V1-to-V2 shadow bridge publishes the same `Prompted` event for already-visible V1 prompts. +`session_input` is the typed durable admission inbox for prompts and Session control operations. `PromptAdmitted` records accepted user input; `Compaction.Admitted` records one coalesced manual compaction barrier. Admitted prompts remain outside model-visible Session history until the serialized runner publishes `Prompted`. Its projector atomically writes the visible user message and marks the inbox row promoted in the same event transaction. A pending compaction blocks all unpromoted prompts, runs before the Session would otherwise become idle, and releases the backlog only after its durable ended or failed event settles the barrier. The V1-to-V2 shadow bridge publishes the same `Prompted` event for already-visible V1 prompts. `admittedSeq` is the durable Session event sequence of `PromptAdmitted`. Clients may use the admission event to represent queued input before `Prompted` makes it part of visible conversation history. @@ -47,7 +47,15 @@ SessionExecution.resume(sessionID) `SessionExecution` and the read-side `SessionStore` are process-global. `SessionRunner`, catalog, model resolver, tool registry, permission state, and filesystem are cached per Location. No layer takes a Session ID. An omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics. -The local runner issues one explicit `llm.stream(request)` per step, projects each complete local tool call durably before eagerly starting its structured child execution, awaits every started tool fiber after provider-stream closure, and reloads projected history once before continuation. Promoting any new user input resets the selected agent's configured step allowance; multiple steers promoted at one boundary reset it once. Tool settlement events carry the owning assistant message ID because provider-local call IDs may repeat across steps. Before assembling a provider request, the runner durably fails any local tool still projected as `running` from a previous process with `Tool execution interrupted`; abandoned side effects are never silently replayed. +The local runner issues one explicit `llm.stream(request)` per step, projects each complete local tool call durably before eagerly starting its structured child execution, awaits every owned tool fiber after provider-stream closure, and reloads projected history once before continuation. For every in-process step, `session.step.started` precedes its tool calls, every local and hosted call settles as `session.tool.success` or `session.tool.failed`, and only then may the runner publish the single terminal `session.step.ended` or `session.step.failed`. Streamed provider-error evidence is retained until this closeout; thrown provider failures and interruption use the same settlement-first ordering. Promoting any new user input resets the selected agent's configured step allowance; multiple steers promoted at one boundary reset it once. + +`callID` is unique only within its owning step, not across the Session. Tool events therefore carry `assistantMessageID`, and consumers correlate a call through the step that owns that assistant message rather than inventing a synthetic composite key. Before assembling a provider request, the runner's cross-drain `failInterruptedTools` recovery durably fails any tool still projected as pending or running from a previous process with `Tool execution interrupted`. This orphan-recovery sweep is the explicit nesting exception: it occurs in a later drain, but attributes every settlement to the original `assistantMessageID`; abandoned side effects are never silently replayed. + +`session.execution.started.1` and exactly one of `session.execution.succeeded.1`, `session.execution.failed.1`, or `session.execution.interrupted.1` observe one process-local coordinator busy period, including coalesced drains and joined resumes. These durable rows are history, not a durable execution identity: replay must never infer current liveness, recovery, grouping, or resumability from an unmatched start. A drain has no durable identity or transcript boundary. `/api/session/active` is the authority for current process-local liveness, and is empty after restart. User interruption records `reason: "user"`; owner-scope interruption defaults to `"shutdown"`; `"superseded"` is reserved for explicit replacement. + +Core retries only typed rate-limit, provider-internal, and transport failures before durable assistant text, reasoning, tool-call, tool-output, or tool-execution evidence. The initial call plus at most four retries use two-second exponential backoff, raised when a provider's `retryAfterMs` is larger. Every retry attempt remains a distinct step and consumes the selected agent's step allowance, while all pre-output attempts reuse one assistant message ID so retry state never creates empty transcript messages. Repeated `session.step.started.1` facts reopen that assistant projection idempotently. `session.retry.scheduled.1` is committed before each delay with the upcoming one-based attempt and absolute epoch-millisecond time, then projects onto `Assistant.retry`. The next `session.step.started.1` or terminal failure/interruption clears it. A scheduled retry surviving a crash is historical UI state only and never triggers recovery. + +A normalized `step-finish` with `content-filter` publishes `session.step.failed.1` with `provider.content-filter`, never `session.step.ended.1`. Any partial streamed content remains visible; a contentless filtered response still has a failed assistant projection. Projected hosted tools preserve call-side and settlement-side provider metadata separately so settlement and interruption recovery cannot erase continuation identifiers. Provider-native reasoning and provider metadata replay only while the historical assistant model matches the selected continuation model; after a model switch, visible reasoning text remains ordinary assistant text and provider-native metadata is omitted. @@ -112,7 +120,6 @@ Current instruction follow-ups: - Add configured and remote instruction sources with explicit precedence and removal semantics. - Add durable post-crash continuation recovery for promoted or provider-dispatched work. -- Add explicit manual compaction on top of automatic request-budget compaction. - Add operational metrics for observation latency, unavailable sources, contention, baseline size, and chronological-update growth. - Consider watcher-backed per-file caching only if measurements show direct step-boundary observation is too expensive. - Design any plugin-defined instruction contribution as an explicit runner composition boundary; do not reintroduce a registry implicitly. @@ -124,7 +131,13 @@ Before each step, the runner estimates the complete model-visible request and co Compaction keeps the full transcript durable while replacing its active model representation with one hidden checkpoint containing a structured rolling summary and token-bounded serialized recent context. Provider-native assistant, reasoning, and tool messages never survive across the boundary, avoiding signature and encrypted-reasoning failures when the earlier prefix changes. -`session.compaction.started.1` durably identifies the attempt. Compaction deltas are live-only progress. `session.compaction.ended.1` durably stores the final summary and serialized recent context; only this completed event projects a model-visible compaction message. On the next physical attempt, the runner observes that completed compaction and directly renders a fresh instruction baseline through `InstructionCheckpoint`. A failed or interrupted attempt therefore leaves the previous history boundary active. +The rolling summary is a continuation checkpoint with this complete heading order: `Objective`, `Important Details`, `Work State`, and `Next Move`. `Work State` records completed, active, and blocked work, while `Next Move` records the immediate and following actions. Every heading remains present even when its value is `(none)`. + +`session.compaction.admitted.1` durably records a manual request and projects its queued transcript row. `session.compaction.started.1` identifies the attempt and transforms that row into a running divider. Compaction deltas are live-only progress rendered beneath it. `session.compaction.ended.1` durably stores the final summary and serialized recent context, completes the same row, and settles the manual barrier. `session.compaction.failed.1` settles an unsuccessful manual barrier without changing the previous history boundary. On the next physical attempt, the runner observes a completed compaction and directly renders a fresh instruction baseline through `InstructionCheckpoint`. + +Assistant text and reasoning follow a strict `started` / live-only `delta` / durable full-value `ended` lifecycle. A publisher permits at most one open fragment of each kind in a step and fails on a second start before the matching end. Provider block IDs remain internal to LLM adapters; each fragment event carries a Session-assigned kind-specific ordinal, matching the ordinal derived from projected content. UI identity is therefore the assistant message ID plus content kind and ordinal. Tool calls retain step-scoped `callID` because settlements and provider replay correlate through it. + +Provider continuation state is opaque and un-nested at the Session boundary. The publisher selects only the active model provider's entry from LLM provider metadata. Same-model replay re-nests that state under the current provider; model switches and failed assistant steps continue to suppress provider-native continuation state. Repeated compactions update the previous structured summary with newly compacted messages. The runner then reloads projected history and executes the original pending step. @@ -160,7 +173,7 @@ Status: `complete` is usable in the native V2 path, `partial` covers only part o | Prompt/reference expansion | Configured-reference expansion | missing | Resolve aliases and emit durable model-visible reference context or failures. | | Prompt/reference expansion | Native synthetic expansion replay | partial | V2 replays synthetic messages but only the V1 compatibility path creates them. | -Provider timeout, retry, and watchdog policy is intentionally deferred. The runner does not impose a universal provider-stream inactivity or absolute timeout. A future slice should design configurable policy around provider behavior, durable failure reporting, and local drain-chain release rather than hardcoding one default for every provider. +Provider timeout and watchdog policy is intentionally deferred. Retry tuning beyond the narrow safe policy above remains separate work; the runner does not impose a universal provider-stream inactivity or absolute timeout. Inbox delivery is explicit: @@ -174,7 +187,7 @@ Execution has two entry points: Post-crash continuation recovery is intentionally deferred. A wake does not infer that ambiguous provider work is safe to retry after an input has already been promoted. Explicit `run` may deliberately continue from durable projected history. A future recovery slice should model provider-dispatch ambiguity, required continuation, queued-input promotion, retry policy, and visible recovery status together. It must not assume an enclosing durable execution identity that the Session model does not otherwise need. -A process-global `SessionRunCoordinator` serializes execution for each local Session while allowing different Sessions to run concurrently. Resumes join active execution, overlapping wakes coalesce into one follow-up, and interruption stops current process-local execution without deleting durable inbox work. The runner enters the Session's current Location when execution starts and fences each new step against that Location. +A process-global `SessionRunCoordinator` serializes execution for each local Session while allowing different Sessions to run concurrently. Resumes join active execution, overlapping wakes coalesce into one follow-up, and interruption stops current process-local execution without deleting durable inbox work. The runner enters the Session's current Location when execution starts and fences each new step against that Location. Its durable lifecycle events are historical observations only; they do not replace the coordinator's process-local active registry. The coordinator's active registry is also the source for `sessions.active()`. It represents only foreground Session drains owned by the current process; background subagents and tasks do not add parent Sessions to this registry. The snapshot is runtime state and is empty after a process restart. diff --git a/specs/v2/todo.md b/specs/v2/todo.md index 6f09684a1b..7c6a137192 100644 --- a/specs/v2/todo.md +++ b/specs/v2/todo.md @@ -24,6 +24,8 @@ through legacy `SessionPrompt.loop(...)`: and issues one explicit `llm.stream(request)` step at a time - durable V2 projections record text, reasoning, provider failures, tool calls, tool results, and assistant output +- owned local tool fibers and unresolved hosted calls settle before their step's + single terminal event; cross-drain orphan recovery retains original assistant attribution - a scoped `ToolRegistry` advertises definitions and the first permission-checked `read` built-in - local continuation reloads projected history, and promoting new user input resets the selected agent's configured step allowance @@ -37,9 +39,6 @@ a FIFO until the Session would otherwise become idle and then promote one at a t Next reviewed slices: -- preserve eager structured local-tool settlement: durably record each complete - call, start its child execution immediately, await every settlement after the - step closes, then reload projected history once - revisit per-step tool-call limits, output truncation, and operational backpressure before broadening exposure; eager local execution is deliberately unbounded in the current local slice while SQLite publication stays serialized @@ -55,7 +54,7 @@ Next reviewed slices: ### Deferred durable continuation recovery -Do not infer that ambiguous provider work is safe to retry from an advisory wake. +Do not infer that ambiguous provider work is safe to retry from an advisory wake, an unmatched historical `session.execution.started` event, or a surviving `session.retry.scheduled` projection. The first inbox-driven runner intentionally omits outer physical-attempt markers until they have a concrete consumer and a complete recovery policy.