diff --git a/.gitignore b/.gitignore index 48e7b32614..6c91758e28 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ **/*dont_commit_me* web/packages/agenta-api-client/dist/ web/tsconfig.tsbuildinfo +# Agent Pi extension bundle, built by `pnpm run build:extension` and in the Docker image. +services/agent/dist/ __pycache__/ **/__pycache__/ diff --git a/docs/design/agent-workflows/README.md b/docs/design/agent-workflows/README.md index 7d5784dfc2..d8ae4537ce 100644 --- a/docs/design/agent-workflows/README.md +++ b/docs/design/agent-workflows/README.md @@ -116,6 +116,9 @@ running agent. - [`wp-7-tools/`](wp-7-tools/README.md) — make runnable tools part of the agent config; resolve Composio actions into Pi tools and route tool calls back through the existing `POST /tools/call`, with MCP and workflow-as-tool as future adapters. +- [`wp-8-rivet-acp-runtime/`](wp-8-rivet-acp-runtime/README.md) — re-platform the service onto + `rivet-dev/sandbox-agent` so the agent is driven over ACP and the harness (Pi, Claude Code, + Codex) becomes a config value, running locally first; tools, Daytona, and the folder jail deferred. ## Related work diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/README.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/README.md new file mode 100644 index 0000000000..716a97d60e --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/README.md @@ -0,0 +1,80 @@ +# WP-8: Rivet + ACP agent runtime + +Status: design ready to implement. Start at [`plan.md`](plan.md). Decisions and open +items are in [`status.md`](status.md). + +This folder is self-contained. A new engineer should be able to read it and implement the +work end to end without prior context. Read in this order: this README, then +[`context.md`](context.md) (the code that exists today), [`research.md`](research.md) +(verified facts about rivet, ACP, and the pattern we copy), [`architecture.md`](architecture.md) +(the target design), and [`plan.md`](plan.md) (the phased build). + +## Summary + +Re-platform the agent workflow service (`services/oss/src/agent.py`) so it drives the +agent over the **Agent Client Protocol (ACP)** through [`rivet-dev/sandbox-agent`](https://github.com/rivet-dev/sandbox-agent), +instead of the bespoke Pi JSON protocol it uses today. + +The `/invoke` contract does not change. The handler still builds a user turn and returns +`{"role": "assistant", "content": ...}`. What changes is the transport behind the existing +`Harness` port: rivet runs the chosen harness (Pi, Claude Code) as an ACP session and +streams the reply back. Picking a different harness becomes a config value, not new code. + +## The four requirements + +1. **Drive the agent over ACP**, not the Pi JSON protocol. Rivet speaks ACP to the + harness; our service drives rivet. +2. **Swap harness as config.** The same agent config runs on Pi or Claude Code by setting + one value. +3. **Run locally.** The same path runs on a dev machine with no container, using rivet's + `local` provider. The rivet server is open source, so running it locally is normal. +4. **Defer tools.** Ship with no tools. The tool model is fixed (definition plus swappable + body, delivered per-harness over MCP), but nothing is built here. + +## The design in five lines + +- Keep `agent.py`, the `/invoke` contract, and the `Harness` port unchanged. +- Add a `RivetHarness` adapter behind the port, plus a small TypeScript runner that wraps + the rivet SDK. +- Run **one rivet daemon and one sandbox per invoke** (cold), then tear it down. This + copies the pattern Agenta already ships for code evaluators. +- Inject the trace context as an environment variable **at the daemon's birth** (the + sandbox `env_vars` on Daytona, the SDK `env` option locally). No fork of rivet or the + adapters is needed under this per-invoke model. +- Two axes swap independently: **sandbox** (local, daytona) and **harness** (pi, claude). + +## Agent configuration (the contract to rivet: filesystem plus config) + +- **AGENTS.md** — instructions, after variable substitution. +- **Input variables** — substituted into AGENTS.md, like prompt-template variables. +- **Skills** — laid into the workspace as files (path and format are per-harness). +- **Tool definitions** — schema only, separate from bodies. Empty here. +- **Harness** — `pi` / `claude`. +- **Sandbox** — `local` / `daytona`. +- **Secrets** — harness and LLM auth, passed as launch env, never written into the + agent-visible filesystem. + +## In scope + +ACP transport via rivet, harness swap (Pi and Claude Code), local run, and **tracing** +(the agent's spans must nest under the `/invoke` span; standalone traces are not +acceptable). Daytona and concurrency are described as the immediate follow-on phases. + +## Deferred (each its own follow-on) + +- **Tools** ([WP-7](../wp-7-tools/README.md)): the definition-plus-body model over MCP. +- **Folder isolation (the jail)**: rivet has no filesystem confinement. Needed only when a + single warm daemon hosts many agents at once. A TypeScript-or-Rust change, deferred. See + [`isolation-and-fork.md`](isolation-and-fork.md). +- **Multi-turn and streaming to the client** ([WP-4](../wp-4-multi-message-output/README.md)): + one turn in, one message out, matching today. A session is persisted message history + replayed via ACP `session/load`. +- **Standalone SDK runner**: run an agent from the SDK with a config. The adapters are + written to live in the SDK so this is a packaging step later, not a rewrite. + +## Why rivet + +Rivet is the thing we were about to hand-build in the `Harness` and `Runtime` ports: an +ACP daemon that drives several harnesses, keyed by session, over a swappable sandbox +(local, daytona) with an HTTP and SSE control plane. We adopt it unmodified (Apache-2.0). +The one capability it lacks, filesystem confinement, we are deferring. diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/architecture.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/architecture.md new file mode 100644 index 0000000000..a9e71321f3 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/architecture.md @@ -0,0 +1,176 @@ +# Architecture + +## Principle + +Keep the `Harness` port and the `/invoke` contract. Add one adapter behind the port that +runs the agent through rivet over ACP, and a small TypeScript runner that wraps the rivet +SDK. Everything Pi-specific moves below the port and becomes one harness choice. + +``` + unchanged + ┌───────────────────────────────────────────────┐ + │ agent.py (/invoke, /inspect, ag.create_app) │ + │ _resolve_run_config / _latest_user_message │ + │ _build_harness() ── selects adapter by env │ + └───────────────────────────────────────────────┘ + │ Harness port (setup / invoke / shutdown) + ▼ + ┌───────────────────────────────────────────────┐ + │ RivetHarness (new, Python) │ PiHarness / PiHttpHarness + │ maps HarnessRequest + {harness, sandbox} → │ (kept; legacy path) + │ a one-shot rivet run; passes trace + secrets │ + └───────────────────────────────────────────────┘ + │ /run (HTTP or stdio), same contract family as runPi + ▼ + ┌───────────────────────────────────────────────┐ + │ runRivet.ts (services/agent, wraps rivet SDK) │ + │ start({ sandbox, env }) → createSession({ │ + │ agent, cwd }) → write AGENTS.md → prompt → │ + │ collect chunks → destroy │ + └───────────────────────────────────────────────┘ + │ spawns the daemon (local subprocess, or in Daytona) + ▼ + ┌───────────────────────────────────────────────┐ + │ sandbox-agent daemon (Rust, one per invoke) │ + └───────────────────────────────────────────────┘ + │ ACP (JSON-RPC: session/prompt, session/update) + ▼ + ┌───────────────────────────────────────────────┐ + │ harness ACP adapter subprocess in cwd │ + │ pi-acp │ claude-code-acp │ + └───────────────────────────────────────────────┘ +``` + +The ACP boundary is daemon to harness. That is the requirement: the agent loop runs over +ACP, not the Pi JSON envelope. The service-to-rivet hop is rivet's own control surface and +stays harness-agnostic behind the port. + +## Two orthogonal swap axes + +These swap independently. Do not bundle them. + +- **Sandbox (where the daemon runs):** `local`, `daytona`. A config value passed to + `runRivet`, which selects the rivet provider. +- **Harness (which engine):** `pi`, `claude`. A config value passed as the rivet `agent`. + +The demo proves each separately: swap `local` and `daytona` with the harness fixed, and +swap `pi` and `claude` with the sandbox fixed. + +## Lifecycle: one daemon and one sandbox per invoke (cold) + +Each `/invoke` brings up its own daemon and sandbox, runs, and tears down. This copies the +shipped code-evaluator pattern (`DaytonaRunner`: an ephemeral sandbox per execution from a +snapshot, deleted in a `finally`). Two reasons it is the right default: + +- It makes the daemon's environment **per-invoke**, which is what makes tracing work + without forking anything (see below). +- It needs no filesystem jail, because agents never share a daemon. + +Cost is acceptable. Locally the daemon is a Rust binary that boots in tens of +milliseconds, so the per-invoke cost is the Node adapter spawn (~0.2 to 0.5s). On Daytona +the sandbox create adds ~1s. Concurrency is bounded the way evaluations already bound it +(see Concurrency). + +## Tracing: inject at the daemon's birth + +The agent's spans must nest under the `/invoke` span. Standalone traces are not +acceptable. The mechanism is uniform across sandboxes because each invoke owns its daemon: + +- The static OTLP target and auth (`OTEL_*`, the Agenta endpoint and `Authorization`) and + the per-invoke `traceparent` go into the daemon's environment when it is created. + - **Local:** the SDK `env` option on `start({ sandbox: local(), env })`. + - **Daytona:** the sandbox `env_vars`, exactly like `DaytonaRunner` injects `AGENTA_*`. +- The daemon passes its env to the adapter subprocess, which passes it to the harness. +- **Pi:** install the `agenta-otel` logic as a Pi extension in the environment (global + `~/.pi/agent/extensions`, or baked into the Daytona snapshot). Pi loads it and emits + spans under the injected `traceparent`. +- **Claude Code:** set `CLAUDE_CODE_ENABLE_TELEMETRY=1`, `OTEL_*`, and `TRACEPARENT`, and + run it in `-p` / Agent-SDK mode. + +No fork of rivet or the adapters is needed under the per-invoke model. A fork (the +TypeScript adapter reading ACP `_meta.traceparent`, not Rust) is only needed if a later +phase shares one warm daemon across concurrent invokes. + +## Components + +### `RivetHarness` (Python, new) + +`services/oss/src/agent_pi/rivet_harness.py`, implements the `Harness` ABC. It holds the +harness id and sandbox choice (from config) and the trace/secret context, and maps a +`HarnessRequest` onto a `runRivet` `/run` call. Field mapping: + +| `HarnessRequest` | Becomes | +| --- | --- | +| `agents_md` | written as `AGENTS.md` into the session `cwd` | +| `model` | session model where the harness honors it (the adapter normalizes this) | +| `prompt` | the ACP prompt text | +| `messages` | MVP uses the latest user turn; history replay is later | +| `tools` etc. | unused (empty) in WP-8 | +| `trace` | injected as daemon env (`traceparent`, OTLP endpoint, auth) | + +### `runRivet.ts` (TypeScript, in `services/agent`) + +Wraps the rivet SDK. Selected by env (`AGENT_BACKEND=rivet`) and serves the same `/run` +contract `runPi.ts` serves, so the Python side stays thin. Per invoke: + +1. `start({ sandbox: local() | daytona({...}), env })` (env carries trace + secrets). +2. `createSession({ agent: , cwd })`. +3. Write `AGENTS.md` (and later skills) into `cwd`. +4. `prompt(sessionId, prompt)`, accumulate `agent_message_chunk` into the output. +5. `destroy()`. +6. Return `{ ok, output, sessionId, model }`. + +### `agent.py` selection + +Extend `_build_harness()` with `AGENTA_AGENT_RUNTIME=rivet` to return `RivetHarness` +(harness from `AGENTA_AGENT_HARNESS`, sandbox from config, default `local`). Keep the Pi +path as default so nothing regresses. + +## Agent configuration (the contract: filesystem plus config) + +Resolved before each run: AGENTS.md, input variables (substituted into AGENTS.md), skills +(files in the workspace), tool definitions (empty here), harness, sandbox, secrets. The +contract handed to rivet is files in `cwd` plus the session/daemon config. Secrets go as +launch env, never as files, because there is no jail. + +## Tools: definition vs body (deferred, but shapes the seam) + +A tool splits into a **definition** (the schema the model sees, stored in a neutral +OpenAI-function shape) and a **body** (the execution). The body is swappable: real, +service-backed, or mock. A test variant of an agent swaps bodies without touching +definitions. Delivery is per-harness over **MCP** (rivet's per-directory MCP config), not a +raw OpenAI array. The body model is general and not Agenta-specific: a self-contained body +runs in-process, a service-backed body (for example a Composio tool calling Agenta's +`/tools/call`) needs its service reachable (a local or remote Agenta), and a mock needs +nothing. WP-8 ships no tools; this is the shape to preserve, not build. + +## Sessions and state + +A session is the **stored message history**, not a kept-alive sandbox. Because we offer no +persistent file writes, nothing on disk is worth keeping. So: ephemeral sandbox per turn, +persisted messages, continue by replaying history with ACP `session/load` (Pi +`resumeSession`, Claude Code `loadSession`). Zero at-rest cost. The history store is the +backend DB on the platform and a local file standalone. Tradeoff: long-history replay +re-sends tokens, so cap it. Paused or FS-persisted sessions wait until we offer durable +writes. + +## Concurrency + +Mirror evaluations. Do not run the agent inside the API request if a background path is +available; dispatch it like an evaluation (taskiq worker on a Redis stream) and bound +concurrency with a shared semaphore. Each concurrent slot is one ephemeral sandbox, so the +semaphore caps how many sandboxes (and how much Daytona cost) run at once. Extra invokes +queue. Locally a slot is a cheap subprocess. + +## Running standalone via the SDK (later) + +The harness and sandbox adapters are written to live in the SDK, so the backend service +and a standalone run share one implementation. Running locally is not special: the rivet +server is open source (Apache-2.0, a static binary), so a local run runs that server +locally and the SDK wraps the rivet client. A standalone run fetches or loads a config, +then calls the SDK runner. + +## What this does not change + +No new endpoints. No change to `/invoke` or `/inspect` shapes. No tools, no jail, no +multi-turn, no client-side streaming. Each is its own follow-on. diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/context.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/context.md new file mode 100644 index 0000000000..fe7d1ecac0 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/context.md @@ -0,0 +1,89 @@ +# Context: the code that exists today + +Read this to orient on the current service before changing it. All paths are in this repo +(`/home/mahmoud/code/agenta`). + +## The agent service (WP-2) + +`services/oss/src/agent.py` is an Agenta app exposing `/invoke` and `/inspect`, like the +chat and completion services. The handler `_agent(...)`: + +1. Resolves config with `_resolve_run_config(...)`: model, AGENTS.md (the system text), + and tools, from the request `parameters` or the file config. +2. Builds the latest user turn with `_latest_user_message(...)`. +3. Picks a harness adapter with `_build_harness()` and calls the `Harness` port + (`setup` / `invoke` / `shutdown`). +4. Returns `{"role": "assistant", "content": result.output}`. + +Trace context is captured in `_trace_context()` and threaded into the harness so the +agent's spans nest under the `/invoke` span. + +## The ports (the seam we keep) + +`services/oss/src/agent_pi/ports.py`: + +- `Harness` (ABC): `setup()`, `invoke(HarnessRequest) -> HarnessResult`, `shutdown()`. +- `HarnessRequest`: `agents_md`, `model`, `prompt`, `messages`, `tools`, `custom_tools`, + `tool_callback`, `trace`. +- `HarnessResult`: `output`, `session_id`, `model`. +- `TraceContext`: `traceparent`, `baggage`, `endpoint` (OTLP), `authorization`, + `capture_content`. Has `to_wire()` (camelCase). +- `Runtime` (ABC): the sandbox/environment seam for the legacy Pi path (`start`, + `shutdown`, `exec`). The rivet path does not use `Runtime.exec`; it selects a rivet + provider instead (see architecture). + +## The current Pi adapters (legacy, keep working) + +- `services/oss/src/agent_pi/pi_harness.py` (`PiHarness`): spawns the TypeScript Pi + wrapper as a subprocess, one JSON object over stdio. +- `services/oss/src/agent_pi/pi_http_harness.py` (`PiHttpHarness`): POSTs the same JSON to + the wrapper running as an HTTP sidecar. +- Both send a Pi-shaped envelope (`{agentsMd, model, prompt, messages, tools, customTools, + toolCallback, trace}`). + +## The TypeScript wrapper + +`services/agent/` is a small Node service. + +- `src/runPi.ts`: turns the envelope into direct Pi SDK calls (`createAgentSession`, ...). +- `src/agenta-otel.ts`: a Pi OTel helper. Today `runPi.ts` imports it in-process and emits + `invoke_agent` as a child of the incoming `traceparent`. Under rivet this logic must + become a Pi **extension** installed in the environment (see architecture, tracing). +- `src/server.ts` (HTTP `/run`) and `src/cli.ts` (stdio) are the two transports. + +## The pattern we copy: how code evaluators run in Daytona + +This is the shipped precedent for "ephemeral sandbox per execution", and the agent service +mirrors it. + +- `sdks/python/agenta/sdk/engines/running/runners/` holds `base.py` (`CodeRunner`), + `local.py` (`LocalRunner`, in-process `exec`), `daytona.py` (`DaytonaRunner`, remote + sandbox), and `registry.py` (`get_runner()`). +- Selection: env `AGENTA_SERVICES_CODE_SANDBOX_RUNNER` (`local` default, `daytona` in + cloud). +- `DaytonaRunner.run()` creates an `ephemeral=True` sandbox from a snapshot + (`DAYTONA_SNAPSHOT`), runs, and deletes it in a `finally`. **One sandbox per execution.** + No warm pool, no shared instance. It injects `AGENTA_HOST`, `AGENTA_API_KEY`, and the + user's provider keys as the sandbox `env_vars`. +- Concurrency is bounded by the evaluation engine, not the runner: a shared + `asyncio.Semaphore(batch_size)` (default 10) in + `sdks/python/agenta/sdk/evaluations/runtime/processor.py`. So at most ~10 ephemeral + sandboxes exist at once. +- Daytona config lives in `api/oss/src/utils/env.py` (`DaytonaConfig`: + `DAYTONA_API_KEY`, `DAYTONA_API_URL`, `DAYTONA_SNAPSHOT`, `DAYTONA_TARGET`). + +## What we change and what we keep + +Change: the transport behind the `Harness` port becomes rivet over ACP, with harness and +sandbox as config values. + +Keep: the `/invoke` and `/inspect` contract, the `Harness` port and its dataclasses, the +config resolution in `agent.py`, and the env-driven adapter selection in +`_build_harness()` (extended with a rivet branch). The legacy Pi adapters keep working so +nothing regresses. + +## Conventions + +- Standalone scripts run with `uv run` and inline `# /// script` dependencies. +- Python edits: `ruff format` then `ruff check --fix` before committing. +- Local-server parity is a first-class requirement carried from WP-2. diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/isolation-and-fork.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/isolation-and-fork.md new file mode 100644 index 0000000000..3f219acebb --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/isolation-and-fork.md @@ -0,0 +1,76 @@ +# Isolation and when a fork is needed + +This is deferred for WP-8. It matters only if a later phase runs **one warm daemon hosting +many agents at once**. The WP-8 model (one daemon and one sandbox per invoke) avoids it: a +single agent owns its sandbox, so there is nothing to isolate it from. Read this only when +you move to a shared warm daemon, or when you want many agents inside one long-lived +sandbox each confined to its own folder. + +Note on language: a "fork" here can mean two different things. The **jail** below is new +code we add. Separately, the tracing discussion mentioned forking an ACP **adapter**; +those are small TypeScript packages, not the Rust daemon. Neither is needed for WP-8. + +## The gap + +Rivet has no filesystem isolation (see [`research.md`](research.md#filesystem-no-jail-exists)). +A session's `cwd` is advisory and the file API resolves absolute paths verbatim. So if many +agents share one daemon, each can read and write the whole host, including other agents' +folders. Confining them to their own folders is then the load-bearing new capability. + +## What rivet gives for free vs what we build + +| Capability | Status in rivet | +| --- | --- | +| One daemon, many agents/sessions | done (`AcpProxyRuntime` instance map) | +| Multiple harnesses incl. Pi | done (`AgentId`, ACP adapters) | +| Per-session working directory | done (`cwd` plumbed end to end) | +| Per-directory tool config | done (MCP / skills) | +| HTTP + SSE streaming | done | +| **Folder jail (the agent sees only its folder)** | **missing; we add it (needs a fork)** | + +## How the jail would work (deferred) + +The field has converged on this for confining a coding agent to one folder without a +container per agent: + +- **Linux, preferred:** bubblewrap (mount namespace, bind-mount only the folder so + nothing else exists) + Landlock (VFS-level deny as a backstop) + seccomp (trim escape + syscalls). This is what Codex CLI and Anthropic's `srt` do. +- **Caveat:** bubblewrap needs unprivileged user namespaces, which are disabled on + hardened or managed distros. Fallback is **Landlock-only**: no root, no namespaces, + still confines file access, but outside paths stay visible (EACCES on access) rather + than invisible. Detect user namespaces at startup and degrade gracefully. +- **macOS:** no Landlock or namespaces. Use `sandbox-exec` / Seatbelt with a + `(deny default)(allow file-* (subpath ""))` profile. +- Do not rely on the harness: opencode and Pi do no FS sandboxing; they trust the caller. + +Threat model sets the bar. For self-hosted single-org, Landlock plus per-session `cwd` is +likely enough, which also sidesteps the user-namespace problem. For multi-tenant cloud, +you want the full bubblewrap + seccomp stack or genuine containers. + +## Where the fork would touch rivet + +If and when we add the jail, the changes are localized (paths inside the rivet repo): + +1. **Subprocess confinement** — wrap the harness launch with bwrap / a Landlock helper. + Easiest at the generated launcher in `agent-management/src/agents.rs` (`write_launcher`), + threading a per-instance root through `acp_proxy_runtime.rs::create_instance` and + `acp-http-adapter/src/process.rs` (`AdapterRuntime::start`, which today never even sets + `current_dir`). +2. **File API jail** — `router/support.rs::resolve_fs_path`: add a configured root and + reject absolute paths outside it. +3. **Process runtime jail** — `process_runtime.rs`: same confinement, or the jail leaks + via `/v1/process`. +4. **Config** — `cli.rs` + `daemon.rs`: a `--root` / per-server root option (none exists). +5. (Optional) a TS provider that maps each agent to its own root folder, copying + `providers/local.ts`. + +Effort: the multi-agent / multi-harness / streaming half is inherited. The jail itself is +medium-to-large because it is platform-specific and has three escape surfaces with no +existing isolation code to build on. A soft jail (path-prefix checks + `cwd`, no kernel +enforcement) is small-to-medium but is not a real "cannot see outside" guarantee. + +## Decision for now + +Use rivet unmodified for WP-8 (ACP + harness swap + local, tools deferred). Fork only +when we need the jail, and keep the fork minimal and rebaseable against upstream. diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/plan.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/plan.md new file mode 100644 index 0000000000..17a0051827 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/plan.md @@ -0,0 +1,110 @@ +# Plan + +Phased so each phase is demonstrable and reversible. Phases 0 to 2 deliver the four +requirements (ACP, harness swap, local, tools deferred) plus tracing. Phase 3 adds +Daytona. Phase 4 adds concurrency. Keep the legacy Pi adapters working throughout; select +the rivet path with env. + +Read [`context.md`](context.md) and [`architecture.md`](architecture.md) first. + +## Demo targets (what success looks like) + +1. **Sandbox swap:** the same agent on `local` and `daytona`, harness fixed. +2. **Harness swap:** the same agent on `pi` and `claude`, sandbox fixed. +3. **Tracing:** the agent's spans nest under the `/invoke` span in Agenta, for both + harnesses. + +## Phase 0 — Spike: rivet + local + Pi + ACP + tracing (throwaway) + +Goal: prove the path end to end before touching the service. + +1. Install locally: the rivet SDK and `sandbox-agent` binary (check the package name on + rivet.dev), the Pi CLI, and the `pi-acp` adapter. Verify the SDK API names against the + installed version. +2. Write `services/agent/src/runRivet.ts`: `start({ sandbox: local(), env })`, + `createSession({ agent: "pi", cwd })`, write `AGENTS.md` into `cwd`, `prompt(...)`, + accumulate `agent_message_chunk` into a string, `destroy()`. Return `{ ok, output, + sessionId, model }`. +3. Package the `agenta-otel` logic (from `services/agent/src/agenta-otel.ts`) as a Pi + extension and install it at `~/.pi/agent/extensions`. Pass `traceparent`, the Agenta + OTLP endpoint, and auth in the `start({ env })` map. +4. Write a `uv run` showcase script (inline `# /// script` deps) that calls `runRivet` + with a fixed config (AGENTS.md, model), prints the reply, then re-runs with + `agent: "claude"`. + +Done when: Pi answers a prompt locally through rivet over ACP, Claude Code answers the same +config, and Pi's spans show up in Agenta nested under a parent trace. + +## Phase 1 — `RivetHarness` behind the port + +Goal: wire rivet into the service with no change to `/invoke`. + +1. `services/oss/src/agent_pi/rivet_harness.py`: `RivetHarness(Harness)`. Map + `HarnessRequest` plus `{harness, sandbox}` config and `TraceContext` to a `runRivet` + `/run` call (reuse the `PiHttpHarness` HTTP-client shape, or stdio). +2. `services/agent/src/server.ts`: route `/run` to `runRivet` when `AGENT_BACKEND=rivet`. +3. `agent.py` `_build_harness()`: add `AGENTA_AGENT_RUNTIME=rivet` to return + `RivetHarness` (harness from `AGENTA_AGENT_HARNESS`, sandbox `local`). Keep the Pi + default. +4. Pass `_trace_context()` through `RivetHarness` to `runRivet`, which injects it into + `start({ env })`. + +Done when: `/invoke` returns the same `{"role": "assistant", "content": ...}` for a +no-tools agent via rivet, spans nest under `/invoke`, and flipping `AGENTA_AGENT_RUNTIME` +switches between the rivet and Pi paths with no other change. + +## Phase 2 — Harness swap as config + +Goal: one config, two harnesses. + +1. Thread `AGENTA_AGENT_HARNESS` (`pi` / `claude`) through `RivetHarness` to `runRivet`'s + `agent` value. +2. Pass harness auth as launch env: Pi's LLM key; Claude Code's Anthropic auth plus + `CLAUDE_CODE_ENABLE_TELEMETRY=1`, `OTEL_*`, `TRACEPARENT`, run in `-p`/SDK mode. +3. The `RivetHarness` (the adapter) normalizes `model` per harness (Pi takes the id; + Claude Code uses its own). + +Done when: the same agent config runs on Pi and Claude Code by changing one value, and both +nest spans under `/invoke`. This completes the four requirements. + +## Phase 3 — Daytona sandbox (mirror the code evaluator) + +Goal: swap `local` for `daytona`, same agent. + +1. Build a Daytona snapshot with the rivet binary, the Pi and Claude CLIs, both ACP + adapters, and the `agenta-otel` Pi extension preinstalled. Record the snapshot id. +2. `runRivet`: when `sandbox=daytona`, `start({ sandbox: daytona({ snapshot, target }), + env })`. Create ephemeral per invoke, inject `traceparent` and secrets as `env_vars`, + `destroy()` after. Reuse the config keys `DaytonaRunner` uses (`DAYTONA_API_KEY`, + `DAYTONA_API_URL`, `DAYTONA_SNAPSHOT`, `DAYTONA_TARGET` in `api/oss/src/utils/env.py`). + +Done when: the same agent runs on `local` and `daytona` by changing the sandbox value, with +one ephemeral sandbox per invoke and spans nested. + +## Phase 4 — Concurrency and background dispatch + +Goal: bound concurrent sandboxes the way evaluations do. + +1. Dispatch agent invokes through the existing taskiq worker + Redis-stream pattern if the + `/invoke` caller allows async; otherwise bound the synchronous path with a shared + semaphore. Size it to the max concurrent ephemeral sandboxes (mirror + `DEFAULT_BATCH_SIZE = 10`). +2. Confirm Daytona cost and quota stay within the cap under load; extra invokes queue. + +Done when: N concurrent invokes never exceed the configured number of live sandboxes. + +## Deferred (own work packages) + +- Tools, definition plus body over MCP ([WP-7](../wp-7-tools/README.md)). +- Folder jail ([`isolation-and-fork.md`](isolation-and-fork.md)), needed only with a warm + shared daemon. +- Multi-turn and client streaming ([WP-4](../wp-4-multi-message-output/README.md)). +- Standalone SDK runner (packaging the adapters into the SDK). + +## Validation + +- Behavior parity: reuse the WP-2 manual `/invoke` curl check against both the Pi and rivet + paths. +- Tracing: confirm in Agenta that the agent run appears under the `/invoke` `trace_id`. +- Python edits: `ruff format` then `ruff check --fix` before committing. +- Add unit coverage for `RivetHarness` request mapping once it grows past a thin client. diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/build_rivet_snapshot.py b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/build_rivet_snapshot.py new file mode 100644 index 0000000000..5e85e7b491 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/build_rivet_snapshot.py @@ -0,0 +1,75 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = ["daytona"] +# /// +"""Build a Daytona snapshot for the WP-8 rivet agent runtime. + +Bakes the `pi` CLI into rivet's `-full` image (which already ships the sandbox-agent +daemon, the Claude CLI, and CA certs) so Daytona runs don't pay a ~150s per-invoke +`npm install pi`. Set the agent service to use it: + + AGENTA_RIVET_DAYTONA_SNAPSHOT=agenta-rivet-pi + AGENTA_RIVET_DAYTONA_INSTALL_PI=false + +Run: DAYTONA_API_KEY=... DAYTONA_TARGET=eu uv run build_rivet_snapshot.py [--force] +""" + +import sys +import time + +from daytona import ( + CreateSnapshotParams, + Daytona, + DaytonaConfig, + Image, + Resources, +) + +SNAPSHOT_NAME = "agenta-rivet-pi" +RIVET_IMAGE = "rivetdev/sandbox-agent:0.5.0-rc.2-full" +PI_PACKAGE = "@earendil-works/pi-coding-agent@0.79.4" + + +def main() -> None: + force = "--force" in sys.argv + daytona = Daytona(DaytonaConfig()) + + try: + existing = daytona.snapshot.get(SNAPSHOT_NAME) + except Exception: + existing = None + + if existing and not force: + print(f"snapshot '{SNAPSHOT_NAME}' already exists; pass --force to rebuild.") + return + if existing: + print(f"deleting existing snapshot '{SNAPSHOT_NAME}'...") + daytona.snapshot.delete(existing) + + # Base on rivet's -full image (daemon + claude + certs) and add the pi CLI globally + # so it is on PATH for the sandbox user the daemon runs as. The image's default user + # is the non-root `sandbox`, so switch to root for the global install, then back. + image = Image.base(RIVET_IMAGE).dockerfile_commands( + [ + "USER root", + f"RUN npm install -g --ignore-scripts {PI_PACKAGE}", + "RUN pi --version || true", + "USER sandbox", + ] + ) + + print(f"building snapshot '{SNAPSHOT_NAME}' from {RIVET_IMAGE} (+ pi)...") + started = time.monotonic() + daytona.snapshot.create( + CreateSnapshotParams( + name=SNAPSHOT_NAME, + image=image, + resources=Resources(cpu=2, memory=4, disk=8), + ), + on_logs=print, + ) + print(f"\nsnapshot '{SNAPSHOT_NAME}' built in {time.monotonic() - started:.1f}s") + + +if __name__ == "__main__": + main() diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/commit_agent_config.py b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/commit_agent_config.py new file mode 100644 index 0000000000..7b6db094de --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/commit_agent_config.py @@ -0,0 +1,75 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = ["httpx"] +# /// +"""Commit an agent revision that exposes harness + sandbox as editable playground config. + +Adds two enum string params (harness: pi/claude, sandbox: local/daytona) to the agent +workflow's parameters schema, alongside the existing model + agents_md, so the playground +renders them as dropdowns (SchemaPropertyRenderer -> EnumSelectControl). WP-8 point 4. +""" + +import os +import httpx + +BASE = os.getenv("AGENTA_HOST", "http://144.76.237.122:8280").rstrip("/") +KEY = os.environ["AGENTA_API_KEY"] +PROJ = os.getenv("AGENTA_PROJECT_ID", "019ecbaf-5f3f-7d12-9aef-f49272dfd82e") +REV = os.getenv("AGENT_REVISION_ID", "019ecfc9-1ea0-7293-aa1c-350c029cb118") + +H = {"Authorization": f"ApiKey {KEY}", "Content-Type": "application/json"} + + +def main() -> None: + with httpx.Client(timeout=30) as client: + r = client.get( + f"{BASE}/api/workflows/revisions/{REV}", + params={"project_id": PROJ}, + headers=H, + ) + r.raise_for_status() + wr = r.json()["workflow_revision"] + variant_id = wr["workflow_variant_id"] + data = dict(wr["data"]) + + props = data["schemas"]["parameters"]["properties"] + props["harness"] = { + "type": "string", + "title": "Harness", + "enum": ["pi", "claude"], + "default": "pi", + "description": "Coding agent engine to drive over ACP.", + } + props["sandbox"] = { + "type": "string", + "title": "Sandbox", + "enum": ["local", "daytona"], + "default": "local", + "description": "Where the agent runs.", + } + params = dict(data["parameters"]) + params.setdefault("harness", "pi") + params.setdefault("sandbox", "local") + data["parameters"] = params + + body = { + "workflow_revision": { + "workflow_variant_id": variant_id, + "message": "WP-8: expose harness + sandbox as editable config", + "data": data, + } + } + resp = client.post( + f"{BASE}/api/workflows/revisions/commit", + params={"project_id": PROJ}, + headers=H, + json=body, + ) + print("commit status:", resp.status_code) + out = resp.json() + new = out.get("workflow_revision") or out + print("new revision id:", new.get("id"), "version:", new.get("version")) + + +if __name__ == "__main__": + main() diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/debug-events.ts b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/debug-events.ts new file mode 100644 index 0000000000..a3db5da87f --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/debug-events.ts @@ -0,0 +1,29 @@ +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { SandboxAgent } from "sandbox-agent"; +import { local } from "sandbox-agent/local"; + +const AGENT = process.env.SPIKE_AGENT ?? "claude"; +const here = dirname(fileURLToPath(import.meta.url)); +const binDir = join(here, "node_modules", ".bin"); +const BIN = join(here, "node_modules/.pnpm/@sandbox-agent+cli-linux-x64@0.4.2/node_modules/@sandbox-agent/cli-linux-x64/bin/sandbox-agent"); + +const cwd = mkdtempSync(join(tmpdir(), "wp8-dbg-")); +writeFileSync(join(cwd, "AGENTS.md"), "You are concise.\n", "utf-8"); +const env: Record = { PATH: `${binDir}:/home/mahmoud/.local/bin:${process.env.PATH ?? ""}`, PI_ACP_PI_COMMAND: join(binDir,"pi"), PI_CODING_AGENT_DIR: join(process.env.HOME??"",".pi/agent"), SANDBOX_AGENT_BIN: BIN, HOME: process.env.HOME??"" }; +const sandbox = await SandboxAgent.start({ sandbox: local({ env, binaryPath: BIN, log: "silent" }) }); +const session = await sandbox.createSession({ agent: AGENT, cwd, model: process.env.SPIKE_MODEL || undefined }); +let n = 0; +session.onEvent((event: any) => { + const p = event?.payload; + const u = p?.params?.update ?? p?.update; + const su = u?.sessionUpdate; + if (su) console.error(`[ev ${n++}] sender=${event.sender} sessionUpdate=${su} text=${JSON.stringify(u?.content?.text ?? u?.content)}`); + else console.error(`[ev ${n++}] sender=${event.sender} method=${p?.method} keys=${Object.keys(p||{})}`); +}); +await session.prompt([{ type: "text", text: "Count from 1 to 5, one number per line" }]); +await sandbox.destroySandbox().catch(()=>{}); +await sandbox.dispose().catch(()=>{}); +rmSync(cwd, { recursive: true, force: true }); diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/dump-full.ts b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/dump-full.ts new file mode 100644 index 0000000000..1cff6fcfa1 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/dump-full.ts @@ -0,0 +1,30 @@ +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { SandboxAgent } from "sandbox-agent"; +import { local } from "sandbox-agent/local"; +const AGENT = process.env.SPIKE_AGENT ?? "pi"; +const here = dirname(fileURLToPath(import.meta.url)); +const binDir = join(here, "node_modules", ".bin"); +const BIN = join(here, "node_modules/.pnpm/@sandbox-agent+cli-linux-x64@0.4.2/node_modules/@sandbox-agent/cli-linux-x64/bin/sandbox-agent"); +const cwd = mkdtempSync(join(tmpdir(), "wp8-dump-")); +writeFileSync(join(cwd, "AGENTS.md"), "You are concise.\n", "utf-8"); +const env: Record = { PATH: `${binDir}:/home/mahmoud/.local/bin:${process.env.PATH ?? ""}`, PI_ACP_PI_COMMAND: join(binDir,"pi"), PI_CODING_AGENT_DIR: join(process.env.HOME??"",".pi/agent"), SANDBOX_AGENT_BIN: BIN, HOME: process.env.HOME??"" }; +const sandbox = await SandboxAgent.start({ sandbox: local({ env, binaryPath: BIN, log: "silent" }) }); +const session = await sandbox.createSession({ agent: AGENT, cwd, model: process.env.SPIKE_MODEL || undefined }); +session.onEvent((event: any) => { + const p = event?.payload; + const u = p?.params?.update ?? p?.update; + const su = u?.sessionUpdate; + if (su === "usage_update" || su === "tool_call" || su === "tool_call_update") { + console.error(`[${su}] ${JSON.stringify(u).slice(0,500)}`); + } else if (!su && p?.result) { + console.error(`[result] ${JSON.stringify(p.result).slice(0,400)}`); + } +}); +const res = await session.prompt([{ type: "text", text: "What is 2+2? Answer in one word." }]); +console.error(`[promptResponse] ${JSON.stringify(res).slice(0,400)}`); +await sandbox.destroySandbox().catch(()=>{}); +await sandbox.dispose().catch(()=>{}); +rmSync(cwd, { recursive: true, force: true }); diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/package.json b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/package.json new file mode 100644 index 0000000000..c491095f12 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/package.json @@ -0,0 +1,14 @@ +{ + "name": "wp8-rivet-spike", + "private": true, + "type": "module", + "version": "0.0.0", + "dependencies": { + "@earendil-works/pi-coding-agent": "0.79.4", + "pi-acp": "0.0.29", + "sandbox-agent": "0.4.2" + }, + "devDependencies": { + "tsx": "4.19.2" + } +} diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/spike.ts b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/spike.ts new file mode 100644 index 0000000000..bd792fe210 --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/poc/spike.ts @@ -0,0 +1,103 @@ +/** + * WP-8 Phase 0 spike: drive Pi over ACP through a local rivet daemon. + * + * Verifies the whole chain end to end before touching the service: + * SandboxAgent.start({ sandbox: local({ env }) }) // spawns `sandbox-agent server` + * -> createSession({ agent: "pi", cwd }) // opens an ACP session + * -> write AGENTS.md into cwd + * -> prompt([{ type: "text", text }]) // sends the user turn + * -> collect `agent_message_chunk` text from session events + * -> dispose() // tears the daemon down + * + * Run: pnpm exec tsx spike.ts "" + */ +import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { SandboxAgent } from "sandbox-agent"; +import { local } from "sandbox-agent/local"; + +const AGENT = process.env.SPIKE_AGENT ?? "pi"; +const MODEL = process.env.SPIKE_MODEL ?? "gpt-5.5"; +const PROMPT = process.argv[2] ?? "Say hello in one short sentence and tell me what 2+2 is."; + +const here = dirname(fileURLToPath(import.meta.url)); +const binDir = join(here, "node_modules", ".bin"); +const BIN = join( + here, + "node_modules/.pnpm/@sandbox-agent+cli-linux-x64@0.4.2/node_modules/@sandbox-agent/cli-linux-x64/bin/sandbox-agent", +); + +function textOf(block: any): string { + if (!block) return ""; + if (typeof block === "string") return block; + if (block.type === "text" && typeof block.text === "string") return block.text; + return ""; +} + +async function main() { + const cwd = mkdtempSync(join(tmpdir(), "wp8-spike-")); + writeFileSync( + join(cwd, "AGENTS.md"), + "You are a concise assistant. Answer in one or two short sentences.\n", + "utf-8", + ); + + // Env handed to the daemon at birth. The local provider merges this into the + // `sandbox-agent server` subprocess, which passes it to the pi-acp adapter and + // then to `pi`. PI_ACP_PI_COMMAND points pi-acp at the local pi bin; PATH lets + // the daemon resolve the pi-acp adapter binary. + const env: Record = { + PATH: `${binDir}:${process.env.PATH ?? ""}`, + PI_ACP_PI_COMMAND: join(binDir, "pi"), + PI_CODING_AGENT_DIR: join(process.env.HOME ?? "", ".pi/agent"), + SANDBOX_AGENT_BIN: BIN, + HOME: process.env.HOME ?? "", + }; + + console.error(`[spike] starting daemon, agent=${AGENT} model=${MODEL}`); + const sandbox = await SandboxAgent.start({ + sandbox: local({ env, binaryPath: BIN, log: "silent" }), + }); + + let output = ""; + try { + console.error(`[spike] creating session in ${cwd}`); + const session = await sandbox.createSession({ agent: AGENT, cwd, model: MODEL }); + + session.onEvent((event: any) => { + const payload = event?.payload; + // ACP session/update notifications carry the streamed assistant text. + const update = payload?.params?.update ?? payload?.update; + if (!update) return; + if (update.sessionUpdate === "agent_message_chunk") { + const t = textOf(update.content); + if (!t) return; + // Harnesses differ: Pi streams pure deltas, Claude streams deltas plus a + // cumulative full snapshot. Replace when a chunk is a superset of what we + // have (snapshot), append otherwise (delta). Unifies both without doubling. + if (t.startsWith(output)) output = t; + else output += t; + } + }); + + console.error(`[spike] prompting...`); + const res = await session.prompt([{ type: "text", text: PROMPT }]); + console.error(`[spike] prompt returned stopReason=${(res as any)?.stopReason}`); + + console.error("[spike] OUTPUT >>>"); + console.log(output.trim()); + console.error("[spike] <<< OUTPUT"); + } finally { + await sandbox.destroySandbox().catch(() => {}); + await sandbox.dispose().catch(() => {}); + rmSync(cwd, { recursive: true, force: true }); + } +} + +main().catch((err) => { + console.error("[spike] FAILED:", err?.stack ?? err); + process.exit(1); +}); diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/research.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/research.md new file mode 100644 index 0000000000..f7d276806c --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/research.md @@ -0,0 +1,147 @@ +# Research (verified facts) + +Source-verified June 2026 against a clone of `rivet-dev/sandbox-agent` (Rust daemon plus +TypeScript SDK), the ACP spec and adapters, the Pi and Claude Code docs, and the Agenta +codebase. Rivet file paths below are inside the rivet repo. Agenta paths are in this repo. + +## Rivet, in one paragraph + +`sandbox-agent` is a daemon that runs **inside** a sandbox and drives coding harnesses +over ACP. Where it runs (local, Docker, E2B, Daytona, Vercel, Cloudflare) is decided by +the **TypeScript SDK** providers, not the Rust core. License: Apache-2.0. We adopt it as a +dependency and do not fork it for this WP. + +## Licensing (verified, safe to adopt commercially) + +Confirmed against the actual LICENSE files and package manifests June 2026. + +- **rivet-dev/sandbox-agent is Apache-2.0 throughout** (root LICENSE, Rust crates, TS SDK). + OSI-open, no BSL/SSPL/Elastic/non-commercial clause. Compatible with Agenta's MIT OSS + core and the commercial EE. +- **The server binary is open and self-buildable** (`cargo run -p sandbox-agent + --release`, ~15MB static binary). The `curl | sh` installer pulls a prebuilt from Rivet's + CDN (`releases.rivet.dev`), the same source compiled, with no key/auth/telemetry. Build + from source if you want zero external dependency. +- **No phone-home.** No Rivet account, API key to rivet.dev, or license server. Runs + offline and air-gapped. `$SANDBOX_TOKEN` is local auth, disable with `--no-token`. + Session persistence is pluggable (Postgres / in-memory; Rivet Actors optional). +- **Everything we ship or link is permissive:** pi-acp (MIT), claude-code-acp + /`@zed-industries/claude-agent-acp` (Apache-2.0), Daytona SDK (Apache-2.0), E2B (MIT), + Pi / Codex / opencode (MIT or Apache-2.0). No GPL/AGPL/SSPL/BSL in the bundled path. +- **Two restrictive pieces, both user-brought (weak coupling):** Claude Code is + proprietary (Anthropic Commercial ToS); the user installs it and brings their own + Anthropic auth, and we only shell out to it over ACP. Never bundle, auto-download, or + repackage it. Daytona's *server* is AGPL-3.0, but its client SDK is Apache-2.0 and the + AGPL binds whoever operates/modifies the server, not an API consumer; Agenta already + depends on the Daytona SDK for code evaluators. + +## The SDK shape (what the TypeScript runner calls) + +Approximate API (verify exact names against the installed SDK version from rivet.dev): + +- `SandboxAgent.start({ sandbox: local() })` or `{ sandbox: daytona({...}), env: {...} }` + brings up a daemon and returns a handle. The `local` provider spawns + `sandbox-agent server` as a host subprocess; the SDK merges `{...process.env, + ...options.env}` into that process. The `daytona` provider creates a Daytona sandbox and + starts the daemon inside it. +- `createSession({ agent, cwd })` opens an ACP session and returns a `serverId`. `agent` + is the harness id. +- `prompt(sessionId, text)` sends the turn; the daemon streams events (SSE), assistant + text arrives as `agent_message_chunk`. Accumulate the chunks into the final string. +- `destroy()` / `pauseSandbox()` tear down. On the Daytona provider, both delete the + sandbox (it implements only create/destroy; no stop/pause is wired). + +Harness ids (`AgentId` enum in `server/packages/agent-management/src/agents.rs`): +`Claude, Codex, Opencode, Amp, Pi, Cursor`. **Pi is first-class.** + +## One daemon hosts many sessions + +The core is `AcpProxyRuntime` with `instances: HashMap` +(`server/packages/sandbox-agent/src/acp_proxy_runtime.rs`). Each session spawns its own +ACP adapter subprocess with its own `cwd`. We do **not** rely on this multiplexing for the +MVP; we run one daemon and one session per invoke (see the lifecycle decision below). + +## Harnesses are ACP adapters, resolved from a registry + +Each harness maps to an ACP adapter program. Rivet builds a `LaunchSpec {program, args, +env}` from a registry (`acp-http-adapter/src/registry.rs`); the canonical registry is the +ACP one, with a pinned audit list in `scripts/audit-acp-deps/adapters.json` (e.g. +`pi-acp@0.0.23`, `@zed-industries/claude-agent-acp@0.20.0`). The adapters are small +TypeScript npm packages: + +- **pi-acp** (svkozak/pi-acp, MIT, TypeScript): spawns `pi --mode rpc`, passes its env + through to `pi`. Pi auto-loads extensions from `~/.pi/agent/extensions` and global + settings. +- **claude-code-acp** (`@zed-industries/claude-agent-acp`, Apache-2.0, TypeScript): wraps + the Claude Agent SDK. + +To use a forked adapter, point the launch command at it (npm package, local path, or your +own registry json). The adapter runs wherever the daemon runs. We do **not** need a fork +for this WP (see tracing). + +## Environment injection (how trace context and secrets reach the harness) + +`AdapterRuntime::start` (`acp-http-adapter/src/process.rs`) inherits the **daemon's env** +and overlays the static registry `LaunchSpec.env`. There is **no per-session env channel** +from the create-session HTTP path. Consequence: + +- A value set in the daemon's env is inherited by the adapter and the harness. +- Because we run **one daemon per invoke**, the daemon's env is per-invoke. So we set the + `traceparent`, OTLP config, and secrets in the daemon's env at its birth: the SDK `env` + option locally, the sandbox `env_vars` on Daytona. This is exactly how `DaytonaRunner` + already injects `AGENTA_*` and provider keys for code evaluators. +- The per-session-env gap only bites if you later share one warm daemon across concurrent + invokes. Then you would carry the traceparent in ACP `_meta` (a spec-blessed reserved + key, RFD completed 2026-06-03) plus a small adapter read, or patch rivet. Not now. + +## ACP facts + +- ACP is Zed's **Agent Client Protocol** (editor to coding-agent), JSON-RPC. Flow: + `initialize`, then `session/new` or `session/load`, then `session/prompt`, with streamed + `session/update` notifications. Not IBM's Agent Communication Protocol, not Google A2A. +- `session/load` replays the conversation via `session/update`, an optional capability + advertised in `initialize`. Pi exposes `resumeSession`; Claude Code `loadSession` (with + limits reconstructing old tool calls). This backs message-history continuation without + any persisted filesystem. + +## The pattern we mirror: code evaluators in Daytona + +Verified in the Agenta SDK. `DaytonaRunner` +(`sdks/python/agenta/sdk/engines/running/runners/daytona.py`) runs each code evaluator in +**one ephemeral Daytona sandbox per execution**: it creates an `ephemeral=True` sandbox +from a snapshot (`DAYTONA_SNAPSHOT`), runs, and deletes it in a `finally`. No warm pool, no +shared instance. It injects `AGENTA_HOST`, `AGENTA_API_KEY`, and provider keys as the +sandbox `env_vars`. Concurrency is bounded by the evaluation engine's shared +`asyncio.Semaphore(batch_size)` (default 10), not by the runner. Selected by env +`AGENTA_SERVICES_CODE_SANDBOX_RUNNER=daytona`. The agent service copies this shape. + +## Sessions and Daytona cost + +Daytona bills compute while a sandbox runs, storage while stopped, cheapest when archived. +An idle-but-running sandbox keeps billing. Rivet's Daytona provider only does +create/destroy, so "keep it warm and resume" is both unbuilt and costly. With no +persistent file writes there is nothing on disk to keep. So a session is stored message +history plus an ephemeral sandbox per turn (~1s Daytona cold start per the WP-3 POC, plus +history replay). Tradeoff: replaying long histories re-sends tokens, so cap with +truncation or summarization. + +## Tracing per harness + +- **Pi:** reuse the existing `agenta-otel` logic, but install it as a Pi extension in the + environment (global `~/.pi/agent/extensions`, or baked into the Daytona snapshot). Feed + `AGENTA_*` / `OTEL_*` / `traceparent` as env. pi-acp passes env through to `pi`, Pi loads + the extension, and spans nest under the parent. +- **Claude Code:** OTel is first-party. Set `CLAUDE_CODE_ENABLE_TELEMETRY=1`, `OTEL_*` + (endpoint and `Authorization` header for Agenta's OTLP), and `TRACEPARENT`, and run it in + `-p` / Agent-SDK mode (interactive mode ignores inbound traceparent). A known beta bug + may drop some spans in streaming ACP mode; verify before relying on it. +- The dominant way people instrument Claude Code is this built-in OTel exporter into a + collector or platform. Our wiring uses the same channel. + +## Filesystem: no jail exists + +Grep for `chroot|landlock|bubblewrap|seccomp|namespace|unshare|jail` across rivet's +`server/` returns zero hits. `cwd` is advisory; the file HTTP API (`resolve_fs_path`) +returns absolute paths verbatim. An agent can read and write anywhere the daemon can. This +only matters when many agents share one daemon, which the per-invoke model avoids. +Confinement is deferred to [`isolation-and-fork.md`](isolation-and-fork.md). diff --git a/docs/design/agent-workflows/wp-8-rivet-acp-runtime/status.md b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/status.md new file mode 100644 index 0000000000..836d60f6ee --- /dev/null +++ b/docs/design/agent-workflows/wp-8-rivet-acp-runtime/status.md @@ -0,0 +1,160 @@ +# Status + +Source of truth for this WP. Keep it current. + +## Current state + +IMPLEMENTED and verified end to end (2026-06-17). The agent service drives the harness +over ACP through a rivet `sandbox-agent` daemon, behind the unchanged `Harness` port and +`/invoke` contract. Verified: Pi and Claude Code locally; harness swap as one config +value; the full UI playground run through the live dev stack; message history; and the +agent's spans nested under the `/invoke` workflow span. Tools are wired over MCP with a +documented harness limitation, and Daytona is wired with a documented snapshot +prerequisite (both below). + +### What was verified + +| Requirement | Status | How | +| --- | --- | --- | +| Drive the harness over ACP via rivet | done | `runRivet.ts` + `sandbox-agent@0.4.2`; Pi & Claude answer over ACP | +| Harness swap as config | done | `AGENTA_AGENT_HARNESS=pi\|claude`; same config, both answer (host) | +| Run locally (self-hosted) | done | rivet `local` provider; host CLI + dockerized sidecar in the dev stack | +| Tracing nested under `/invoke` | done | live: `_agent`→`invoke_agent`→`turn 0`→`chat ` in one trace, span_ids chained | +| End to end from the UI | done | playground run in pi-agents → "Success" reply via the rivet path | +| Message history | done | prior turns replayed as transcript context (client/playground holds history) | +| Tools | mechanism | MCP bridge → `/tools/call` built & bridge-verified; harness MCP support gates it (below) | +| Daytona sandbox | wired | provider branch implemented + auth upload; needs a rivet+Pi snapshot (below) | + +### Implementation map + +- `services/agent/src/runRivet.ts` — the rivet driver (same `/run` contract as `runPi`). +- `services/agent/src/agenta-otel.ts` — added `createRivetOtel` (ACP-event-stream tracer). +- `services/agent/src/toolBridge.ts` + `toolBridgeServer.ts` — tools over MCP → `/tools/call`. +- `services/agent/src/{server,cli}.ts` — route `/run` to `runRivet` (`AGENT_BACKEND`, or auto by request shape). +- `services/oss/src/agent_pi/rivet_harness.py` — `RivetHarness` (HTTP sidecar or subprocess). +- `services/oss/src/agent.py` — `_build_harness()` rivet branch (`AGENTA_AGENT_RUNTIME=rivet`). +- `hosting/docker-compose/ee/docker-compose.dev.yml` — rivet env on the `services` container. + +### Tracing: propagate trace context into the harness (the WP-1/WP-2 mechanism) + +For Pi we DON'T build spans in the runner. We propagate the caller's trace context into +Pi and let Pi emit its real span tree (`invoke_agent` → `turn N` → `chat ` / +`execute_tool`, with real token usage), via the `agenta` Pi extension. The extension is +bundled self-contained with esbuild (`scripts/build-extension.mjs` → `dist/extensions/ +agenta.js`), installed into Pi's agent dir (local: copied; Daytona: uploaded via the +sandbox FS API), and reads everything from env (`AGENTA_TRACEPARENT`, `AGENTA_OTLP_*`, +`AGENTA_TOOL_*`). It is inert when no Agenta env is set, so a global install is safe. +Verified live: `chat gpt-5.5` carries `input_tokens`/`cost` and nests under the caller's +`/invoke` span, in both REST (ApiKey) and the browser playground (session JWT). + +Cumulative roll-up: the harness span tree and the `_agent` workflow span are exported in +separate OTLP batches (different processes), so Agenta's per-batch cumulative roll-up +cannot bridge them. We close that by passing the run's token/cost totals back (Pi writes +them on `agent_end` to `AGENTA_USAGE_OUT`; `runRivet` returns them; `agent.py` stamps +`gen_ai.usage.*` on the workflow span in-process). Verified: `_agent` shows +`ag.metrics.tokens.cumulative` and the trace list Usage/Cost columns populate. + +For non-Pi harnesses (e.g. Claude) the runner still builds the span tree from the ACP +event stream (`createRivetOtel`, `emitSpans:true`) as a uniform fallback. + +The runner-built chat span is named from the model the harness actually resolved, not the +requested one: `runRivet` creates the tracer after `applyModel`, so when a harness rejects +the requested id and keeps its own default (Claude ignores `gpt-5.5`; the in-sandbox Pi on +Daytona only advertises `default`), the span is `chat` rather than falsely `chat gpt-5.5`. +Pi-local sets the requested model and the Pi extension emits the real `chat `. + +### Tools: Pi-native (no MCP) + +Pi tools are delivered the Pi-native way: the same extension calls `pi.registerTool` for +each backend-resolved spec, and each tool's `execute` POSTs back to Agenta's +`/tools/call` (the WP-7 envelope; the provider key + connection auth stay server-side). +Verified live: a real Composio `github_whoami` tool runs in the dockerized playground and +shows an `execute_tool` span. Other (MCP-capable) harnesses get tools over ACP MCP via +`toolBridge.ts` instead. + +### Daytona status — working (fast, traced) + +`sandbox=daytona` runs Pi end to end in ~10s (verified live via `/invoke`), with the full +trace tree. Per invoke runRivet creates an ephemeral sandbox from the pre-baked snapshot +`agenta-rivet-pi` (rivet `-full` image + `pi` baked in, built by +`poc/build_rivet_snapshot.py`), uploads AGENTS.md, runs the ACP session, and destroys it +in `finally`. The earlier ~150s came from a per-invoke `npm install pi`; the snapshot +removes it (`AGENTA_RIVET_DAYTONA_INSTALL_PI=false`). + +Credentials: the `agent-pi` sidecar gets scoped `DAYTONA_API_KEY`/`API_URL`/`TARGET`. The +model provider key (OpenAI/Anthropic) is resolved from the project vault and injected as +the sandbox env var, so no Codex/OAuth subscription token leaves the box (OAuth upload +remains a fallback only when no key exists). + +Tracing: the in-sandbox harness can't reach Agenta's OTLP, so on Daytona the **runner** +builds the span tree from the ACP event stream (reliable export from the sidecar) and the +token total is passed back onto the `_agent` workflow span. Verified: 4-span tree +`_agent → invoke_agent → turn → chat`, `_agent` tokens populated. + +Known limitation: the freshly-provisioned Pi inside the Daytona snapshot advertises only +`model: default` over ACP (it lacks the model catalog the dev's local Pi loads from its +`auth.json`), so a playground `model` choice is not honored on Daytona — Pi runs its +default with whatever provider key the vault supplied. The model axis is honored on +Pi-local. Settling the in-sandbox Pi model config is follow-up. + +Notable fixes: the rivet daytona provider's default `image` conflicts with `snapshot` +("Cannot specify a snapshot when using a build info entry") — suppressed by passing +`image: undefined` in the create opts. The Daytona preview proxy uses cookie auth — a +cookie-persisting `fetch` is passed to `SandboxAgent.start`. Unhandled rejections from the +rivet SDK are caught in `server.ts` so one bad run can't crash the sidecar. + +### Credentials (API key or OAuth) + +Auth is a resolved credential, not hardcoded. The agent fetches the project vault's +`provider_key` secrets and injects each as its env var (`OPENAI_API_KEY`, +`ANTHROPIC_API_KEY`, …) into the harness; the harness uses whichever its model needs. With +no key the harness falls back to its own login (OAuth): local Pi uses the Codex login; +Claude needs an Anthropic key (verified: with credit, `/invoke` returns a clean reply; the +guardrail surfaces "insufficient credit"/"authentication failed" as one line). + +## Decisions + +| Decision | Rationale | +| --- | --- | +| Adopt rivet unmodified (no Rust fork) | It gives ACP, harness swap, local, and streaming. The only gap (the jail) is deferred. | +| Licensing is clear for commercial use | rivet is Apache-2.0 (binary self-buildable, no phone-home); all shipped deps are MIT/Apache-2.0. Claude Code (proprietary) and Daytona's AGPL server are user-brought, weak coupling. Never bundle Claude Code. See [`research.md`](research.md#licensing-verified-safe-to-adopt-commercially). | +| Drive the harness over ACP via rivet | Satisfies "ACP, not Pi JSON". | +| Keep the `Harness` port and `/invoke` unchanged | The seam is right; only the adapter below it changes. Keep the legacy Pi adapters working. | +| Add `RivetHarness` (Python) + `runRivet.ts` (wraps the rivet SDK) | Thin Python adapter over a TS runner; reuse the `/run` contract. | +| Sandbox and harness are two orthogonal config axes | Swap each independently; matches rivet (provider vs `agent`). | +| One daemon and one sandbox per invoke (cold) | Mirrors the shipped code-evaluator `DaytonaRunner` (ephemeral per execution). Makes daemon env per-invoke and needs no jail. | +| Inject trace + secrets at the daemon's birth (SDK `env` local, sandbox `env_vars` Daytona) | Per-invoke daemon means per-invoke env. No fork of rivet or adapters needed. | +| Tracing is in scope; standalone traces are not acceptable | Pi reuses `agenta-otel` as a Pi extension; Claude Code uses `CLAUDE_CODE_ENABLE_TELEMETRY` + `OTEL_*` + `TRACEPARENT` in `-p` mode. | +| Local run = run the open-source rivet server locally; Python wraps the client | Rivet is Apache-2.0. Not a special case. | +| Session = persisted message history + ephemeral sandbox; continue via ACP `session/load` | No persistent FS writes, so nothing on disk to keep. Zero at-rest cost. | +| Concurrency mirrors evaluations (taskiq + Redis + shared semaphore) | Each slot = one ephemeral sandbox; the semaphore caps Daytona cost/quota. | +| Tools split into definition + swappable body, per-harness over MCP; deferred build | Enables test variants with mock bodies; body model is general, not Agenta-specific. | +| Input variables substituted into AGENTS.md | Mirrors prompt-template variables. | +| Secrets via launch env, never in the agent-visible filesystem | No jail. | +| `model` semantics owned by the harness adapter | The adapter normalizes per harness. Not an open question. | +| Adapters live in the SDK | Backend and standalone share one implementation. | + +## Open questions + +1. **SDK API names.** Verify the exact rivet SDK package name and method signatures + (`start` / `createSession` / `prompt` / event names) against the installed version + during Phase 0. +2. **Message-history store and truncation.** Backend DB on the platform, local file + standalone; pick a truncation or summarization policy so replay does not grow tokens + unbounded. +3. **Concurrency placement.** Dispatch agent invokes through the taskiq worker, or bound a + synchronous `/invoke` with a semaphore? Depends on what the playground expects. +4. **Claude Code ACP-mode span completeness.** Confirm the current beta behavior before + relying on Claude Code traces; a known bug may drop `interaction`/`tool` spans. +5. **Daytona snapshot contents.** Settle exactly what the snapshot pre-installs (rivet, + both harness CLIs, both adapters, the `agenta-otel` extension) and how it is built. + +## Future (returns only if we change the lifecycle) + +- A warm shared daemon multiplexing concurrent invokes would re-introduce the per-session + env problem (fork an adapter to read ACP `_meta.traceparent`, TypeScript not Rust) and + the need for a filesystem jail. The per-invoke model avoids both. + +## Next step + +Phase 0 spike. See [`plan.md`](plan.md). diff --git a/hosting/docker-compose/ee/docker-compose.dev.yml b/hosting/docker-compose/ee/docker-compose.dev.yml index e09b82b29f..b1f9a2d773 100644 --- a/hosting/docker-compose/ee/docker-compose.dev.yml +++ b/hosting/docker-compose/ee/docker-compose.dev.yml @@ -394,8 +394,13 @@ services: - ${ENV_FILE:-./.env.ee.dev} environment: DOCKER_NETWORK_MODE: ${DOCKER_NETWORK_MODE:-bridge} - # Agent workflow (WP-2): reach the Pi harness sidecar in-network. + # Agent workflow (WP-2): reach the harness wrapper sidecar in-network. AGENTA_AGENT_PI_URL: http://agent-pi:8765 + # Agent runtime (WP-8): drive the harness over ACP via a rivet daemon. + # Harness (pi/claude) and sandbox (local/daytona) are independent axes. + AGENTA_AGENT_RUNTIME: ${AGENTA_AGENT_RUNTIME:-rivet} + AGENTA_AGENT_HARNESS: ${AGENTA_AGENT_HARNESS:-pi} + AGENTA_AGENT_SANDBOX: ${AGENTA_AGENT_SANDBOX:-local} # === NETWORK ============================================== # networks: - agenta-network @@ -429,11 +434,10 @@ services: exec node_modules/.bin/tsx src/server.ts" # === CONFIGURATION ======================================== # # Deliberately NO env_file: the Pi sandbox must not inherit the stack's - # secrets (COMPOSIO_API_KEY, STRIPE/POSTHOG/GOOGLE/DAYTONA keys, ...). Tools - # run server-side via /tools/call, so the sandbox only needs its own port, - # the Pi login (mounted below), and the OTLP export fallback. The wrapper - # reads exactly: PORT, PI_CODING_AGENT_DIR, AGENTA_HOST, AGENTA_API_KEY, and - # the optional AGENTA_AGENT_TOOL_CALL_TIMEOUT_MS / OTEL_SERVICE_NAME. + # secrets (COMPOSIO_API_KEY, STRIPE/POSTHOG/GOOGLE keys, ...). Tools run + # server-side via /tools/call, so the sandbox only needs its own port, the Pi + # login (mounted below), the OTLP export fallback, and — for the rivet `daytona` + # sandbox axis (WP-8) — the Daytona credentials the SDK reads to create sandboxes. environment: PORT: "8765" PI_CODING_AGENT_DIR: /pi-agent @@ -441,6 +445,15 @@ services: # credential). Must be reachable from this container. AGENTA_HOST: ${AGENTA_HOST:-http://144.76.237.122:8280} AGENTA_API_KEY: ${AGENTA_API_KEY:-} + # Daytona sandbox axis: the rivet daytona provider's `new Daytona()` reads + # these. Scoped to Daytona only (not the full stack secret set). + DAYTONA_API_KEY: ${DAYTONA_API_KEY:-} + DAYTONA_API_URL: ${DAYTONA_API_URL:-} + DAYTONA_TARGET: ${DAYTONA_TARGET:-} + # Pre-baked snapshot (rivet daemon + Pi + Claude + certs) so Daytona runs skip + # the ~150s per-invoke `npm install pi`. Built by poc/build_rivet_snapshot.py. + AGENTA_RIVET_DAYTONA_SNAPSHOT: ${AGENTA_RIVET_DAYTONA_SNAPSHOT:-agenta-rivet-pi} + AGENTA_RIVET_DAYTONA_INSTALL_PI: ${AGENTA_RIVET_DAYTONA_INSTALL_PI:-false} # === STORAGE ============================================== # volumes: - ../../../services/agent/src:/app/src diff --git a/services/agent/docker/Dockerfile.dev b/services/agent/docker/Dockerfile.dev index 2b2320600e..4f2f64f126 100644 --- a/services/agent/docker/Dockerfile.dev +++ b/services/agent/docker/Dockerfile.dev @@ -8,6 +8,13 @@ FROM node:24-slim WORKDIR /app +# CA certificates: the rivet daemon (Rust) downloads harness CLIs (e.g. Claude Code) over +# HTTPS using the system trust store, which node:*-slim omits — without this the daemon's +# `install-agent claude` fails TLS verification. git lets npm/installers fetch git deps. +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* + RUN corepack enable # Install deps as a cached layer (manifest + lockfile only). @@ -16,8 +23,14 @@ RUN pnpm install --frozen-lockfile # Fallback copy for non-mounted runs; in dev these are bind-mounted over. COPY tsconfig.json ./ +COPY scripts ./scripts COPY src ./src +# Bundle the Agenta Pi extension (tracing + tools) into dist/. dist/ is NOT bind-mounted +# in dev, so this baked copy is what runRivet installs into Pi's agent dir. Rebuild the +# image after editing src/piExtension.ts or src/agenta-otel.ts. +RUN pnpm run build:extension + ENV NODE_ENV=development \ PORT=8765 diff --git a/services/agent/package.json b/services/agent/package.json index 5f2a39fb88..231b6ff5f6 100644 --- a/services/agent/package.json +++ b/services/agent/package.json @@ -9,19 +9,34 @@ "run:cli": "tsx src/cli.ts", "serve": "tsx src/server.ts", "serve:watch": "tsx watch src/server.ts", + "build:extension": "node scripts/build-extension.mjs", "login": "pi" }, "dependencies": { + "@daytonaio/sdk": "^0.187.0", "@earendil-works/pi-coding-agent": "0.79.4", "@opentelemetry/api": "1.9.0", "@opentelemetry/exporter-trace-otlp-proto": "0.54.0", "@opentelemetry/resources": "1.28.0", "@opentelemetry/sdk-trace-base": "1.28.0", "@opentelemetry/sdk-trace-node": "1.28.0", - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/semantic-conventions": "1.28.0", + "@zed-industries/claude-agent-acp": "^0.23.1", + "pi-acp": "0.0.29", + "sandbox-agent": "0.4.2" }, "devDependencies": { - "tsx": "4.19.2", - "@types/node": "22.10.2" + "@types/node": "22.10.2", + "esbuild": "0.23.1", + "tsx": "4.19.2" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@sandbox-agent/cli-linux-x64", + "@sandbox-agent/cli-darwin-arm64", + "@sandbox-agent/cli-darwin-x64", + "@sandbox-agent/cli-linux-arm64", + "esbuild" + ] } } diff --git a/services/agent/pnpm-lock.yaml b/services/agent/pnpm-lock.yaml index eab8e5fb3a..7bd7134915 100644 --- a/services/agent/pnpm-lock.yaml +++ b/services/agent/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@daytonaio/sdk': + specifier: ^0.187.0 + version: 0.187.0(ws@8.21.0) '@earendil-works/pi-coding-agent': specifier: 0.79.4 version: 0.79.4(ws@8.21.0)(zod@4.4.3) @@ -29,16 +32,49 @@ importers: '@opentelemetry/semantic-conventions': specifier: 1.28.0 version: 1.28.0 + '@zed-industries/claude-agent-acp': + specifier: ^0.23.1 + version: 0.23.1 + pi-acp: + specifier: 0.0.29 + version: 0.0.29 + sandbox-agent: + specifier: 0.4.2 + version: 0.4.2(@daytonaio/sdk@0.187.0(ws@8.21.0))(zod@4.4.3) devDependencies: '@types/node': specifier: 22.10.2 version: 22.10.2 + esbuild: + specifier: 0.23.1 + version: 0.23.1 tsx: specifier: 4.19.2 version: 4.19.2 packages: + '@agentclientprotocol/sdk@0.16.1': + resolution: {integrity: sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + '@agentclientprotocol/sdk@0.17.0': + resolution: {integrity: sha512-inBMYAEd9t4E+ULZK2os9kmLG5jbPvMLbPvY71XDDem1YteW/uDwkahg6OwsGR3tvvgVhYbRJ9mJCp2VXqG4xQ==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + '@agentclientprotocol/sdk@0.26.0': + resolution: {integrity: sha512-ialrcI+RzKOYe+fw+TfpyTdRmEoqIkXLlwbTi6XgaXXfdhNcdod7TmE1VsTnG3yTlox8TMTSMQgWbLLbz3r86Q==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + '@anthropic-ai/claude-agent-sdk@0.2.83': + resolution: {integrity: sha512-O8g56htGMxrwbjCbqUqRBMNC0O98B7SkPnfQC7vmo3w2DVnUrBj3qat/IBLB8SI4sjVSZHeJrcK7+ozsCzStSw==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^4.0.0 + '@anthropic-ai/sdk@0.91.1': resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} hasBin: true @@ -52,6 +88,12 @@ packages: resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -65,54 +107,112 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + '@aws-sdk/checksums@3.1000.6': + resolution: {integrity: sha512-RMCrCteiUwYTEv2G9zfP/BEuKHv57665vVieJyp9cf8VgilWxP/KrWVtMdfdDlIH8nFhvu3rIMc29z3ebGEZ1w==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-bedrock-runtime@3.1048.0': resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/client-s3@3.1070.0': + resolution: {integrity: sha512-B/OUiCqGQ4Zr7v9gFFyiuitKN2c0PIgvOlQb5bYg1SM2y0F8a5JQ7FNsjRcl+d2PqYWLHwHx12CvZDyLn4KxIw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/core@3.974.20': resolution: {integrity: sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==} engines: {node: '>=20.0.0'} + '@aws-sdk/core@3.974.21': + resolution: {integrity: sha512-P5JAHvn4dTi96UsAGS67LVOqqpUNNRhnfFXqzCYtdBIGZtqBue4CXvRr9YenOO7PALj/Pn8uuyw53FBCiCYw8w==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.972.46': resolution: {integrity: sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.972.47': + resolution: {integrity: sha512-3YoPwJczcc+MtX2xxXaYaOOWO6xKUJr1ZIIDIFuninr51BYONVVcF/CP8K2xfVRC/PztJjqKWxNGFH7BWQAw1Q==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.972.48': resolution: {integrity: sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.972.49': + resolution: {integrity: sha512-2UtGUPy+x3lqyceHrtC1uEuVxBZbDalPF6KAFqBwYgm4edWdBrZKNnCqzDs7KynWUvEC6mrR+ojRk+ZgQz9C2w==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.972.53': resolution: {integrity: sha512-ZfdhIOR41q8TcWEnUac+gCOb+O2LBWdHLmjedXpXz4IEFW2ppNuFcm6p0sMTavpM+zD5TYfpH5Gp7guRyqSgsQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.972.54': + resolution: {integrity: sha512-Hx4gO4YRjFwitf3MVl3cDwYe1aryJthC4txVl9b+JAURovA50M2ywf9r8j1E/Q6SCTPT4qQpjOAbKYIC9CG+Vw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-login@3.972.52': resolution: {integrity: sha512-9hu2oR0qH7Fst5Tzdx+UWxm+w5zCXtErTLtOOW5hwwQc170CLwOeniRxyFY6s9mHfGEfC5zFukNBdKBwJR8mhQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-login@3.972.53': + resolution: {integrity: sha512-+71sluhkgPqdhbbD3UDwUpj24GCkng9HQx6z7qoBFb8dwkF4ktpOcVKDeHpgg8PvBgLYwAnUYLTEGRC/PniCiQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.972.55': resolution: {integrity: sha512-zMGLa/dhESVqmCD7mmIFFKSwSFrJGScvCXcjvBZEVOOMauFS5JRQvLTMukFpMEFWiV6dTAlsen2ATDBulLPtbg==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.972.56': + resolution: {integrity: sha512-iI+4o0dvQQ4NHel4FMDiFy5q2gaU/ryLK3niOsoPccAt9WLFRkV4XTYPWRr9XvmBUqEzXG73S4p/8gm0Lu/W3A==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.972.46': resolution: {integrity: sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.972.47': + resolution: {integrity: sha512-tAizPm9IFo/PHn06c+LQJlzfY2AGOlyF0CUljFejrU6LcZBjnk8pmbZK3/xoIDdnIzjEdbClfvY3mXfr818ZEg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.972.52': resolution: {integrity: sha512-nb2/n4o/HQf+FVpVbZe9vCTFngmuDoIsltMgLAtjixaKzvzhB4J8WSDFyWgnErgLHk55ctWH+I4PU+LIHhyffg==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.972.53': + resolution: {integrity: sha512-pUXE3fu4tfEDV8BksIgf4dXvuIH10FhwHMl/wu8rBD5T1sMpryQWFVitH3kdPS90wlgrGYJQ/meQTSPacyZfeg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.52': resolution: {integrity: sha512-lKj6aRSGbqLmpYmM24bY7a1Xmfcq2vkE3hv8CSPYfc1yCu0BPu/XEJ1L4Fm61MsU6ULLNSG8UGsffNoFUBjESA==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.53': + resolution: {integrity: sha512-JmMGlhVvSj8uSG9CpeDkJAXT35H89tc6v84iMgEIE75q4yp1MKVVKvopv6Gg28HJIR7hMNkojRF8H2m5W44wyg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/eventstream-handler-node@3.972.21': resolution: {integrity: sha512-mVC0hOmwGJmNFezZ+wM8Sqfap/LjsMavEf2Evl0YWrLAcrdZOEdjnY8nRvgakVViWJSGm2eJxLuPVHGdeV06kA==} engines: {node: '>=20.0.0'} + '@aws-sdk/lib-storage@3.1070.0': + resolution: {integrity: sha512-TMfkkBaLIlHhqt28wJp14EhATO9WbFwEheCi5K5gahYKQNWCUE4l4CmuWl1Wi8j0ZeVs/vCaSWxHv6DahrHOzQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.1070.0 + '@aws-sdk/middleware-eventstream@3.972.17': resolution: {integrity: sha512-tdbnXbw73ww62ABWP0G0Z/euvFowEEvAoi/zG4NaZo7HJFpfGho/Z65HyVzkJLT1cMsUregr4pTyxljlarT0wA==} engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.974.31': + resolution: {integrity: sha512-Yzj6NRYVZdBaCp7o1BwHGyeDBfixdeToLIAMprshIITEdl9wKVSiidVOfeaiH8FyeC1hBmBfDZFvs/aH1Y3xpw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.52': + resolution: {integrity: sha512-rerjP08onRqkBh0AcCqip6GkKvESapmLoTgi1xysZ4C6a1xMrIMtTBcEbUb6EY71oeajnigeUD4KwZjtIO+aWQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-websocket@3.972.28': resolution: {integrity: sha512-SCW06Zjugn86pq7+dxGnFcyWJuEWHT753HTU/Vj/OzVxP+NoShwdAr4ynxAcvWL883OgRVbSqW3ohnjIxwXjjw==} engines: {node: '>= 14.0.0'} @@ -121,10 +221,18 @@ packages: resolution: {integrity: sha512-IYJuLpXp2DEILVQpQOy0PMpkftv0AHEOCn52o0atyOaumA0CdWQ3klPyXdViGYLbNpESsVFMVybvHUeZAuiGxA==} engines: {node: '>=20.0.0'} + '@aws-sdk/nested-clients@3.997.21': + resolution: {integrity: sha512-eC7Vl7Qom/BGhZjG9GEqPwdQ/fk45hg1t5LP4EUxG5d1fdshLbaxCiwh/tszUzDX/4mW40mu2QsbeJJRPBbqUw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.34': resolution: {integrity: sha512-mx1L5qlumSOt/nKM3BFaHE2HVkWwz0i4Bw0pyYO42FfX/FeLlo8YI6csC0gSPprEk6fTIqI+CZN9RwUwKd5krQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.35': + resolution: {integrity: sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1048.0': resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} engines: {node: '>=20.0.0'} @@ -133,10 +241,18 @@ packages: resolution: {integrity: sha512-UqEUJq7dqa44hneLDUcX7UJy95cg8YqEWyakRpvIPnrNS3Mq+UlQHgCDGu5pvwAPtlIW4qcYbvW6reG6++FyvA==} engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1069.0': + resolution: {integrity: sha512-ks4X+kngC3PA5howV7Qu1TgG4bfC4jPykKdvw3nmBSXR9yZxRJouBholFSNQ5kY3L+Fgwyw+LCjzQmNi+KR91g==} + engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.973.12': resolution: {integrity: sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==} engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.973.13': + resolution: {integrity: sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-locate-window@3.965.7': resolution: {integrity: sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==} engines: {node: '>=20.0.0'} @@ -145,6 +261,10 @@ packages: resolution: {integrity: sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/xml-builder@3.972.30': + resolution: {integrity: sha512-StElZPEoBquWwNqw1AcfpzEyZqJvFxouG+mpDNYlcH6ZOrqd2CuIryv+8LV8gNHZUOyKyJF3Dq9vxaXEmDR9TQ==} + engines: {node: '>=20.0.0'} + '@aws/lambda-invoke-store@0.2.4': resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} engines: {node: '>=18.0.0'} @@ -153,6 +273,16 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@daytona/api-client@0.187.0': + resolution: {integrity: sha512-riKOJ6eSuy67DL6iJlAa3Bfjnm4iQmkOdJk0B5hqrYMZeZmVDsgdiZtYvFpyoa+2KCZFNb0Gs5dQwO1d6NhGCw==} + + '@daytona/toolbox-api-client@0.187.0': + resolution: {integrity: sha512-T5F+++cakH5Nl67fR53SLkEeTgayEmw5JFXhdMKRgk/mUf6IL30nHC/2kIbc4yK8Iol6YVo9vlG4cLk+4x8y1A==} + + '@daytonaio/sdk@0.187.0': + resolution: {integrity: sha512-j6PfT6735Uu34t4JoxBi4IMh1JLNrEDg5w3ZUaT0Mgkas2UfoAAhQ2Eg1LqMhy4n1CTffvCyJID9W6Ldi4xEGQ==} + deprecated: 'Moved to @daytona/sdk, same API, no breaking changes. Please update: npm uninstall @daytonaio/sdk && npm i @daytona/sdk' + '@earendil-works/pi-agent-core@0.79.4': resolution: {integrity: sha512-xkaZ3yK2XbP9HYdHrrdj/6HqZPM0o/mwbjMSU4RTJyR3HjDG0ZrPz76Hg6s0W+G4u6PpJr1mGx/srCG+3eQA8A==} engines: {node: '>=22.19.0'} @@ -324,6 +454,124 @@ packages: '@modelcontextprotocol/sdk': optional: true + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@iarna/toml@2.2.5': + resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@mariozechner/clipboard-darwin-arm64@0.3.9': resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==} engines: {node: '>= 10'} @@ -398,6 +646,22 @@ packages: '@nodable/entities@2.2.0': resolution: {integrity: sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@opentelemetry/api-logs@0.217.0': + resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.54.0': resolution: {integrity: sha512-9HhEh5GqFrassUndqJsyW7a0PzfyWr2eV2xwzHLIS+wX3125+9HE9FMRAKmJRwxZhgZGwH3HNQQjoMGZqmOeVA==} engines: {node: '>=14'} @@ -406,12 +670,24 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@opentelemetry/configuration@0.217.0': + resolution: {integrity: sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks@1.28.0': resolution: {integrity: sha512-igcl4Ve+F1N2063PJUkesk/GkYyuGIWinYkSyAFTnIj3gzrOgvOA4k747XNdL47HRRL1w/qh7UW8NDuxOLvKFA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/context-async-hooks@2.7.1': + resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@1.27.0': resolution: {integrity: sha512-yQPKnK5e+76XuiqUH/gKyS8wv/7qITd5ln56QkBTf3uggr0VkXOXfcaAuG330UfdYu83wsyoBwqwxigpIG+Jkg==} engines: {node: '>=14'} @@ -424,18 +700,126 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.7.1': + resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.8.0': + resolution: {integrity: sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0': + resolution: {integrity: sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.217.0': + resolution: {integrity: sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.217.0': + resolution: {integrity: sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0': + resolution: {integrity: sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.217.0': + resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0': + resolution: {integrity: sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.217.0': + resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0': + resolution: {integrity: sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.217.0': + resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.217.0': + resolution: {integrity: sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-trace-otlp-proto@0.54.0': resolution: {integrity: sha512-cpDQj5wl7G8pLu3lW94SnMpn0C85A9Ehe7+JBow2IL5DGPWXTkynFngMtCC3PpQzQgzlyOVe0MVZfoBB3M5ECA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-zipkin@2.7.1': + resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-http@0.217.0': + resolution: {integrity: sha512-B88Y7k5A9a60pHUboFoeJlgVwXq2T0rsZKj6dTwzSMKSOsNXR4Jz5ovwprVn3kHLAZrkyLEjQtBJ34DYHs1U4Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.217.0': + resolution: {integrity: sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.217.0': + resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.54.0': resolution: {integrity: sha512-g+H7+QleVF/9lz4zhaR9Dt4VwApjqG5WWupy5CTMpWJfHB/nLxBbX73GBZDgdiNfh08nO3rNa6AS7fK8OhgF5g==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-grpc-exporter-base@0.217.0': + resolution: {integrity: sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.217.0': + resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.54.0': resolution: {integrity: sha512-jRexIASQQzdK4AjfNIBfn94itAq4Q8EXR9d3b/OVbhd3kKQKvMr7GkxYDjbeTbY7hHCOLcLfJ3dpYQYGOe8qOQ==} engines: {node: '>=14'} @@ -448,12 +832,24 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/propagator-b3@2.7.1': + resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/propagator-jaeger@1.28.0': resolution: {integrity: sha512-wKJ94+s8467CnIRgoSRh0yXm/te0QMOwTq9J01PfG/RzYZvlvN8aRisN2oZ9SznB45dDGnMj3BhUlchSA9cEKA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/propagator-jaeger@2.7.1': + resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/resources@1.27.0': resolution: {integrity: sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==} engines: {node: '>=14'} @@ -466,6 +862,24 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.8.0': + resolution: {integrity: sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.217.0': + resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-logs@0.54.0': resolution: {integrity: sha512-HeWvOPiWhEw6lWvg+lCIi1WhJnIPbI4/OFZgHq9tKfpwF3LX6/kk3+GR8sGUGAEZfbjPElkkngzvd2s03zbD7Q==} engines: {node: '>=14'} @@ -478,6 +892,18 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.217.0': + resolution: {integrity: sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@1.27.0': resolution: {integrity: sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==} engines: {node: '>=14'} @@ -490,12 +916,30 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.7.1': + resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.8.0': + resolution: {integrity: sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-node@1.28.0': resolution: {integrity: sha512-N0sYfYXvHpP0FNIyc+UfhLnLSTOuZLytV0qQVrDWIlABeD/DWJIGttS7nYeR14gQLXch0M1DW8zm3VeN6Opwtg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/sdk-trace-node@2.7.1': + resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/semantic-conventions@1.27.0': resolution: {integrity: sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==} engines: {node: '>=14'} @@ -504,6 +948,10 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -522,6 +970,9 @@ packages: '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} @@ -531,6 +982,38 @@ packages: '@protobufjs/utf8@1.1.1': resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@sandbox-agent/cli-darwin-arm64@0.4.2': + resolution: {integrity: sha512-+L1O8SI7k/LLhyB4dG0ghmz1cJHa0WtVjuRTrEE2gw/5EbGLWopPBsCVCmQ7snrQ4fPwtaiZDhfExcEj1VI7aw==} + cpu: [arm64] + os: [darwin] + + '@sandbox-agent/cli-darwin-x64@0.4.2': + resolution: {integrity: sha512-dDg/EwWsdgVVbJiiCX1scSNRRA48u77SsC7Tuqrfzx4fIJMLuLiIcmEtXQyCBWysSyQNV2Cr+PYXXQfCb3xg8g==} + cpu: [x64] + os: [darwin] + + '@sandbox-agent/cli-linux-arm64@0.4.2': + resolution: {integrity: sha512-TGmTUexMoubmWQyTeaOJu0rDVl2h0Ifh1pZ0ceZy7u/6Eoqs2n46CbfQtasUxZJf10uxPgRyzEDhcdDrTYVQUA==} + cpu: [arm64] + os: [linux] + + '@sandbox-agent/cli-linux-x64@0.4.2': + resolution: {integrity: sha512-H9Rbqq0DRkCHvakzefJUDrDa2y+vJjlYd5/tefzKbQ34locE13TGNygRLxdEVXpBECjK9wVdBwTVEphQNsOcjw==} + cpu: [x64] + os: [linux] + + '@sandbox-agent/cli-shared@0.4.2': + resolution: {integrity: sha512-sjZXRkKeFXCSKR6hHzF2Af8CCRO3F3WFwVQJ22+sLTXJ2xskV8lkUE4egknQU9B5BC1Zumts/YiNCFQWG85awQ==} + + '@sandbox-agent/cli-win32-x64@0.4.2': + resolution: {integrity: sha512-lZNfHWPwQe/VH51Yvrl/ATCUvBZ3a+c8mwovojhQcmZlv4QuUQPkuvxhPqHRh9AyBx78L5J/ha46es2doa34nQ==} + cpu: [x64] + os: [win32] + + '@sandbox-agent/cli@0.4.2': + resolution: {integrity: sha512-trO//ypJBSt5xkewuol9LOykvDgHwUXq8R+yQVS+0CmpN3lYUtewHkb+At9RVGRhDMmJZY2oasaXDnhfurQ33w==} + hasBin: true + '@silvia-odwyer/photon-node@0.3.4': resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} @@ -580,13 +1063,49 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@zed-industries/claude-agent-acp@0.23.1': + resolution: {integrity: sha512-aQ1gAm1MBalwEgE/VB/m4z6sXw/fRccNOW268pNLXnWV704ZuLbbm0N+oEv8KTmd53dJ6YzMhMpD8p5ig6C+sA==} + deprecated: This package has been renamed to @agentclientprotocol/claude-agent-acp. Please migrate to continue receiving updates. + hasBin: true + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + acp-http-client@0.4.2: + resolution: {integrity: sha512-3wtPieF08YIU4vNXaoL5up/1D0if4i9IX3Ye5q/bwbcwg1BKsazIK/VNNfvN4ldbPjWul69IqIOpGRS3I0qo3Q==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + anynum@1.0.0: resolution: {integrity: sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -604,13 +1123,50 @@ packages: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -628,21 +1184,68 @@ packages: supports-color: optional: true + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.23.1: resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-xml-builder@1.2.0: resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} @@ -650,19 +1253,45 @@ packages: resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} hasBin: true + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gaxios@7.1.5: resolution: {integrity: sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==} engines: {node: '>=18'} @@ -671,13 +1300,29 @@ packages: resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} engines: {node: '>=18'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -690,12 +1335,32 @@ packages: resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + hosted-git-info@9.0.3: resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -704,17 +1369,52 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@7.0.5: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + import-in-the-middle@3.0.2: + resolution: {integrity: sha512-LGLYRl0A2gtyUJb2WDliBHmk6TtlHwdDjxonacZ8QrEs/ZW+YDgNv2QAfjRQWpS8HqvNcq6GGnN6jrOa5FysDQ==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -732,6 +1432,9 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -744,6 +1447,26 @@ packages: engines: {node: '>= 18'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -752,6 +1475,13 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -780,6 +1510,10 @@ packages: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} @@ -795,6 +1529,18 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pi-acp@0.0.29: + resolution: {integrity: sha512-WL2+arwD+TFpZoXSsybopL5nOcZQSWn5W50tnXgPJeYrBBVG43afzHs7SJl1+QFNgFtKUh2xT6VqaF76Kggn3w==} + engines: {node: '>=20'} + hasBin: true + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} @@ -802,6 +1548,29 @@ packages: resolution: {integrity: sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==} engines: {node: '>=12.0.0'} + protobufjs@8.0.1: + resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} + engines: {node: '>=12.0.0'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -813,9 +1582,48 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + sandbox-agent@0.4.2: + resolution: {integrity: sha512-fH6WDQEaIrgiu93LxZcy+4Dx+t+/cslu+hzXImDyUlsaL6jV2jIv4fdxELkALlo7uzyEDVK9lmqs9qy65RHwBQ==} + peerDependencies: + '@cloudflare/sandbox': '>=0.1.0' + '@daytonaio/sdk': '>=0.12.0' + '@e2b/code-interpreter': '>=1.0.0' + '@fly/sprites': '>=0.0.1' + '@vercel/sandbox': '>=0.1.0' + computesdk: '>=0.1.0' + dockerode: '>=4.0.0' + get-port: '>=7.0.0' + modal: '>=0.1.0' + peerDependenciesMeta: + '@cloudflare/sandbox': + optional: true + '@daytonaio/sdk': + optional: true + '@e2b/code-interpreter': + optional: true + '@fly/sprites': + optional: true + '@vercel/sandbox': + optional: true + computesdk: + optional: true + dockerode: + optional: true + get-port: + optional: true + modal: + optional: true + semver@7.8.0: resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} engines: {node: '>=10'} @@ -829,12 +1637,42 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.4: + resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strnum@2.4.0: resolution: {integrity: sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==} + tar@7.5.16: + resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==} + engines: {node: '>=18'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} @@ -856,6 +1694,9 @@ packages: resolution: {integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==} engines: {node: '>=22.19.0'} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -865,6 +1706,10 @@ packages: engines: {node: '>= 8'} hasBin: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -881,21 +1726,66 @@ packages: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: zod: ^3.25.28 || ^4 + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: + '@agentclientprotocol/sdk@0.16.1(zod@4.4.3)': + dependencies: + zod: 4.4.3 + + '@agentclientprotocol/sdk@0.17.0(zod@4.4.3)': + dependencies: + zod: 4.4.3 + + '@agentclientprotocol/sdk@0.26.0(zod@3.25.76)': + dependencies: + zod: 3.25.76 + + '@anthropic-ai/claude-agent-sdk@0.2.83(zod@4.4.3)': + dependencies: + zod: 4.4.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 @@ -908,6 +1798,21 @@ snapshots: '@aws-sdk/types': 3.973.12 tslib: 2.8.1 + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.13 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.13 + '@aws-sdk/util-locate-window': 3.965.7 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + '@aws-crypto/sha256-browser@5.2.0': dependencies: '@aws-crypto/sha256-js': 5.2.0 @@ -934,6 +1839,17 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + '@aws-sdk/checksums@3.1000.6': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.974.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/client-bedrock-runtime@3.1048.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -951,6 +1867,23 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/client-s3@3.1070.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.21 + '@aws-sdk/credential-provider-node': 3.972.56 + '@aws-sdk/middleware-flexible-checksums': 3.974.31 + '@aws-sdk/middleware-sdk-s3': 3.972.52 + '@aws-sdk/signature-v4-multi-region': 3.996.35 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/fetch-http-handler': 5.4.7 + '@smithy/node-http-handler': 4.7.8 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/core@3.974.20': dependencies: '@aws-sdk/types': 3.973.12 @@ -962,6 +1895,17 @@ snapshots: bowser: 2.14.1 tslib: 2.8.1 + '@aws-sdk/core@3.974.21': + dependencies: + '@aws-sdk/types': 3.973.13 + '@aws-sdk/xml-builder': 3.972.30 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/core': 3.24.7 + '@smithy/signature-v4': 5.4.7 + '@smithy/types': 4.14.4 + bowser: 2.14.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.46': dependencies: '@aws-sdk/core': 3.974.20 @@ -970,6 +1914,14 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.47': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.972.48': dependencies: '@aws-sdk/core': 3.974.20 @@ -980,6 +1932,16 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-http@3.972.49': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/fetch-http-handler': 5.4.7 + '@smithy/node-http-handler': 4.7.8 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-ini@3.972.53': dependencies: '@aws-sdk/core': 3.974.20 @@ -996,6 +1958,22 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-ini@3.972.54': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/credential-provider-env': 3.972.47 + '@aws-sdk/credential-provider-http': 3.972.49 + '@aws-sdk/credential-provider-login': 3.972.53 + '@aws-sdk/credential-provider-process': 3.972.47 + '@aws-sdk/credential-provider-sso': 3.972.53 + '@aws-sdk/credential-provider-web-identity': 3.972.53 + '@aws-sdk/nested-clients': 3.997.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/credential-provider-imds': 4.3.9 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-login@3.972.52': dependencies: '@aws-sdk/core': 3.974.20 @@ -1005,6 +1983,15 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-login@3.972.53': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/nested-clients': 3.997.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-node@3.972.55': dependencies: '@aws-sdk/credential-provider-env': 3.972.46 @@ -1019,6 +2006,20 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-node@3.972.56': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.47 + '@aws-sdk/credential-provider-http': 3.972.49 + '@aws-sdk/credential-provider-ini': 3.972.54 + '@aws-sdk/credential-provider-process': 3.972.47 + '@aws-sdk/credential-provider-sso': 3.972.53 + '@aws-sdk/credential-provider-web-identity': 3.972.53 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/credential-provider-imds': 4.3.9 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.46': dependencies: '@aws-sdk/core': 3.974.20 @@ -1027,6 +2028,14 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.47': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-sso@3.972.52': dependencies: '@aws-sdk/core': 3.974.20 @@ -1037,6 +2046,16 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-sso@3.972.53': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/nested-clients': 3.997.21 + '@aws-sdk/token-providers': 3.1069.0 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/credential-provider-web-identity@3.972.52': dependencies: '@aws-sdk/core': 3.974.20 @@ -1046,6 +2065,15 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/credential-provider-web-identity@3.972.53': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/nested-clients': 3.997.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/eventstream-handler-node@3.972.21': dependencies: '@aws-sdk/types': 3.973.12 @@ -1053,6 +2081,16 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/lib-storage@3.1070.0(@aws-sdk/client-s3@3.1070.0)': + dependencies: + '@aws-sdk/client-s3': 3.1070.0 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + '@aws-sdk/middleware-eventstream@3.972.17': dependencies: '@aws-sdk/types': 3.973.12 @@ -1060,6 +2098,20 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/middleware-flexible-checksums@3.974.31': + dependencies: + '@aws-sdk/checksums': 3.1000.6 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.52': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/signature-v4-multi-region': 3.996.35 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/middleware-websocket@3.972.28': dependencies: '@aws-sdk/core': 3.974.20 @@ -1083,6 +2135,19 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/nested-clients@3.997.21': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.974.21 + '@aws-sdk/signature-v4-multi-region': 3.996.35 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/fetch-http-handler': 5.4.7 + '@smithy/node-http-handler': 4.7.8 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.34': dependencies: '@aws-sdk/types': 3.973.12 @@ -1090,6 +2155,13 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.35': + dependencies: + '@aws-sdk/types': 3.973.13 + '@smithy/signature-v4': 5.4.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/token-providers@3.1048.0': dependencies: '@aws-sdk/core': 3.974.20 @@ -1108,11 +2180,25 @@ snapshots: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/token-providers@3.1069.0': + dependencies: + '@aws-sdk/core': 3.974.21 + '@aws-sdk/nested-clients': 3.997.21 + '@aws-sdk/types': 3.973.13 + '@smithy/core': 3.24.7 + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/types@3.973.12': dependencies: '@smithy/types': 4.14.4 tslib: 2.8.1 + '@aws-sdk/types@3.973.13': + dependencies: + '@smithy/types': 4.14.4 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.965.7': dependencies: tslib: 2.8.1 @@ -1123,10 +2209,60 @@ snapshots: fast-xml-parser: 5.7.3 tslib: 2.8.1 + '@aws-sdk/xml-builder@3.972.30': + dependencies: + '@smithy/types': 4.14.4 + fast-xml-parser: 5.7.3 + tslib: 2.8.1 + '@aws/lambda-invoke-store@0.2.4': {} '@babel/runtime@7.29.7': {} + '@daytona/api-client@0.187.0': + dependencies: + axios: 1.18.0 + transitivePeerDependencies: + - debug + - supports-color + + '@daytona/toolbox-api-client@0.187.0': + dependencies: + axios: 1.18.0 + transitivePeerDependencies: + - debug + - supports-color + + '@daytonaio/sdk@0.187.0(ws@8.21.0)': + dependencies: + '@aws-sdk/client-s3': 3.1070.0 + '@aws-sdk/lib-storage': 3.1070.0(@aws-sdk/client-s3@3.1070.0) + '@daytona/api-client': 0.187.0 + '@daytona/toolbox-api-client': 0.187.0 + '@iarna/toml': 2.2.5 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + axios: 1.18.0 + busboy: 1.6.0 + dotenv: 17.4.2 + expand-tilde: 2.0.2 + fast-glob: 3.3.3 + form-data: 4.0.6 + isomorphic-ws: 5.0.0(ws@8.21.0) + pathe: 2.0.3 + shell-quote: 1.8.4 + tar: 7.5.16 + transitivePeerDependencies: + - debug + - supports-color + - ws + '@earendil-works/pi-agent-core@0.79.4(ws@8.21.0)(zod@4.4.3)': dependencies: '@earendil-works/pi-ai': 0.79.4(ws@8.21.0)(zod@4.4.3) @@ -1279,8 +2415,90 @@ snapshots: - supports-color - utf-8-validate - '@mariozechner/clipboard-darwin-arm64@0.3.9': - optional: true + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.4 + yargs: 17.7.2 + + '@iarna/toml@2.2.5': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@mariozechner/clipboard-darwin-arm64@0.3.9': + optional: true '@mariozechner/clipboard-darwin-universal@0.3.9': optional: true @@ -1334,16 +2552,42 @@ snapshots: '@nodable/entities@2.2.0': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@opentelemetry/api-logs@0.217.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.54.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api@1.9.0': {} + '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + yaml: 2.9.0 + '@opentelemetry/context-async-hooks@1.28.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1354,6 +2598,114 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.4 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.4 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.4 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1363,12 +2715,64 @@ snapshots: '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/instrumentation-http@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + import-in-the-middle: 3.0.2 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.4 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + protobufjs: 8.0.1 + '@opentelemetry/otlp-transformer@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1385,11 +2789,21 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger@1.28.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources@1.27.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1402,6 +2816,26 @@ snapshots: '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sdk-logs@0.54.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1415,6 +2849,43 @@ snapshots: '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-node@0.217.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/configuration': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1429,6 +2900,20 @@ snapshots: '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.27.0 + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sdk-trace-node@1.28.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -1439,10 +2924,19 @@ snapshots: '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) semver: 7.8.0 + '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions@1.27.0': {} '@opentelemetry/semantic-conventions@1.28.0': {} + '@opentelemetry/semantic-conventions@1.41.1': {} + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -1457,12 +2951,42 @@ snapshots: '@protobufjs/float@1.0.2': {} + '@protobufjs/inquire@1.1.2': {} + '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} '@protobufjs/utf8@1.1.1': {} + '@sandbox-agent/cli-darwin-arm64@0.4.2': + optional: true + + '@sandbox-agent/cli-darwin-x64@0.4.2': + optional: true + + '@sandbox-agent/cli-linux-arm64@0.4.2': + optional: true + + '@sandbox-agent/cli-linux-x64@0.4.2': + optional: true + + '@sandbox-agent/cli-shared@0.4.2': {} + + '@sandbox-agent/cli-win32-x64@0.4.2': + optional: true + + '@sandbox-agent/cli@0.4.2': + dependencies: + '@sandbox-agent/cli-shared': 0.4.2 + optionalDependencies: + '@sandbox-agent/cli-darwin-arm64': 0.4.2 + '@sandbox-agent/cli-darwin-x64': 0.4.2 + '@sandbox-agent/cli-linux-arm64': 0.4.2 + '@sandbox-agent/cli-linux-x64': 0.4.2 + '@sandbox-agent/cli-win32-x64': 0.4.2 + optional: true + '@silvia-odwyer/photon-node@0.3.4': {} '@smithy/core@3.24.7': @@ -1525,10 +3049,52 @@ snapshots: '@types/retry@0.12.0': {} + '@zed-industries/claude-agent-acp@0.23.1': + dependencies: + '@agentclientprotocol/sdk': 0.17.0(zod@4.4.3) + '@anthropic-ai/claude-agent-sdk': 0.2.83(zod@4.4.3) + zod: 4.4.3 + + acorn-import-attributes@1.9.5(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + acp-http-client@0.4.2(zod@4.4.3): + dependencies: + '@agentclientprotocol/sdk': 0.16.1(zod@4.4.3) + transitivePeerDependencies: + - zod + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + agent-base@7.1.4: {} + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + anynum@1.0.0: {} + asynckit@0.4.0: {} + + axios@1.18.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + balanced-match@4.0.4: {} base64-js@1.5.1: {} @@ -1541,10 +3107,48 @@ snapshots: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + buffer-equal-constant-time@1.0.1: {} + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + chalk@5.6.2: {} + chownr@3.0.0: {} + + cjs-module-lexer@2.2.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1557,12 +3161,39 @@ snapshots: dependencies: ms: 2.1.3 + delayed-stream@1.0.0: {} + diff@8.0.4: {} + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 + emoji-regex@8.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + esbuild@0.23.1: optionalDependencies: '@esbuild/aix-ppc64': 0.23.1 @@ -1590,8 +3221,24 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + escalade@3.2.0: {} + + events@3.3.0: {} + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + extend@3.0.2: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-xml-builder@1.2.0: dependencies: path-expression-matcher: 1.5.0 @@ -1604,18 +3251,40 @@ snapshots: path-expression-matcher: 1.5.0 strnum: 2.4.0 + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + follow-redirects@1.16.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 + forwarded-parse@2.1.2: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gaxios@7.1.5: dependencies: extend: 3.0.2 @@ -1632,12 +3301,36 @@ snapshots: transitivePeerDependencies: - supports-color + get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -1657,10 +3350,26 @@ snapshots: google-logging-utils@1.1.3: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + highlight.js@10.7.3: {} + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + hosted-git-info@9.0.3: dependencies: lru-cache: 11.5.1 @@ -1672,6 +3381,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -1679,10 +3395,35 @@ snapshots: transitivePeerDependencies: - supports-color + ieee754@1.2.1: {} + ignore@7.0.5: {} + import-in-the-middle@3.0.2: + dependencies: + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + + inherits@2.0.4: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + isexe@2.0.0: {} + isomorphic-ws@5.0.0(ws@8.21.0): + dependencies: + ws: 8.21.0 + jiti@2.7.0: {} json-bigint@1.0.0: @@ -1705,18 +3446,41 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 + lodash.camelcase@4.3.0: {} + long@5.3.2: {} lru-cache@11.5.1: {} marked@15.0.12: {} + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 minipass@7.1.3: {} + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + module-details-from-path@1.0.4: {} + ms@2.1.3: {} node-domexception@1.0.0: {} @@ -1737,6 +3501,8 @@ snapshots: '@types/retry': 0.12.0 retry: 0.13.1 + parse-passwd@1.0.0: {} + partial-json@0.1.7: {} path-expression-matcher@1.5.0: {} @@ -1748,6 +3514,15 @@ snapshots: lru-cache: 11.5.1 minipass: 7.1.3 + pathe@2.0.3: {} + + pi-acp@0.0.29: + dependencies: + '@agentclientprotocol/sdk': 0.26.0(zod@3.25.76) + zod: 3.25.76 + + picomatch@2.3.2: {} + proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -1768,14 +3543,64 @@ snapshots: '@types/node': 22.10.2 long: 5.3.2 + protobufjs@8.0.1: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 22.10.2 + long: 5.3.2 + + proxy-from-env@2.1.0: {} + + queue-microtask@1.2.3: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + require-directory@2.1.1: {} + + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + resolve-pkg-maps@1.0.0: {} retry@0.12.0: {} retry@0.13.1: {} + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-buffer@5.2.1: {} + sandbox-agent@0.4.2(@daytonaio/sdk@0.187.0(ws@8.21.0))(zod@4.4.3): + dependencies: + '@sandbox-agent/cli-shared': 0.4.2 + acp-http-client: 0.4.2(zod@4.4.3) + optionalDependencies: + '@daytonaio/sdk': 0.187.0(ws@8.21.0) + '@sandbox-agent/cli': 0.4.2 + transitivePeerDependencies: + - zod + semver@7.8.0: {} shebang-command@2.0.0: @@ -1784,12 +3609,47 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.4: {} + signal-exit@3.0.7: {} + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strnum@2.4.0: dependencies: anynum: 1.0.0 + tar@7.5.16: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + ts-algebra@2.0.0: {} tslib@2.8.1: {} @@ -1807,20 +3667,46 @@ snapshots: undici@8.3.0: {} + util-deprecate@1.0.2: {} + web-streams-polyfill@3.3.3: {} which@2.0.2: dependencies: isexe: 2.0.0 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + ws@8.21.0: {} xml-naming@0.1.0: {} + y18n@5.0.8: {} + + yallist@5.0.0: {} + yaml@2.9.0: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: zod: 4.4.3 + zod@3.25.76: {} + zod@4.4.3: {} diff --git a/services/agent/scripts/build-extension.mjs b/services/agent/scripts/build-extension.mjs new file mode 100644 index 0000000000..229d805040 --- /dev/null +++ b/services/agent/scripts/build-extension.mjs @@ -0,0 +1,30 @@ +/** + * Bundle the Agenta Pi extension into one self-contained file so its OpenTelemetry deps + * resolve wherever Pi loads it (host, docker sidecar, Daytona snapshot). Pi only accepts + * `.ts`/`.js` extension files, so we emit `.js` (ESM) with a default export. + * + * Run: pnpm run build:extension -> dist/extensions/agenta.js + */ +import { build } from "esbuild"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); + +await build({ + entryPoints: [join(root, "src/piExtension.ts")], + outfile: join(root, "dist/extensions/agenta.js"), + bundle: true, + platform: "node", + format: "esm", + target: "node20", + // Pi provides the ExtensionAPI at load time; never bundle the harness SDK. + external: ["@earendil-works/pi-coding-agent"], + banner: { + // protobufjs and some deps expect CommonJS globals under ESM; shim them. + js: "import{createRequire as __cr}from'node:module';const require=__cr(import.meta.url);", + }, + logLevel: "info", +}); + +process.stderr.write("[build-extension] wrote dist/extensions/agenta.js\n"); diff --git a/services/agent/src/agenta-otel.ts b/services/agent/src/agenta-otel.ts index 3d838329a1..35bdeffd6a 100644 --- a/services/agent/src/agenta-otel.ts +++ b/services/agent/src/agenta-otel.ts @@ -402,6 +402,8 @@ export interface AgentaOtel { config: RunConfig; /** Flush this run's trace to Agenta. Await before the process/response ends. */ flush: () => Promise; + /** Run totals (tokens + cost) summed across turns, for roll-up onto the parent. */ + usage: () => { input: number; output: number; total: number; cost: number }; } /** @@ -434,6 +436,22 @@ export function createAgentaOtel( let llmSpan: Span | undefined; let lastContextMessages: any[] | undefined; const toolSpans = new Map(); + // Run totals, summed across every assistant turn. Stamped on the agent span and + // returned so the caller can roll them up onto the workflow span in its own process + // (the agent and workflow spans are exported in separate OTLP batches, so Agenta's + // per-batch cumulative roll-up cannot bridge them on its own). + const runUsage = { input: 0, output: 0, total: 0, cost: 0 }; + + function accumulateUsage(msg: any): void { + const u = msg?.usage; + if (!u) return; + const input = u.input ?? 0; + const output = u.output ?? 0; + runUsage.input += input; + runUsage.output += output; + runUsage.total += u.totalTokens ?? input + output; + if (u.cost?.total != null) runUsage.cost += u.cost.total; + } const register = (pi: ExtensionAPI): void => { pi.on("before_agent_start", async (event: any) => { @@ -494,6 +512,7 @@ export function createAgentaOtel( const msg = event?.message; if (!msg || msg.role !== "assistant" || !llmSpan) return; applyAssistant(llmSpan, msg, config.captureContent); + accumulateUsage(msg); llmSpan.end(); llmSpan = undefined; }); @@ -524,6 +543,7 @@ export function createAgentaOtel( // close it from the turn's assistant message. if (llmSpan && event?.message) { applyAssistant(llmSpan, event.message, config.captureContent); + accumulateUsage(event.message); llmSpan.end(); llmSpan = undefined; } @@ -536,6 +556,16 @@ export function createAgentaOtel( pi.on("agent_end", async (event: any) => { if (!agentSpan) return; setOutput(agentSpan, lastAssistantText(event?.messages), config.captureContent); + // Stamp the run total on the agent span so it shows the agent's tokens/cost even + // though Agenta cannot roll the per-turn LLM spans up across batches. + if (runUsage.total > 0) { + agentSpan.setAttribute("gen_ai.usage.input_tokens", runUsage.input); + agentSpan.setAttribute("gen_ai.usage.output_tokens", runUsage.output); + agentSpan.setAttribute("gen_ai.usage.prompt_tokens", runUsage.input); + agentSpan.setAttribute("gen_ai.usage.completion_tokens", runUsage.output); + agentSpan.setAttribute("gen_ai.usage.total_tokens", runUsage.total); + if (runUsage.cost > 0) agentSpan.setAttribute("gen_ai.usage.cost", runUsage.cost); + } agentSpan.end(); agentSpan = undefined; agentCtx = undefined; @@ -547,5 +577,277 @@ export function createAgentaOtel( register, config, flush: () => flushTrace(config.traceId), + usage: () => ({ ...runUsage }), + }; +} + +// --------------------------------------------------------------------------- +// Rivet / ACP tracer (one per run; state is closure-scoped) +// --------------------------------------------------------------------------- +// +// The Pi extension above hooks Pi's in-process `pi.on(...)` events. Under rivet the +// harness runs as a separate process and we never see those events; instead the rivet +// SDK surfaces the run as ACP `session/update` notifications (agent_message_chunk, +// tool_call, tool_call_update, usage_update). This tracer builds the SAME span tree +// from that event stream, so tracing is uniform across every harness rivet drives +// (Pi, Claude Code, ...) and always nests under the caller's `/invoke` span. +// +// Span tree (per prompt turn): +// invoke_agent (AGENT) +// turn 0 (CHAIN) +// chat (LLM) — model interaction; usage where the harness reports it +// execute_tool (TOOL) — one per ACP tool_call + +/** Text of an ACP ContentBlock (the shape carried by message/thought chunks). */ +function acpBlockText(block: any): string { + if (!block) return ""; + if (typeof block === "string") return block; + if (block.type === "text" && typeof block.text === "string") return block.text; + return ""; +} + +/** Text of an ACP tool_call `content` array (ToolCallContent[]). */ +function acpToolContentText(content: any): string { + if (!content) return ""; + if (typeof content === "string") return content; + if (Array.isArray(content)) { + return content + .map((c: any) => acpBlockText(c?.content ?? c)) + .filter(Boolean) + .join(""); + } + return ""; +} + +/** + * Strip the pi-acp startup banner that some setups emit as the first agent message + * chunk (a "pi vX.Y.Z" / "## Context" / file list / "New version available" prelude, + * surfaced ahead of the real answer). Removes only a leading run of those marker lines + * so a genuine answer is never touched. + */ +function stripStartupBanner(text: string): string { + const lines = text.split("\n"); + const isBanner = (line: string) => + /^pi v\d+\.\d+\.\d+/.test(line) || + /^## Context\b/.test(line) || + /^-\s+\/.*AGENTS\.md\s*$/.test(line) || + /^New version available:/.test(line) || + /^Run: `npm/.test(line) || + line.trim() === "---" || + line.trim() === ""; + let i = 0; + let sawBanner = false; + while (i < lines.length && isBanner(lines[i])) { + if (lines[i].trim() !== "") sawBanner = true; + i++; + } + return sawBanner ? lines.slice(i).join("\n").trim() : text; +} + +/** Split a resolved model id ("openai-codex/gpt-5.5") into provider + id. */ +function splitModel(model?: string): { provider?: string; id?: string } { + if (!model) return {}; + const slash = model.indexOf("/"); + if (slash === -1) return { id: model }; + return { provider: model.slice(0, slash), id: model.slice(slash + 1) }; +} + +export interface RivetOtelInit extends Partial { + captureContent?: boolean; + /** Harness id ("pi" / "claude"); becomes gen_ai.agent.name. */ + harness?: string; + /** Resolved model id ("openai-codex/gpt-5.5"); set on the LLM span. */ + model?: string; + /** + * Emit the span tree from the ACP event stream. Default true. Set false when the + * harness instruments itself (e.g. Pi via the agenta extension propagates the trace + * context and emits its own real turn/chat/tool spans) — then this only accumulates + * the reply text and builds no spans, so the two do not double up. + */ + emitSpans?: boolean; +} + +export interface RivetOtel { + /** Start the invoke_agent (AGENT) span as a child of the caller's traceparent. */ + start(input: { prompt?: string; messages?: any[]; sessionId?: string }): void; + /** Feed one ACP `session/update` payload (the `update` object). */ + handleUpdate(update: any): void; + /** End all open spans. Returns the accumulated assistant text. */ + finish(): string; + /** Flush this run's trace to Agenta (invoke_agent has a remote parent). */ + flush(): Promise; + /** Trace id of the run (the caller's trace when a traceparent was passed). */ + traceId(): string | undefined; + /** Accumulated assistant output text so far. */ + output(): string; +} + +/** + * Build an ACP-event-driven tracer scoped to a single rivet run. Call `start` once, + * `handleUpdate` for every ACP session update, then `finish` + `await flush`. + */ +export function createRivetOtel(init: RivetOtelInit): RivetOtel { + ensureProvider(); + + const capture = init.captureContent !== false; + const emitSpans = init.emitSpans !== false; + const endpoint = init.endpoint ?? defaultTarget().endpoint; + const authorization = init.authorization ?? defaultTarget().authorization; + const { provider, id: modelId } = splitModel(init.model); + const tracer = trace.getTracer("agenta-rivet-otel", "0.1.0"); + + let agentSpan: Span | undefined; + let agentCtx: Context | undefined; + let turnSpan: Span | undefined; + let turnCtx: Context | undefined; + let llmSpan: Span | undefined; + let runTraceId: string | undefined; + let accumulated = ""; + let usage: { cost?: number; total?: number } | undefined; + const toolSpans = new Map(); + + function start(input: { prompt?: string; messages?: any[]; sessionId?: string }): void { + // Span-less mode (harness self-instruments): only track the trace id so the run can + // report it; the harness emits the spans under the propagated parent. + if (!emitSpans) { + const m = /^00-([0-9a-f]{32})-/.exec(init.traceparent ?? ""); + runTraceId = m ? m[1] : undefined; + return; + } + const parent = parentContext(init.traceparent); + agentSpan = tracer.startSpan("invoke_agent", undefined, parent); + agentSpan.setAttribute("openinference.span.kind", "AGENT"); + agentSpan.setAttribute("gen_ai.operation.name", "invoke_agent"); + agentSpan.setAttribute("gen_ai.agent.name", init.harness ?? "agent"); + const sessionId = input.sessionId ?? init.sessionId; + if (sessionId) { + agentSpan.setAttribute("session.id", sessionId); + agentSpan.setAttribute("gen_ai.conversation.id", sessionId); + } + setInputs(agentSpan, { prompt: input.prompt ?? "" }, capture); + + runTraceId = agentSpan.spanContext().traceId; + traceTargets.set(runTraceId, { endpoint, authorization }); + agentCtx = trace.setSpan(parent ?? context.active(), agentSpan); + + turnSpan = tracer.startSpan("turn 0", undefined, agentCtx); + turnSpan.setAttribute("openinference.span.kind", "CHAIN"); + turnSpan.setAttribute("pi.turn.index", 0); + turnCtx = trace.setSpan(agentCtx, turnSpan); + + llmSpan = tracer.startSpan(modelId ? `chat ${modelId}` : "chat", undefined, turnCtx); + llmSpan.setAttribute("openinference.span.kind", "LLM"); + llmSpan.setAttribute("gen_ai.operation.name", "chat"); + if (provider) llmSpan.setAttribute("gen_ai.system", provider); + if (modelId) llmSpan.setAttribute("gen_ai.request.model", modelId); + const inputMessages = + input.messages && input.messages.length + ? input.messages + : [{ role: "user", content: input.prompt ?? "" }]; + emitMessages(llmSpan, "llm.input_messages", inputMessages, capture); + } + + function handleUpdate(update: any): void { + const kind = update?.sessionUpdate; + if (!kind) return; + + if (kind === "agent_message_chunk") { + const t = acpBlockText(update.content); + if (!t) return; + // Pi streams pure deltas; Claude streams deltas plus a cumulative snapshot. + // Replace when a chunk is a superset of what we have, append otherwise. + if (t.startsWith(accumulated)) accumulated = t; + else accumulated += t; + return; + } + + if (!emitSpans) return; // output accumulated above; spans come from the harness + + if (kind === "tool_call") { + const id = update.toolCallId; + if (!id || !turnCtx) return; + const name = update.title || update.kind || "tool"; + const span = tracer.startSpan(`execute_tool ${name}`, undefined, turnCtx); + span.setAttribute("openinference.span.kind", "TOOL"); + span.setAttribute("gen_ai.operation.name", "execute_tool"); + span.setAttribute("gen_ai.tool.name", String(name)); + span.setAttribute("gen_ai.tool.call.id", String(id)); + if (update.rawInput != null) + setInputs(span, update.rawInput as Record, capture); + toolSpans.set(id, { span, name: String(name) }); + // A tool_call can arrive already completed (status set up front). + maybeCloseTool(id, update); + return; + } + + if (kind === "tool_call_update") { + maybeCloseTool(update.toolCallId, update); + return; + } + + if (kind === "usage_update") { + const cost = update.cost?.amount; + const total = update.used; + usage = { + cost: typeof cost === "number" ? cost : usage?.cost, + total: typeof total === "number" ? total : usage?.total, + }; + } + } + + /** Close a tool span when the update marks it completed or failed. */ + function maybeCloseTool(id: string | undefined, update: any): void { + if (!id) return; + const entry = toolSpans.get(id); + if (!entry) return; + const status = update?.status; + if (status !== "completed" && status !== "failed") return; + const out = acpToolContentText(update.content) || acpToolContentText(update.rawOutput); + setOutput(entry.span, out, capture); + if (status === "failed") entry.span.setStatus({ code: SpanStatusCode.ERROR }); + entry.span.end(); + toolSpans.delete(id); + } + + function finish(): string { + const text = stripStartupBanner(accumulated.trim()); + if (!emitSpans) return text; + if (llmSpan) { + emitMessages( + llmSpan, + "llm.output_messages", + [{ role: "assistant", content: text }], + capture, + ); + if (usage?.total != null) { + llmSpan.setAttribute("gen_ai.usage.total_tokens", usage.total); + } + if (usage?.cost != null) llmSpan.setAttribute("gen_ai.usage.cost", usage.cost); + llmSpan.end(); + llmSpan = undefined; + } + for (const { span } of toolSpans.values()) span.end(); + toolSpans.clear(); + if (turnSpan) { + turnSpan.end(); + turnSpan = undefined; + } + if (agentSpan) { + setOutput(agentSpan, text, capture); + agentSpan.end(); + agentSpan = undefined; + } + agentCtx = undefined; + turnCtx = undefined; + return text; + } + + return { + start, + handleUpdate, + finish, + flush: () => flushTrace(runTraceId), + traceId: () => runTraceId, + output: () => accumulated, }; } diff --git a/services/agent/src/cli.ts b/services/agent/src/cli.ts index ed8b99c3ae..3eacd78abc 100644 --- a/services/agent/src/cli.ts +++ b/services/agent/src/cli.ts @@ -7,6 +7,14 @@ * long-lived RPC adapter can replace it later behind the same Python-side port. */ import { runPi, type AgentRunRequest, type AgentRunResult } from "./runPi.ts"; +import { runRivet } from "./runRivet.ts"; + +// `rivet` drives the harness over ACP via a rivet daemon (WP-8); default = legacy Pi. +const BACKEND = (process.env.AGENT_BACKEND ?? "pi").toLowerCase(); + +function runAgent(request: AgentRunRequest): Promise { + return BACKEND === "rivet" ? runRivet(request) : runPi(request); +} async function readStdin(): Promise { const chunks: Buffer[] = []; @@ -32,7 +40,7 @@ async function main(): Promise { } try { - const result = await runPi(request); + const result = await runAgent(request); emit(result); process.exit(result.ok ? 0 : 1); } catch (err) { diff --git a/services/agent/src/piExtension.ts b/services/agent/src/piExtension.ts new file mode 100644 index 0000000000..94418a137f --- /dev/null +++ b/services/agent/src/piExtension.ts @@ -0,0 +1,171 @@ +/** + * Agenta Pi extension (WP-8): tracing + tools, installed into Pi's agent dir and loaded + * by Pi when it runs under rivet (`pi --mode rpc` via pi-acp). + * + * This is how we keep WP-1/WP-2/WP-7 behavior on the rivet path: instead of a synthetic, + * coarse tracer in the runner, we propagate the caller's trace context INTO Pi and let + * Pi emit its real span tree (turn / chat / tool, with token usage) under that parent — + * and we deliver tools the Pi-native way (`registerTool`), each routing back to Agenta's + * /tools/call, rather than over MCP. Pi is highly customizable; this leans on that. + * + * Everything is read from the environment (injected at the daemon's birth), so nothing + * run-specific is written to the agent-visible filesystem: + * AGENTA_TRACEPARENT W3C traceparent of the caller's /invoke span + * AGENTA_OTLP_ENDPOINT OTLP traces URL (e.g. https://host/api/otlp/v1/traces) + * AGENTA_OTLP_AUTHORIZATION Authorization header for the OTLP export + * AGENTA_CAPTURE_CONTENT "false" to drop prompt/completion/tool I/O from spans + * AGENTA_TOOL_SPECS JSON [{ name, description, inputSchema, callRef }] + * AGENTA_TOOL_CALLBACK_ENDPOINT full /tools/call URL + * AGENTA_TOOL_CALLBACK_AUTH Authorization header for the callback + * + * Bundled self-contained (esbuild) so its OpenTelemetry deps resolve wherever Pi loads + * it (local, the docker sidecar, a Daytona snapshot). Default export is the Pi + * ExtensionFactory. + */ +import { writeFileSync } from "node:fs"; + +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +import { createAgentaOtel } from "./agenta-otel.ts"; + +interface ToolSpec { + name: string; + description?: string; + inputSchema?: Record | null; + callRef: string; +} + +const TOOL_CALL_TIMEOUT_MS = Number(process.env.AGENTA_AGENT_TOOL_CALL_TIMEOUT_MS ?? 30000); +const EMPTY_SCHEMA = { type: "object", properties: {}, additionalProperties: true }; + +function log(message: string): void { + process.stderr.write(`[agenta-pi-ext] ${message}\n`); +} + +/** One /tools/call round-trip. Returns the result text; throws on failure (Pi turns a + * thrown execute into a tool-error result, so the loop continues). */ +async function callAgentaTool( + endpoint: string, + authorization: string | undefined, + callRef: string, + toolCallId: string, + args: unknown, + signal?: AbortSignal, +): Promise { + const headers: Record = { "content-type": "application/json" }; + if (authorization) headers["authorization"] = authorization; + + const timeoutSignal = AbortSignal.timeout(TOOL_CALL_TIMEOUT_MS); + const anyOf = (AbortSignal as any).any; + const combined = + signal && typeof anyOf === "function" ? anyOf([signal, timeoutSignal]) : timeoutSignal; + + let response: Response; + try { + response = await fetch(endpoint, { + method: "POST", + headers, + body: JSON.stringify({ + data: { + id: toolCallId, + type: "function", + function: { name: callRef, arguments: args ?? {} }, + }, + }), + signal: combined, + }); + } catch (err) { + throw new Error(`tool call ${callRef} failed: ${err instanceof Error ? err.message : String(err)}`); + } + + const bodyText = await response.text(); + if (!response.ok) { + throw new Error(`tool call ${callRef} returned HTTP ${response.status}: ${bodyText.slice(0, 500)}`); + } + try { + const parsed = JSON.parse(bodyText); + const content = parsed?.call?.data?.content; + if (typeof content === "string") return content; + if (content != null) return JSON.stringify(content); + return bodyText; + } catch { + return bodyText; + } +} + +/** Register the resolved tools (from env) as Pi tools that call back to Agenta. */ +function registerTools(pi: ExtensionAPI): void { + const raw = process.env.AGENTA_TOOL_SPECS; + const endpoint = process.env.AGENTA_TOOL_CALLBACK_ENDPOINT; + if (!raw || !endpoint) return; + + let specs: ToolSpec[] = []; + try { + specs = JSON.parse(raw); + } catch (err) { + log(`bad AGENTA_TOOL_SPECS: ${(err as Error).message}`); + return; + } + const authorization = process.env.AGENTA_TOOL_CALLBACK_AUTH; + + for (const spec of specs) { + pi.registerTool({ + name: spec.name, + label: spec.name, + description: spec.description ?? spec.name, + // Pi accepts plain JSON Schema here (non-TypeBox validation path). + parameters: (spec.inputSchema as any) ?? EMPTY_SCHEMA, + async execute(toolCallId: string, params: unknown, signal?: AbortSignal) { + const text = await callAgentaTool( + endpoint, + authorization, + spec.callRef, + toolCallId, + params, + signal, + ); + return { content: [{ type: "text", text }], details: { callRef: spec.callRef } }; + }, + } as any); + } + log(`registered ${specs.length} tool(s) -> ${endpoint}`); +} + +/** The Pi ExtensionFactory: tools + (env-driven) tracing + usage writeback. */ +const factory = (pi: ExtensionAPI): void => { + // Fully inert unless Agenta wired this run (so it is safe to install globally in a + // shared Pi agent dir — a normal `pi` session with no Agenta env does nothing). + const hasTracing = !!(process.env.AGENTA_TRACEPARENT || process.env.AGENTA_OTLP_ENDPOINT); + const hasTools = !!(process.env.AGENTA_TOOL_SPECS && process.env.AGENTA_TOOL_CALLBACK_ENDPOINT); + const usageOut = process.env.AGENTA_USAGE_OUT; + if (!hasTracing && !hasTools && !usageOut) return; + + if (hasTools) registerTools(pi); + // Tracing exports the span tree (when the OTLP target is reachable, i.e. local runs). + // Usage accumulation is needed both for that export AND for the writeback the runner + // uses on Daytona (where the in-sandbox process can't reach Agenta's OTLP, so the + // runner traces from the event stream and only needs the token totals). So set up the + // otel state whenever either applies; only flush (export) when tracing is on. + if (!hasTracing && !usageOut) return; + + const otel = createAgentaOtel({ + traceparent: process.env.AGENTA_TRACEPARENT, + endpoint: process.env.AGENTA_OTLP_ENDPOINT, + authorization: process.env.AGENTA_OTLP_AUTHORIZATION, + captureContent: process.env.AGENTA_CAPTURE_CONTENT !== "false", + }); + otel.register(pi); // lifecycle handlers (spans + usage accumulation) + + pi.on("agent_end", async () => { + if (hasTracing) await otel.flush(); // invoke_agent has a remote parent → flush by id + if (usageOut) { + try { + writeFileSync(usageOut, JSON.stringify(otel.usage()), "utf-8"); + } catch (err) { + log(`usage writeback skipped: ${(err as Error).message}`); + } + } + }); +}; + +export default factory; diff --git a/services/agent/src/runPi.ts b/services/agent/src/runPi.ts index 4056d0dce7..74a7ab98ac 100644 --- a/services/agent/src/runPi.ts +++ b/services/agent/src/runPi.ts @@ -84,6 +84,15 @@ export interface ToolCallbackContext { } export interface AgentRunRequest { + /** Harness id for the rivet backend ("pi" / "claude"). Ignored by the Pi backend. */ + harness?: string; + /** Sandbox for the rivet backend ("local" / "daytona"). Ignored by the Pi backend. */ + sandbox?: string; + /** Continue a prior run by replaying its history. The rivet backend resumes by id. */ + sessionId?: string; + /** Provider API keys as env vars ({OPENAI_API_KEY,...}), resolved from the vault. + * Injected into the harness env; empty means the harness uses its own login (OAuth). */ + secrets?: Record; /** AGENTS.md text injected as the agent's instructions (in memory). */ agentsMd?: string; /** Model id ("gpt-5.5") or "provider/id" ("openai-codex/gpt-5.5"). */ @@ -109,6 +118,8 @@ export interface AgentRunResult { model?: string; /** Trace id of the run (the caller's trace when a traceparent was passed). */ traceId?: string; + /** Run token/cost totals, for roll-up onto the caller's workflow span. */ + usage?: { input: number; output: number; total: number; cost: number }; error?: string; } diff --git a/services/agent/src/runRivet.ts b/services/agent/src/runRivet.ts new file mode 100644 index 0000000000..45b5657834 --- /dev/null +++ b/services/agent/src/runRivet.ts @@ -0,0 +1,698 @@ +/** + * WP-8 rivet harness driver. + * + * Drives a coding harness (Pi, Claude Code, ...) over the Agent Client Protocol (ACP) + * through a rivet `sandbox-agent` daemon, instead of the bespoke Pi SDK calls in + * runPi.ts. It serves the same /run contract (AgentRunRequest -> AgentRunResult), so + * the Python side stays thin and the choice of harness/sandbox is config, not new code. + * + * Per invoke (cold), mirroring the shipped code-evaluator DaytonaRunner pattern: + * + * SandboxAgent.start({ sandbox: local({ env }) | daytona({ create }) }) + * -> createSession({ agent: , cwd, model }) + * -> write AGENTS.md into cwd + * -> session.prompt([{ type: "text", text }]) + * -> accumulate ACP `agent_message_chunk` text + build the trace + * -> destroySandbox() + * + * Two orthogonal axes swap independently: the sandbox (where the daemon runs) and the + * harness (which engine). The ACP boundary is daemon-to-harness; the service-to-rivet + * hop stays harness-agnostic behind the Harness port. + * + * Tracing is built here from the ACP event stream (see agenta-otel.ts createRivetOtel), + * so it is uniform across every harness and always nests under the caller's /invoke + * span. stdout is reserved for the JSON result (see cli.ts); logs go to stderr. + */ +import { randomBytes } from "node:crypto"; +import { + chmodSync, + copyFileSync, + existsSync, + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { SandboxAgent, InMemorySessionPersistDriver } from "sandbox-agent"; +import { local } from "sandbox-agent/local"; +import { daytona } from "sandbox-agent/daytona"; + +import { createRivetOtel } from "./agenta-otel.ts"; +import { buildToolMcpServers, type ResolvedToolSpec, type ToolCallbackContext } from "./toolBridge.ts"; +import type { AgentRunRequest, AgentRunResult, ChatMessage } from "./runPi.ts"; + +const require = createRequire(import.meta.url); +// services/agent/src/runRivet.ts -> services/agent +const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); +const ADAPTER_BIN_DIR = join(PKG_ROOT, "node_modules", ".bin"); + +/** Map node platform/arch to the @sandbox-agent CLI binary package. */ +const CLI_PACKAGES: Record = { + "darwin-arm64": "@sandbox-agent/cli-darwin-arm64", + "darwin-x64": "@sandbox-agent/cli-darwin-x64", + "linux-x64": "@sandbox-agent/cli-linux-x64", + "linux-arm64": "@sandbox-agent/cli-linux-arm64", + "win32-x64": "@sandbox-agent/cli-win32-x64", +}; + +function log(message: string): void { + process.stderr.write(`[rivet-wrapper] ${message}\n`); +} + +/** + * Resolve the sandbox-agent daemon binary. Prefers SANDBOX_AGENT_BIN, then the + * platform CLI package shipped with `sandbox-agent` (resolved from the SDK's own + * location, since pnpm nests it under `sandbox-agent`). Ensures it is executable + * (pnpm may skip the package's chmod postinstall). Returns undefined when not found; + * the local provider then runs its own resolution and surfaces a clear error. + */ +function resolveDaemonBinary(): string | undefined { + const fromEnv = process.env.SANDBOX_AGENT_BIN; + if (fromEnv && existsSync(fromEnv)) return ensureExecutable(fromEnv); + + const pkg = CLI_PACKAGES[`${process.platform}-${process.arch}`]; + if (!pkg) return undefined; + const bin = process.platform === "win32" ? "sandbox-agent.exe" : "sandbox-agent"; + try { + // Resolve from the sandbox-agent package context (its node_modules sees the + // sibling CLI package in the pnpm layout); package.json blocks the subpath, so + // resolve from the main entry instead. + const sdkRequire = createRequire(require.resolve("sandbox-agent")); + const pkgJson = sdkRequire.resolve(`${pkg}/package.json`); + const resolved = join(dirname(pkgJson), "bin", bin); + if (existsSync(resolved)) return ensureExecutable(resolved); + } catch { + // fall through to a store scan + } + // Fallback: scan the pnpm store for the platform binary. + try { + const store = join(PKG_ROOT, "node_modules", ".pnpm"); + for (const entry of readdirSync(store)) { + if (!entry.startsWith(`@sandbox-agent+cli-${process.platform}`)) continue; + const candidate = join(store, entry, "node_modules", pkg, "bin", bin); + if (existsSync(candidate)) return ensureExecutable(candidate); + } + } catch { + // store not present + } + return undefined; +} + +function ensureExecutable(path: string): string { + try { + chmodSync(path, 0o755); + } catch { + // read-only fs (e.g. baked snapshot already +x): ignore + } + return path; +} + +// The bundled Agenta Pi extension (tracing + tools). Built by `pnpm run build:extension` +// and into the image; installed into Pi's agent dir so Pi loads it on every run. +const EXTENSION_BUNDLE = + process.env.AGENTA_RIVET_EXTENSION_BUNDLE ?? join(PKG_ROOT, "dist", "extensions", "agenta.js"); + +/** + * Env the Agenta Pi extension reads. Propagating the trace context here is what makes Pi + * emit its real spans under the caller's `/invoke` span; the tool spec + callback make Pi + * register the resolved tools natively (no MCP). Empty keys are omitted so the extension + * stays inert when nothing applies. + */ +function buildPiExtensionEnv( + request: AgentRunRequest, + tracing: boolean, +): Record { + const env: Record = {}; + // Tracing env is omitted when the harness process can't reach Agenta's OTLP (Daytona): + // there the runner traces from the event stream instead, and the extension only does + // tools + the usage writeback. + const trace = tracing ? request.trace : undefined; + if (trace?.traceparent) env.AGENTA_TRACEPARENT = trace.traceparent; + if (trace?.endpoint) env.AGENTA_OTLP_ENDPOINT = trace.endpoint; + if (trace?.authorization) env.AGENTA_OTLP_AUTHORIZATION = trace.authorization; + if (trace && trace.captureContent === false) env.AGENTA_CAPTURE_CONTENT = "false"; + + const specs = (request.customTools as ResolvedToolSpec[]) ?? []; + if (specs.length && request.toolCallback?.endpoint) { + env.AGENTA_TOOL_SPECS = JSON.stringify(specs); + env.AGENTA_TOOL_CALLBACK_ENDPOINT = request.toolCallback.endpoint; + if (request.toolCallback.authorization) { + env.AGENTA_TOOL_CALLBACK_AUTH = request.toolCallback.authorization; + } + } + return env; +} + +/** Install the extension bundle into a local Pi agent dir's extensions/. Best-effort. */ +function installPiExtensionLocal(agentDir: string): void { + if (!existsSync(EXTENSION_BUNDLE)) { + log(`pi extension bundle missing at ${EXTENSION_BUNDLE} (run build:extension)`); + return; + } + try { + const dir = join(agentDir, "extensions"); + mkdirSync(dir, { recursive: true }); + copyFileSync(EXTENSION_BUNDLE, join(dir, "agenta.js")); + } catch (err) { + log(`pi extension install skipped: ${(err as Error).message}`); + } +} + +/** Upload the extension bundle into a Daytona sandbox's Pi extensions dir. Best-effort. */ +async function uploadPiExtensionToSandbox(sandbox: any, agentDir: string): Promise { + if (!existsSync(EXTENSION_BUNDLE)) return; + try { + const dir = `${agentDir}/extensions`; + await sandbox.mkdirFs({ path: dir }); + await sandbox.writeFsFile({ path: `${dir}/agenta.js` }, readFileSync(EXTENSION_BUNDLE, "utf-8")); + } catch (err) { + log(`pi extension upload skipped: ${(err as Error).message}`); + } +} + +/** + * The environment the daemon is born with. The local provider merges this into the + * `sandbox-agent server` subprocess, which passes it to the ACP adapter and then to + * the harness. This is also where per-invoke trace/secret injection would go for a + * warm-daemon model; under one-daemon-per-invoke the in-process tracer handles spans, + * so this only needs to make the adapters and harness resolvable + authed. + */ +function buildDaemonEnv(harness: string): Record { + const env: Record = {}; + + // Adapters (pi-acp, claude-agent-acp) and the pi CLI live in our node_modules/.bin; + // claude CLI is on the inherited PATH. Prepend ours, keep the inherited PATH. + const extra = process.env.AGENTA_RIVET_ADAPTER_PATH; + env.PATH = [ADAPTER_BIN_DIR, extra, process.env.PATH].filter(Boolean).join(":"); + + // Pi: point pi-acp at our pi bin and the agent dir that carries auth.json. + env.PI_ACP_PI_COMMAND = + process.env.AGENTA_RIVET_PI_COMMAND ?? join(ADAPTER_BIN_DIR, "pi"); + const piAgentDir = process.env.PI_CODING_AGENT_DIR; + if (piAgentDir) env.PI_CODING_AGENT_DIR = piAgentDir; + + // Keep HOME so harness logins (~/.pi/agent, ~/.claude) resolve. + if (process.env.HOME) env.HOME = process.env.HOME; + + // Harness LLM auth passed as launch env, never written into the agent filesystem. + for (const key of [ + "OPENAI_API_KEY", + "ANTHROPIC_API_KEY", + "ANTHROPIC_AUTH_TOKEN", + "CLAUDE_CODE_OAUTH_TOKEN", + "CLAUDE_CONFIG_DIR", + "GEMINI_API_KEY", + ]) { + const value = process.env[key]; + if (value) env[key] = value; + } + + return env; +} + +/** The latest user turn: explicit prompt, else last user message content. */ +function resolvePrompt(request: AgentRunRequest): string { + if (request.prompt && request.prompt.trim()) return request.prompt; + const messages = request.messages ?? []; + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === "user" && messages[i].content) return messages[i].content; + } + return ""; +} + +/** Prior turns (everything before the latest user message) for trace + history. */ +function priorMessages(request: AgentRunRequest): ChatMessage[] { + const messages = request.messages ?? []; + const latest = resolvePrompt(request); + // Drop the trailing user turn (it is the prompt we send) to avoid double-counting. + if (messages.length && messages[messages.length - 1].role === "user") { + return messages.slice(0, -1); + } + // No trailing user message (prompt came in explicitly): keep turns that aren't it. + return messages.filter((m) => !(m.role === "user" && m.content === latest)); +} + +/** + * The text sent over ACP for this turn. Each invoke is a cold sandbox, so prior turns + * are replayed as transcript context ahead of the latest user message — this is the + * "persisted message history replayed" model, with the client/playground holding the + * history. Capped by AGENTA_AGENT_HISTORY_MAX_CHARS so replay tokens stay bounded. + */ +function buildTurnText(request: AgentRunRequest): string { + const latest = resolvePrompt(request); + const history = priorMessages(request).filter((m) => m.content); + if (history.length === 0) return latest; + + const maxChars = Number(process.env.AGENTA_AGENT_HISTORY_MAX_CHARS ?? 24000); + let transcript = history.map((m) => `${m.role}: ${m.content}`).join("\n"); + if (transcript.length > maxChars) transcript = transcript.slice(-maxChars); + return ( + `Conversation so far:\n${transcript}\n\n` + + `Continue the conversation. The user now says:\n${latest}` + ); +} + +/** + * Pick the harness-specific model id for a requested name. Harnesses expose their own + * ids (Pi: "openai-codex/gpt-5.5"; Claude: its own). Match exact, then by the id after + * the provider prefix, so "gpt-5.5" resolves to "openai-codex/gpt-5.5". + */ +function pickModel(allowed: string[], wanted?: string): string | undefined { + if (!wanted) return undefined; + if (allowed.includes(wanted)) return wanted; + const suffix = (id: string) => id.slice(id.indexOf("/") + 1); + return ( + allowed.find((id) => suffix(id) === wanted) ?? + allowed.find((id) => suffix(id) === suffix(wanted)) ?? + undefined + ); +} + +/** Enumerate the harness's selectable model ids from the session config options. */ +async function allowedModels(session: any): Promise { + try { + const options = await session.getConfigOptions(); + const modelOpt = (options ?? []).find( + (o: any) => o.category === "model" || o.id === "model", + ); + const choices = modelOpt?.options ?? []; + return choices.map((c: any) => c.id).filter(Boolean); + } catch { + return []; + } +} + +/** Parse the allowed model ids out of an UnsupportedSessionValueError message. */ +function allowedFromError(err: unknown): string[] { + const match = /Allowed values:\s*(.+?)\s*$/.exec(String((err as Error)?.message ?? err)); + if (!match) return []; + return match[1] + .split(",") + .map((s) => s.trim()) + .filter(Boolean); +} + +/** + * Apply the requested model to a session, normalizing to the harness's own id. Tries the + * value as given first (already-qualified ids pass); on rejection it reads the allowed + * ids from the error (always listed there) or the session config and retries a match. + * Returns the id set, or undefined when no match exists (the harness keeps its default + * rather than failing the run). + */ +async function applyModel(session: any, wanted?: string): Promise { + if (!wanted) return undefined; + try { + await session.setModel(wanted); + return wanted; + } catch (err) { + const allowed = allowedFromError(err); + const fallbackAllowed = allowed.length ? allowed : await allowedModels(session); + const match = pickModel(fallbackAllowed, wanted); + if (match && match !== wanted) { + try { + await session.setModel(match); + return match; + } catch { + // fall through to harness default + } + } + log(`model '${wanted}' not settable (${(err as Error).message}); using harness default`); + return undefined; + } +} + +/** + * In-sandbox env for the Daytona daemon: where Pi reads its login, any provider keys, + * and the Agenta extension env (traceparent + OTLP + tool spec) so the remote Pi traces + * and runs tools exactly like local. No local-only paths (PATH/PI_ACP_PI_COMMAND) here. + */ +function daytonaEnvVars( + piExtEnv: Record, + secrets: Record, +): Record { + const env: Record = { + PI_CODING_AGENT_DIR: DAYTONA_PI_DIR, + ...piExtEnv, + // Provider API keys from the vault: the in-sandbox harness authenticates with these. + ...secrets, + }; + // Point pi-acp at the `pi` we install into the sandbox (the image lacks it). + if (DAYTONA_PI_INSTALL) { + env.PI_ACP_PI_COMMAND = `${DAYTONA_PI_INSTALL_DIR}/node_modules/.bin/pi`; + } + return env; +} + +/** + * Build the rivet sandbox provider for the requested axis. + * + * Daytona needs an image that carries both the rivet daemon and the harness CLI. Rivet's + * `-full` image ships the daemon and the ACP adapters but NOT the `pi` CLI, so we run + * from a pre-baked snapshot (`AGENTA_RIVET_DAYTONA_SNAPSHOT`, default `agenta-rivet-pi`, + * built by poc/build_rivet_snapshot.py) that adds `pi`; this avoids a ~150s per-invoke + * `npm install pi`. `AGENTA_RIVET_DAYTONA_IMAGE` overrides with a plain image instead. The + * code-evaluator DAYTONA_SNAPSHOT is intentionally NOT reused (it has no daemon). The + * provider key comes from the vault env; Pi's OAuth login is only uploaded when no key. + */ +function buildSandboxProvider( + sandboxId: string, + env: Record, + binaryPath: string | undefined, + piExtEnv: Record, + secrets: Record, +) { + if (sandboxId === "daytona") { + const snapshot = process.env.AGENTA_RIVET_DAYTONA_SNAPSHOT; + const image = process.env.AGENTA_RIVET_DAYTONA_IMAGE; + const target = process.env.DAYTONA_TARGET; + return daytona({ + ...(image ? { image } : {}), + create: { + // The rivet provider always sets a default `image`, which Daytona turns into a + // build entry that conflicts with `snapshot`. Spreading image:undefined last + // suppresses that so the snapshot is used as-is. + ...(snapshot ? { snapshot, image: undefined } : {}), + ...(target ? { target } : {}), + envVars: daytonaEnvVars(piExtEnv, secrets), + ephemeral: true, + } as any, + }); + } + // local: spawn `sandbox-agent server` on this host with the daemon env merged in. + const logMode = (process.env.AGENTA_RIVET_DAEMON_LOG ?? "silent") as any; + return local({ env, binaryPath, log: logMode }); +} + +/** In-sandbox Pi agent dir on the rivet `-full` image (daemon runs as user `sandbox`). */ +const DAYTONA_PI_DIR = process.env.AGENTA_RIVET_DAYTONA_PI_DIR ?? "/home/sandbox/.pi/agent"; +// The rivet `-full` image ships the pi-acp adapter but NOT the `pi` CLI, so by default we +// install it into the sandbox at session time and point pi-acp at it. A snapshot that +// pre-installs `pi` should set AGENTA_RIVET_DAYTONA_INSTALL_PI=false (faster, no per-run +// npm install). Version mirrors the wrapper's pinned Pi. +const DAYTONA_PI_INSTALL_DIR = "/home/sandbox/.agenta-pi"; +const DAYTONA_PI_INSTALL = process.env.AGENTA_RIVET_DAYTONA_INSTALL_PI !== "false"; +const DAYTONA_PI_VERSION = process.env.AGENTA_RIVET_PI_VERSION ?? "0.79.4"; + +/** Install the `pi` CLI into a Daytona sandbox (the rivet image lacks it). Best-effort. */ +async function installPiInSandbox(sandbox: any): Promise { + try { + await sandbox.mkdirFs({ path: DAYTONA_PI_INSTALL_DIR }); + const res = await sandbox.runProcess({ + command: "npm", + args: [ + "install", + "--no-fund", + "--no-audit", + `@earendil-works/pi-coding-agent@${DAYTONA_PI_VERSION}`, + ], + cwd: DAYTONA_PI_INSTALL_DIR, + timeoutMs: 180_000, + }); + if (res?.exitCode !== 0) { + log(`pi install in sandbox exit=${res?.exitCode}: ${String(res?.stderr).slice(-400)}`); + } + } catch (err) { + log(`pi install in sandbox skipped: ${(err as Error).message}`); + } +} + +/** + * Upload the local Pi login into a Daytona sandbox so the remote Pi authenticates with + * the dev's ChatGPT/Codex OAuth (it auto-refreshes from the token in auth.json). Must + * `mkdirFs` the parent first (a fresh sandbox lacks it) and pass a string body — a + * missing dir or a stream body is what produced the earlier "Stream Error". Best-effort: + * with no local login the remote run falls back to any provider key in the sandbox env. + */ +async function uploadPiAuthToSandbox(sandbox: any): Promise { + const localDir = process.env.PI_CODING_AGENT_DIR || join(process.env.HOME ?? "", ".pi/agent"); + const authPath = join(localDir, "auth.json"); + if (!existsSync(authPath)) return; + try { + await sandbox.mkdirFs({ path: DAYTONA_PI_DIR }); + await sandbox.writeFsFile({ path: `${DAYTONA_PI_DIR}/auth.json` }, readFileSync(authPath, "utf-8")); + const settingsPath = join(localDir, "settings.json"); + if (existsSync(settingsPath)) { + await sandbox.writeFsFile( + { path: `${DAYTONA_PI_DIR}/settings.json` }, + readFileSync(settingsPath, "utf-8"), + ); + } + } catch (err) { + log(`pi auth upload skipped: ${(err as Error).message}`); + } +} + +/** + * A `fetch` that persists cookies per host. Daytona's preview proxy authenticates with a + * `daytona-sandbox-auth-*` cookie set on the first response; Node's fetch keeps no cookie + * jar, so without this the proxy rejects later ACP requests with "Authentication + * required" / 502. The rivet SDK accepts a custom fetch, so we hand it this one. + */ +function createCookieFetch(): typeof fetch { + const jar = new Map>(); // host -> (name -> "name=value") + return async (input: any, init?: any) => { + const url = new URL(typeof input === "string" ? input : input.url); + const host = url.host; + const cookies = jar.get(host); + const headers = new Headers(init?.headers ?? (typeof input !== "string" ? input.headers : undefined)); + if (cookies && cookies.size > 0) { + const existing = headers.get("cookie"); + const merged = [...cookies.values()]; + if (existing) merged.unshift(existing); + headers.set("cookie", merged.join("; ")); + } + const response = await fetch(input, { ...init, headers }); + const setCookies = + typeof (response.headers as any).getSetCookie === "function" + ? (response.headers as any).getSetCookie() + : (response.headers.get("set-cookie") ? [response.headers.get("set-cookie")] : []); + if (setCookies.length) { + const store = jar.get(host) ?? new Map(); + for (const sc of setCookies) { + const pair = String(sc).split(";")[0]; + const name = pair.split("=")[0]; + if (name) store.set(name, pair); + } + jar.set(host, store); + } + return response; + }; +} + +/** Read the run-total usage Pi wrote on agent_end (local fs or the sandbox FS API). */ +async function readRunUsage( + sandbox: any, + path: string | undefined, + isDaytona: boolean, +): Promise { + if (!path) return undefined; + try { + let raw: string; + if (isDaytona) { + const bytes = await sandbox.readFsFile({ path }); + raw = typeof bytes === "string" ? bytes : new TextDecoder().decode(bytes); + } else { + if (!existsSync(path)) return undefined; + raw = readFileSync(path, "utf-8"); + } + const u = JSON.parse(raw); + return u && u.total > 0 ? u : undefined; + } catch { + return undefined; + } +} + +/** + * Turn a harness/SDK error into one clear line for the caller (the playground shows it + * verbatim), instead of dumping a full ACP/JS stack. Recognizes the common harness auth + * failures so the user sees what to fix. + */ +function conciseError(err: unknown, harness: string): string { + const raw = err instanceof Error ? err.message : String(err); + const msg = raw.split("\n")[0].trim(); + const keyHint = + harness === "claude" ? "the project's Anthropic key" : "the project's OpenAI key"; + if (/credit balance is too low/i.test(raw)) { + return `${harness}: the model provider account has insufficient credit (check ${keyHint}).`; + } + if (/authentication required|invalid api key|401|unauthorized/i.test(raw)) { + return `${harness}: model authentication failed — add ${keyHint} to the project vault, or log in (OAuth).`; + } + return msg || "agent run failed"; +} + +export async function runRivet(request: AgentRunRequest): Promise { + const harness = request.harness || process.env.AGENTA_AGENT_HARNESS || "pi"; + const sandboxId = request.sandbox || process.env.AGENTA_AGENT_SANDBOX || "local"; + + const prompt = resolvePrompt(request); + if (!prompt) { + return { ok: false, error: "No user message to send (prompt/messages empty)." }; + } + // What we actually send over ACP: the latest turn, with prior turns replayed as + // context when this is a continued conversation. + const turnText = buildTurnText(request); + + const isPi = harness === "pi"; + const isDaytona = sandboxId === "daytona"; + + // Provider API keys resolved from the vault (OPENAI_API_KEY/ANTHROPIC_API_KEY/...). + // Present => the harness authenticates with the key; absent => it uses its own login + // (OAuth: local Codex / a mounted-or-uploaded auth.json). + const secrets = request.secrets ?? {}; + const harnessKeyVar = harness === "claude" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"; + const hasApiKey = !!secrets[harnessKeyVar]; + + const env = buildDaemonEnv(harness); + Object.assign(env, secrets); // local daemon inherits the provider keys + // Pi self-instruments locally: propagate the trace context + tools into Pi via the + // Agenta extension (real spans + native tools). On Daytona the in-sandbox process + // can't reach Agenta's OTLP, so the extension skips tracing (tools + usage only) and + // the runner traces from the ACP event stream instead — hence emitSpans on Daytona. + const piExtEnv = isPi ? buildPiExtensionEnv(request, !isDaytona) : {}; + Object.assign(env, piExtEnv); // local daemon inherits it; daytona gets it via envVars + // undefined is fine: the local provider runs its own resolution and errors clearly. + const binaryPath = resolveDaemonBinary(); + + // For local Pi, install the extension into the agent dir Pi loads from. + const localPiAgentDir = process.env.PI_CODING_AGENT_DIR; + if (isPi && !isDaytona && localPiAgentDir) installPiExtensionLocal(localPiAgentDir); + + // Session cwd holds AGENTS.md. Local: a host temp dir. Daytona: an in-sandbox path + // (the host path would not exist on the remote sandbox). + const cwd = isDaytona + ? `/home/sandbox/agenta-${randomBytes(6).toString("hex")}` + : mkdtempSync(join(tmpdir(), "agenta-rivet-")); + const agentsMd = request.agentsMd?.trim(); + + // Pi writes its run totals here on agent_end; we read them back and return them so the + // caller can roll them onto the workflow span (separate OTLP batch, see piExtension). + const usageOutPath = isPi ? `${cwd}/.agenta-usage.json` : undefined; + if (usageOutPath) { + env.AGENTA_USAGE_OUT = usageOutPath; + piExtEnv.AGENTA_USAGE_OUT = usageOutPath; + } + + log(`harness=${harness} sandbox=${sandboxId} cwd=${cwd}`); + + // Persist events in-process so a follow-up turn can resume by session id. + const persist = new InMemorySessionPersistDriver(); + const sandbox = await SandboxAgent.start({ + sandbox: buildSandboxProvider(sandboxId, env, binaryPath, piExtEnv, secrets), + persist, + // Daytona's preview proxy authenticates with a per-sandbox cookie; carry it across + // requests so ACP calls after the first don't 401. Harmless for local. + ...(isDaytona ? { fetch: createCookieFetch() } : {}), + }); + + // Pi traces itself via the extension under the propagated traceparent; for other + // harnesses we build the span tree here from the ACP event stream. Created below, once + // the model is resolved, so the chat span carries the harness's actual model rather + // than the requested one. Declared here so the catch can flush a partial trace. + let otel: ReturnType | undefined; + + try { + // On Daytona, push the harness login, the extension, and AGENTS.md into the remote + // sandbox via the filesystem API (nothing secret is baked into the image). Locally + // these use the host filesystem and the harness's own login (PI_CODING_AGENT_DIR). + if (isDaytona) { + if (isPi) { + // With a provider API key the harness authenticates via env; only fall back to + // uploading the Codex/OAuth login when no key is available. + if (!hasApiKey) await uploadPiAuthToSandbox(sandbox); + await uploadPiExtensionToSandbox(sandbox, DAYTONA_PI_DIR); + if (DAYTONA_PI_INSTALL) await installPiInSandbox(sandbox); + } + await sandbox.mkdirFs({ path: cwd }).catch(() => {}); + if (agentsMd) await sandbox.writeFsFile({ path: `${cwd}/AGENTS.md` }, agentsMd); + } else if (agentsMd) { + writeFileSync(join(cwd, "AGENTS.md"), agentsMd, "utf-8"); + } + + // Pi gets tools via the extension (above); other harnesses via MCP. + const mcpServers = isPi + ? [] + : buildToolMcpServers( + (request.customTools as ResolvedToolSpec[]) ?? [], + request.toolCallback as ToolCallbackContext | undefined, + ); + + const session = await sandbox.createSession({ + agent: harness, + cwd, + sessionInit: { cwd, mcpServers }, + }); + + // Resolve the model first: when the harness rejects the requested id and keeps its + // own default (e.g. Claude ignores "gpt-5.5"), `model` is undefined and the chat span + // is labelled "chat" instead of falsely claiming the requested model. + const model = await applyModel(session, request.model); + + const run = createRivetOtel({ + harness, + model, + traceparent: request.trace?.traceparent, + baggage: request.trace?.baggage, + endpoint: request.trace?.endpoint, + authorization: request.trace?.authorization, + captureContent: request.trace?.captureContent, + emitSpans: !isPi || isDaytona, + }); + otel = run; + + run.start({ + prompt, + sessionId: session.id, + messages: [...priorMessages(request), { role: "user", content: prompt }], + }); + + session.onEvent((event: any) => { + const payload = event?.payload; + const update = payload?.params?.update ?? payload?.update; + if (update) run.handleUpdate(update); + }); + + // Auto-approve permission requests so a permission-gating harness (e.g. Claude + // Code) does not block on tool use. Tools are backend-resolved and trusted; the + // run is headless so there is no human to prompt. Set AGENTA_RIVET_DENY_PERMISSIONS + // to reject instead. + const denyPermissions = process.env.AGENTA_RIVET_DENY_PERMISSIONS === "true"; + session.onPermissionRequest((req: any) => { + const replies: string[] = req?.availableReplies ?? []; + const reply = denyPermissions + ? "reject" + : replies.find((r) => r === "always") ?? replies.find((r) => r === "once") ?? "once"; + if (req?.id) session.respondPermission(req.id, reply as any).catch(() => {}); + }); + + const result = await session.prompt([{ type: "text", text: turnText }]); + log(`prompt stopReason=${(result as any)?.stopReason}`); + + const output = run.finish(); + await run.flush(); + + return { + ok: true, + output, + sessionId: session.id, + model: model ?? request.model, + traceId: run.traceId(), + usage: await readRunUsage(sandbox, usageOutPath, isDaytona), + }; + } catch (err) { + otel?.finish(); + await otel?.flush().catch(() => {}); + return { ok: false, error: conciseError(err, harness) }; + } finally { + await sandbox.destroySandbox().catch(() => {}); + await sandbox.dispose().catch(() => {}); + rmSync(cwd, { recursive: true, force: true }); + } +} diff --git a/services/agent/src/server.ts b/services/agent/src/server.ts index 968ff6be4e..2eee90d1fc 100644 --- a/services/agent/src/server.ts +++ b/services/agent/src/server.ts @@ -12,10 +12,23 @@ */ import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; -import { runPi, type AgentRunRequest } from "./runPi.ts"; +import { runPi, type AgentRunRequest, type AgentRunResult } from "./runPi.ts"; +import { runRivet } from "./runRivet.ts"; const PORT = Number(process.env.PORT ?? 8765); +// Select the harness driver. `rivet` drives the harness over ACP via a rivet daemon +// (WP-8); `pi` is the legacy in-process Pi path (WP-2). `auto` (default) routes by the +// request: a rivet envelope carries `harness`/`sandbox`, so one sidecar serves both and +// nothing regresses. +const BACKEND = (process.env.AGENT_BACKEND ?? "auto").toLowerCase(); + +function runAgent(request: AgentRunRequest): Promise { + if (BACKEND === "rivet") return runRivet(request); + if (BACKEND === "pi") return runPi(request); + return request.harness || request.sandbox ? runRivet(request) : runPi(request); +} + function send(res: ServerResponse, status: number, body: unknown): void { const payload = JSON.stringify(body); res.writeHead(status, { @@ -48,7 +61,7 @@ const server = createServer(async (req, res) => { return send(res, 400, { ok: false, error: `Invalid JSON: ${String(err)}` }); } - const result = await runPi(request); + const result = await runAgent(request); return send(res, result.ok ? 200 : 500, result); } @@ -59,6 +72,20 @@ const server = createServer(async (req, res) => { } }); +// The rivet SDK can reject a background promise (e.g. an adapter install or the Daytona +// preview SSE failing) outside any awaited path. Node's default turns that into an +// uncaught exception that kills the whole process — taking every in-flight request with +// it (the caller sees "Server disconnected"). Log and keep serving instead; the failing +// run still returns its own error to its caller. +process.on("unhandledRejection", (reason) => { + process.stderr.write( + `[pi-wrapper] unhandledRejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}\n`, + ); +}); +process.on("uncaughtException", (err) => { + process.stderr.write(`[pi-wrapper] uncaughtException: ${err.stack ?? err.message}\n`); +}); + server.listen(PORT, () => { process.stderr.write(`[pi-wrapper] http server listening on :${PORT}\n`); }); diff --git a/services/agent/src/toolBridge.ts b/services/agent/src/toolBridge.ts new file mode 100644 index 0000000000..6cf27b10cb --- /dev/null +++ b/services/agent/src/toolBridge.ts @@ -0,0 +1,76 @@ +/** + * WP-8 tool delivery over rivet/ACP. + * + * The Pi backend (runPi.ts) injected resolved runnable tools (WP-7) as in-process Pi + * customTools. Over ACP the harness only accepts tools through MCP, so the same + * resolved specs are exposed as an MCP server whose tool bodies POST back to Agenta's + * /tools/call (the provider key and connection auth stay server-side, exactly as in + * the Pi path). `buildToolMcpServers` returns the ACP `mcpServers` entry to attach to + * the session. + * + * Delivery: a stdio MCP bridge (toolBridgeServer.ts) launched by the daemon. The specs + * and callback are passed to it as env, so nothing tool-specific is written to the + * agent-visible filesystem. + */ +import { existsSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { ResolvedToolSpec, ToolCallbackContext } from "./runPi.ts"; + +export type { ResolvedToolSpec, ToolCallbackContext } from "./runPi.ts"; + +const HERE = dirname(fileURLToPath(import.meta.url)); +// services/agent/src/toolBridge.ts -> services/agent/node_modules/.bin/tsx +const TSX_BIN = join(HERE, "..", "node_modules", ".bin", "tsx"); +const SERVER = join(HERE, "toolBridgeServer.ts"); + +/** Resolve how to launch the bridge: an explicit override, else the local tsx bin. */ +function bridgeLauncher(): { command: string; args: string[] } { + const override = process.env.AGENTA_TOOL_BRIDGE_COMMAND; + if (override) return { command: override, args: [SERVER] }; + if (existsSync(TSX_BIN)) return { command: TSX_BIN, args: [SERVER] }; + // Fall back to npx tsx (resolves from PATH wherever the daemon runs). + return { command: "npx", args: ["-y", "tsx", SERVER] }; +} + +/** ACP McpServerStdio entry: env is a list of {name, value}. */ +interface EnvVariable { + name: string; + value: string; +} + +export interface McpServerStdio { + name: string; + command: string; + args: string[]; + env: EnvVariable[]; +} + +/** + * Build the ACP `mcpServers` list that exposes the resolved tools to the harness. + * Empty when there are no tools or no callback (the no-tools path stays untouched). + */ +export function buildToolMcpServers( + specs: ResolvedToolSpec[], + callback: ToolCallbackContext | undefined, +): McpServerStdio[] { + if (!specs || specs.length === 0) return []; + if (!callback?.endpoint) { + process.stderr.write( + `[tool-bridge] skipping ${specs.length} tool(s): missing toolCallback endpoint\n`, + ); + return []; + } + + const env: EnvVariable[] = [ + { name: "AGENTA_TOOL_SPECS", value: JSON.stringify(specs) }, + { name: "AGENTA_TOOL_CALLBACK_ENDPOINT", value: callback.endpoint }, + ]; + if (callback.authorization) { + env.push({ name: "AGENTA_TOOL_CALLBACK_AUTH", value: callback.authorization }); + } + + const { command, args } = bridgeLauncher(); + return [{ name: "agenta-tools", command, args, env }]; +} diff --git a/services/agent/src/toolBridgeServer.ts b/services/agent/src/toolBridgeServer.ts new file mode 100644 index 0000000000..7a8dd44971 --- /dev/null +++ b/services/agent/src/toolBridgeServer.ts @@ -0,0 +1,170 @@ +/** + * WP-8 tool MCP bridge (stdio server). + * + * The harness only accepts tools over MCP when driven via ACP. This is a minimal, + * dependency-free MCP stdio server that exposes the backend-resolved runnable tools + * (WP-7) and routes each tool call back through Agenta's /tools/call — so the Composio + * key and connection auth stay server-side, exactly as in the in-process Pi path. + * + * Launched by the rivet daemon as a session MCP server (see toolBridge.ts). It reads + * everything from env so nothing tool-specific is written to the agent filesystem: + * AGENTA_TOOL_SPECS JSON array of { name, description, inputSchema, callRef } + * AGENTA_TOOL_CALLBACK_ENDPOINT full /tools/call URL + * AGENTA_TOOL_CALLBACK_AUTH Authorization header value (optional) + * + * Protocol: JSON-RPC 2.0 over stdio, newline-delimited (the MCP stdio framing). Handles + * initialize, tools/list, tools/call; ignores notifications. stdout carries protocol + * messages only; logs go to stderr. + */ +interface ToolSpec { + name: string; + description?: string; + inputSchema?: Record | null; + callRef: string; +} + +const SPECS: ToolSpec[] = JSON.parse(process.env.AGENTA_TOOL_SPECS ?? "[]"); +const ENDPOINT = process.env.AGENTA_TOOL_CALLBACK_ENDPOINT ?? ""; +const AUTH = process.env.AGENTA_TOOL_CALLBACK_AUTH; +const SPEC_BY_NAME = new Map(SPECS.map((s) => [s.name, s])); +const TOOL_CALL_TIMEOUT_MS = Number(process.env.AGENTA_AGENT_TOOL_CALL_TIMEOUT_MS ?? 30000); +const DEFAULT_PROTOCOL = "2025-06-18"; + +const EMPTY_SCHEMA = { type: "object", properties: {}, additionalProperties: true }; + +function log(message: string): void { + process.stderr.write(`[tool-bridge] ${message}\n`); +} + +function send(message: unknown): void { + process.stdout.write(`${JSON.stringify(message)}\n`); +} + +/** One /tools/call round-trip. Returns the result text; throws on failure. */ +async function callAgentaTool(callRef: string, args: unknown): Promise { + const headers: Record = { "content-type": "application/json" }; + if (AUTH) headers["authorization"] = AUTH; + + let response: Response; + try { + response = await fetch(ENDPOINT, { + method: "POST", + headers, + body: JSON.stringify({ + data: { + id: `tool-${Date.now()}`, + type: "function", + function: { name: callRef, arguments: args ?? {} }, + }, + }), + signal: AbortSignal.timeout(TOOL_CALL_TIMEOUT_MS), + }); + } catch (err) { + throw new Error(`tool call ${callRef} failed: ${err instanceof Error ? err.message : String(err)}`); + } + + const bodyText = await response.text(); + if (!response.ok) { + throw new Error(`tool call ${callRef} returned HTTP ${response.status}: ${bodyText.slice(0, 500)}`); + } + // ToolCallResponse -> { call: { data: { content }, status } }; content is the result + // serialized as a string, handed to the model verbatim. + try { + const parsed = JSON.parse(bodyText); + const content = parsed?.call?.data?.content; + if (typeof content === "string") return content; + if (content != null) return JSON.stringify(content); + return bodyText; + } catch { + return bodyText; + } +} + +async function handle(message: any): Promise { + const { id, method, params } = message ?? {}; + + // Notifications (no id) need no response. + if (id === undefined || id === null) { + return undefined; + } + + if (method === "initialize") { + return { + jsonrpc: "2.0", + id, + result: { + protocolVersion: params?.protocolVersion ?? DEFAULT_PROTOCOL, + capabilities: { tools: {} }, + serverInfo: { name: "agenta-tools", version: "0.1.0" }, + }, + }; + } + + if (method === "tools/list") { + return { + jsonrpc: "2.0", + id, + result: { + tools: SPECS.map((s) => ({ + name: s.name, + description: s.description ?? s.name, + inputSchema: (s.inputSchema as Record) ?? EMPTY_SCHEMA, + })), + }, + }; + } + + if (method === "tools/call") { + const name = params?.name; + const spec = SPEC_BY_NAME.get(name); + if (!spec) { + return { jsonrpc: "2.0", id, error: { code: -32602, message: `unknown tool: ${name}` } }; + } + try { + const text = await callAgentaTool(spec.callRef, params?.arguments); + return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text }] } }; + } catch (err) { + // Surface as an MCP tool error (isError) so the model can recover, not a crash. + return { + jsonrpc: "2.0", + id, + result: { + content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], + isError: true, + }, + }; + } + } + + return { jsonrpc: "2.0", id, error: { code: -32601, message: `method not found: ${method}` } }; +} + +function main(): void { + log(`serving ${SPECS.length} tool(s) -> ${ENDPOINT || "(no endpoint)"}`); + let buffer = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk: string) => { + buffer += chunk; + let newline: number; + while ((newline = buffer.indexOf("\n")) !== -1) { + const line = buffer.slice(0, newline).trim(); + buffer = buffer.slice(newline + 1); + if (!line) continue; + let parsed: any; + try { + parsed = JSON.parse(line); + } catch { + log(`skipping non-JSON line: ${line.slice(0, 120)}`); + continue; + } + Promise.resolve(handle(parsed)) + .then((response) => { + if (response) send(response); + }) + .catch((err) => log(`handler error: ${err?.message ?? err}`)); + } + }); + process.stdin.on("end", () => process.exit(0)); +} + +main(); diff --git a/services/oss/src/agent.py b/services/oss/src/agent.py index 42f9b1832c..90f98ae948 100644 --- a/services/oss/src/agent.py +++ b/services/oss/src/agent.py @@ -17,6 +17,7 @@ from typing import Any, Dict, List, Optional, Tuple import httpx +from opentelemetry import trace as otel_trace import agenta as ag from agenta.sdk.engines.tracing.propagation import inject @@ -27,6 +28,7 @@ from oss.src.agent_pi.pi_harness import PiHarness from oss.src.agent_pi.pi_http_harness import PiHttpHarness from oss.src.agent_pi.ports import Harness, HarnessRequest, ToolCallback, TraceContext +from oss.src.agent_pi.rivet_harness import RivetHarness from oss.src.agent_pi.schemas import AGENT_SCHEMAS log = get_module_logger(__name__) @@ -41,13 +43,38 @@ _TOOLS_RESOLVE_TIMEOUT = float(os.getenv("AGENTA_AGENT_TOOLS_TIMEOUT", "30")) -def _build_harness() -> Harness: +def _build_harness( + harness: Optional[str] = None, + sandbox: Optional[str] = None, +) -> Harness: """Pick the harness adapter for the current deployment. - - ``AGENTA_AGENT_PI_URL`` set (docker): call the Pi sidecar over HTTP. + Runtime axis (``AGENTA_AGENT_RUNTIME``): + - ``rivet``: drive the harness over ACP via a rivet daemon (WP-8). The harness + (pi/claude) and sandbox (local/daytona) are independent config axes, taken from + the request config when set (so they are editable in the playground), else the + ``AGENTA_AGENT_HARNESS`` / ``AGENTA_AGENT_SANDBOX`` env defaults. + - default (``pi``): the legacy in-process Pi path (WP-2), kept so nothing regresses. + + Transport axis (both runtimes): + - ``AGENTA_AGENT_PI_URL`` set (docker): call the TS wrapper sidecar over HTTP. - otherwise (local): spawn the TS wrapper as a subprocess. """ pi_url = os.getenv("AGENTA_AGENT_PI_URL") + runtime = os.getenv("AGENTA_AGENT_RUNTIME", "pi").lower() + + if runtime == "rivet": + harness = (harness or os.getenv("AGENTA_AGENT_HARNESS", "pi")).lower() + sandbox = (sandbox or os.getenv("AGENTA_AGENT_SANDBOX", "local")).lower() + if pi_url: + return RivetHarness(harness=harness, sandbox=sandbox, base_url=pi_url) + return RivetHarness( + harness=harness, + sandbox=sandbox, + runtime=LocalRuntime(), + wrapper_dir=str(wrapper_dir()), + ) + if pi_url: return PiHttpHarness(pi_url) return PiHarness(LocalRuntime(), wrapper_dir=str(wrapper_dir())) @@ -110,6 +137,62 @@ def _latest_user_message(messages: Optional[List[Any]]) -> str: return "" +# Map a vault standard-provider kind to the env var the harness (Pi/Claude/litellm) +# reads. Only providers an agent harness can use are listed. +_PROVIDER_ENV_VARS = { + "openai": "OPENAI_API_KEY", + "anthropic": "ANTHROPIC_API_KEY", + "gemini": "GEMINI_API_KEY", + "mistral": "MISTRAL_API_KEY", + "mistralai": "MISTRAL_API_KEY", + "groq": "GROQ_API_KEY", + "together_ai": "TOGETHERAI_API_KEY", + "openrouter": "OPENROUTER_API_KEY", +} + + +async def _resolve_harness_secrets() -> Dict[str, str]: + """Resolve provider API keys from the project vault into harness env vars. + + The agent authenticates the harness with the same provider keys the project + configured for LLM access. We fetch the project's vault ``provider_key`` secrets + from the backend directly (same backend + caller credential the tool resolver uses) + and inject each as its standard env var, so the harness uses whichever its model + needs. The SDK's per-request secret context does not propagate to this custom route, + so we resolve here rather than reading it. Empty when the vault has none (the harness + then falls back to its own login / OAuth — see ``runRivet``). Best-effort. + """ + api_base = _agenta_api_base() + if not api_base: + return {} + headers = {"Content-Type": "application/json"} + authorization = _request_authorization() + if authorization: + headers["Authorization"] = authorization + + try: + async with httpx.AsyncClient(timeout=_TOOLS_RESOLVE_TIMEOUT) as client: + response = await client.get(f"{api_base}/secrets/", headers=headers) + if response.status_code >= 400: + log.warning("agent: vault secrets fetch HTTP %s", response.status_code) + return {} + secrets = response.json() or [] + except Exception: # pylint: disable=broad-except + log.warning("agent: vault secrets fetch failed", exc_info=True) + return {} + + env: Dict[str, str] = {} + for secret in secrets: + if not isinstance(secret, dict) or secret.get("kind") != "provider_key": + continue + data = secret.get("data") or {} + env_var = _PROVIDER_ENV_VARS.get(str(data.get("kind", "")).lower()) + key = (data.get("provider") or {}).get("key") + if env_var and key: + env.setdefault(env_var, key) + return env + + def _trace_context() -> Optional[TraceContext]: """Capture the active workflow span's trace context for the harness. @@ -324,7 +407,12 @@ async def _agent( builtins, custom_tools, tool_callback = await _resolve_tools(tools_config) - harness = _build_harness() + # Harness (pi/claude) and sandbox (local/daytona) are editable config (see + # schemas.py), so a playground run can switch engine or environment; unset falls + # back to the env defaults inside _build_harness. + harness_id = params.get("harness") + sandbox_id = params.get("sandbox") + harness = _build_harness(harness=harness_id, sandbox=sandbox_id) await harness.setup() try: @@ -338,14 +426,43 @@ async def _agent( custom_tools=custom_tools, tool_callback=tool_callback, trace=_trace_context(), + secrets=await _resolve_harness_secrets(), ) ) finally: await harness.shutdown() + _record_usage(result.usage) + return {"role": "assistant", "content": result.output} +def _record_usage(usage: Optional[Dict[str, Any]]) -> None: + """Stamp the agent's token/cost totals onto the active ``/invoke`` workflow span. + + The harness emits its own span tree (turns, LLM, tools) in a separate OTLP batch, so + Agenta's per-batch cumulative roll-up cannot bridge the totals onto the workflow + span. Setting ``gen_ai.usage.*`` here records them directly on that span (the root of + its batch), so the trace shows the run's tokens and cost. Best-effort. + """ + if not usage or not usage.get("total"): + return + try: + span = otel_trace.get_current_span() + input_tokens = int(usage.get("input") or 0) + output_tokens = int(usage.get("output") or 0) + span.set_attribute("gen_ai.usage.input_tokens", input_tokens) + span.set_attribute("gen_ai.usage.output_tokens", output_tokens) + span.set_attribute("gen_ai.usage.prompt_tokens", input_tokens) + span.set_attribute("gen_ai.usage.completion_tokens", output_tokens) + span.set_attribute("gen_ai.usage.total_tokens", int(usage.get("total") or 0)) + cost = usage.get("cost") + if cost: + span.set_attribute("gen_ai.usage.cost", float(cost)) + except Exception: # pylint: disable=broad-except + log.warning("agent: failed to record usage on workflow span", exc_info=True) + + def create_agent_app(): app = ag.create_app() # No builtin URI yet: registering the agent as a first-class workflow type diff --git a/services/oss/src/agent_pi/ports.py b/services/oss/src/agent_pi/ports.py index 4b436db6c6..dc768a29cd 100644 --- a/services/oss/src/agent_pi/ports.py +++ b/services/oss/src/agent_pi/ports.py @@ -114,6 +114,12 @@ class HarnessRequest: model: Optional[str] = None prompt: Optional[str] = None messages: List[Any] = field(default_factory=list) + # Continue a prior run by id (rivet path resumes/replays its history). None = new. + session_id: Optional[str] = None + # Provider API keys resolved from the project vault, as harness env vars + # ({"OPENAI_API_KEY": "...", ...}). Injected into the harness environment (local + # daemon + Daytona env_vars). Empty => the harness uses its own login (OAuth). + secrets: Dict[str, str] = field(default_factory=dict) tools: List[str] = field(default_factory=list) # Resolved runnable tool specs, already in the camelCase wire shape the TS # wrapper turns into Pi customTools: {name, description, inputSchema, callRef}. @@ -129,6 +135,10 @@ class HarnessResult: output: str session_id: Optional[str] = None model: Optional[str] = None + # Run token/cost totals ({input, output, total, cost}). The harness span tree is + # exported in a separate OTLP batch from the workflow span, so the service rolls + # these onto the workflow span itself (see agent.py). None when unavailable. + usage: Optional[Dict[str, Any]] = None class Harness(ABC): diff --git a/services/oss/src/agent_pi/rivet_harness.py b/services/oss/src/agent_pi/rivet_harness.py new file mode 100644 index 0000000000..cd84939ab1 --- /dev/null +++ b/services/oss/src/agent_pi/rivet_harness.py @@ -0,0 +1,143 @@ +"""Rivet harness adapter (WP-8): drives the agent over ACP via a rivet daemon. + +Same ``Harness`` port as the Pi adapters, but the transport behind it runs the chosen +harness (Pi, Claude Code, ...) over the Agent Client Protocol through a rivet +``sandbox-agent`` daemon, rather than the bespoke Pi SDK calls. The ``/invoke`` contract +is unchanged; harness and sandbox become config values carried on the wire to the TS +runner (``runRivet.ts``, selected by ``AGENT_BACKEND=rivet``). + +Two transports, mirroring the Pi adapters: + +- HTTP (docker): POST the envelope to the wrapper running as a sidecar. Selected when a + base URL is provided (``AGENTA_AGENT_PI_URL``); the sidecar runs in rivet mode. +- subprocess (local): spawn the TS CLI with ``AGENT_BACKEND=rivet`` and hand it the + envelope over stdio. + +The envelope adds ``harness``, ``sandbox``, and ``sessionId`` to the Pi-shaped fields; +everything else (agentsMd, model, prompt, messages, tools, customTools, toolCallback, +trace) is identical, so the Python side stays thin. +""" + +import json +import os +from typing import List, Optional, Sequence + +import httpx + +from agenta.sdk.utils.logging import get_module_logger + +from .ports import Harness, HarnessRequest, HarnessResult, Runtime + +log = get_module_logger(__name__) + +_DEFAULT_TIMEOUT = float(os.getenv("AGENTA_AGENT_TIMEOUT", "180")) +_DEFAULT_COMMAND = ["pnpm", "exec", "tsx", "src/cli.ts"] + + +def _rivet_payload(request: HarnessRequest, harness: str, sandbox: str) -> dict: + """Build the wire envelope: the Pi-shaped fields plus harness/sandbox/sessionId.""" + return { + "harness": harness, + "sandbox": sandbox, + "sessionId": request.session_id, + "secrets": request.secrets or {}, + "agentsMd": request.agents_md, + "model": request.model, + "prompt": request.prompt, + "messages": request.messages, + "tools": request.tools, + "customTools": request.custom_tools, + "toolCallback": request.tool_callback.to_wire() + if request.tool_callback + else None, + "trace": request.trace.to_wire() if request.trace else None, + } + + +def _to_result(data: dict) -> HarnessResult: + if not data.get("ok"): + raise RuntimeError(f"Rivet run failed: {data.get('error')}") + return HarnessResult( + output=data.get("output", ""), + session_id=data.get("sessionId"), + model=data.get("model"), + usage=data.get("usage"), + ) + + +class RivetHarness(Harness): + """Drive the harness over ACP via rivet, over HTTP or a local subprocess. + + Pass ``base_url`` for the HTTP sidecar transport; otherwise a ``runtime`` plus + ``wrapper_dir`` runs the TS CLI as a subprocess. ``harness`` (pi/claude) and + ``sandbox`` (local/daytona) are the two orthogonal swap axes. + """ + + def __init__( + self, + *, + harness: str, + sandbox: str, + base_url: Optional[str] = None, + runtime: Optional[Runtime] = None, + wrapper_dir: Optional[str] = None, + command: Optional[Sequence[str]] = None, + timeout: float = _DEFAULT_TIMEOUT, + ) -> None: + if not base_url and not runtime: + raise ValueError( + "RivetHarness needs either base_url (HTTP) or runtime (subprocess)" + ) + self._harness = harness + self._sandbox = sandbox + self._base_url = base_url.rstrip("/") if base_url else None + self._runtime = runtime + self._wrapper_dir = wrapper_dir + self._command: List[str] = list(command or _DEFAULT_COMMAND) + self._timeout = timeout + + async def setup(self) -> None: + if self._runtime: + await self._runtime.start() + + async def shutdown(self) -> None: + if self._runtime: + await self._runtime.shutdown() + + async def invoke(self, request: HarnessRequest) -> HarnessResult: + payload = _rivet_payload(request, self._harness, self._sandbox) + if self._base_url: + return await self._invoke_http(payload) + return await self._invoke_subprocess(payload) + + async def _invoke_http(self, payload: dict) -> HarnessResult: + async with httpx.AsyncClient(timeout=self._timeout) as client: + response = await client.post(f"{self._base_url}/run", json=payload) + if response.status_code >= 500: + raise RuntimeError( + f"Rivet wrapper HTTP {response.status_code}: {response.text[:1000]}" + ) + return _to_result(response.json()) + + async def _invoke_subprocess(self, payload: dict) -> HarnessResult: + assert self._runtime is not None + result = await self._runtime.exec( + self._command, + json.dumps(payload).encode("utf-8"), + cwd=self._wrapper_dir, + env={**os.environ, "AGENT_BACKEND": "rivet"}, + timeout=self._timeout, + ) + if not result.stdout.strip(): + raise RuntimeError( + "Rivet wrapper returned no output. " + f"exit={result.code} stderr={result.stderr[-2000:]}" + ) + try: + data = json.loads(result.stdout) + except json.JSONDecodeError as exc: + raise RuntimeError( + "Rivet wrapper returned invalid JSON. " + f"stdout={result.stdout[:500]} stderr={result.stderr[-1000:]}" + ) from exc + return _to_result(data) diff --git a/services/oss/src/agent_pi/schemas.py b/services/oss/src/agent_pi/schemas.py index cef2440679..7dc6af2580 100644 --- a/services/oss/src/agent_pi/schemas.py +++ b/services/oss/src/agent_pi/schemas.py @@ -59,6 +59,23 @@ "llm_config": {"model": _DEFAULT_MODEL, "tools": []}, }, }, + # The two orthogonal runtime axes, editable in the playground so a run can + # switch engine (pi/claude) or where it runs (local/daytona) without redeploy. + # Read in agent.py and threaded to the rivet harness; fall back to env defaults. + "harness": { + "type": "string", + "title": "Harness", + "enum": ["pi", "claude"], + "default": "pi", + "description": "Coding agent engine to drive over ACP (pi or claude).", + }, + "sandbox": { + "type": "string", + "title": "Sandbox", + "enum": ["local", "daytona"], + "default": "local", + "description": "Where the agent runs: local daemon or a Daytona sandbox.", + }, }, }