diff --git a/.gitignore b/.gitignore index f6d5d54..52c748a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,35 @@ +# Dependencies + build artifacts node_modules/ dist/ .turbo/ +*.tsbuildinfo +.pnpm-store/ +.cache/ +coverage/ *.log + +# Editor / OS noise .DS_Store -.env -.env.local -coverage/ .vscode/ .idea/ -*.tsbuildinfo -.pnpm-store/ -.cache/ +*.swp +*~ + +# Env files — only .env.example is committed +.env +.env.* +!.env.example + +# Local fixtures + scratch fixtures/.tmp/ +text +test.html +scratch/ +tmp/ +*.tmp + +# Example agent OUTPUTS (the agents under examples/ generate these — outputs +# are run artifacts, not source) examples/binary-outputs/ examples/decks/ examples/marketing-outputs/ @@ -20,13 +38,20 @@ examples/pdfs/ examples/security-reports/ examples/wedge16-sessions/ -# Local Claude Code state (per-user settings.local.json holds live API keys -# for that developer's environment — never commit). +# Local Claude Code state (per-user settings.local.json may hold live API keys +# for that developer's environment — never commit) .claude/ -# Untracked dev tester with baked-in keys for local testing. -test.html - -# Python bytecode caches from scripts/ test runners. +# Python bytecode caches from scripts/ test runners __pycache__/ *.pyc + +# Private operational handoff docs — keys, access creds, infra maps. NEVER +# commit these to a public repo. +private.md +PRIVATE.md +*.private.md + +# Internal planning / handoff docs that should not be on the public repo +PLAN.md +docs.md diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index c8dde5f..0000000 --- a/PLAN.md +++ /dev/null @@ -1,1060 +0,0 @@ -# ComputerAgent SDK — Architecture & Plan (rev 4) - -> **Rev 4 changes:** Codified engineering principles (SOLID + design patterns + code rules). Tightened Wedge 1 to a true MVP with explicit build sequence — skeleton first, working curl demo second, polish (replay buffer, audit, auth, full conformance suite) third. Several originally-Wedge-1 features moved to Wedge 1.5 to keep first-ship lean. -> -> **Rev 3 (preserved):** Build order inverted. **The reusable Harness Server module ships first** as a generic SSE+POST framework with two pluggable interfaces (`EngineDriver`, `IdentityLoader`). Claude Agent SDK and gitagent (GAP/gapman) are the first plug-ins, not built-in. -> -> **Rev 2 (preserved):** Transport pinned to **SSE + POST** (not WebSocket). `engine-claude-agent-sdk` is built on the real `@anthropic-ai/claude-agent-sdk` v0.2.132 — direct import, no CLI / stdout parsing. Inherits `SessionStore`, `canUseTool`, partial-message streaming, and budget caps from the SDK. - -## Context - -Today's agent stack conflates three concerns that should be orthogonal: - -1. **Identity** — who the agent is (prompts, rules, tools, compliance, skills) -2. **Loop** — the agentic engine that turns prompts → tool calls → outputs (Claude Code, Codex, OpenCode, Gemini CLI, gitclaw, …) -3. **Substrate** — where the OS-level work happens (local shell, E2B, Lyzr Compute, Modal, Daytona, …) - -GitAgentProtocol (GAP) v0.1.0 cleanly solved (1) — a portable, git-native, framework-agnostic agent definition. It also has scaffolding for (2) via `gapman run --adapter` but doesn't standardize (3) at all. - -`ComputerAgent` is the SDK that completes the picture: a single calling convention that takes a GAP repo and runs it under any *harness* on any *runtime*. **Agents become Docker-shaped** — a portable image (GAP repo), a swappable engine (harness), and a swappable host (runtime). - -## Vision - -``` -const agent = new ComputerAgent({ source, envs, sessionId, harness, runtime }) - │ │ │ │ - │ │ │ └─ WHERE (substrate) - │ │ └─────────── HOW (agentic loop) - │ └─────────────────────── STATE (session handle) - └────────────────────────────────────── WHAT (identity = GAP repo) - -agent.chat(messages) → AsyncIterable & Promise - │ - └─ INVOKE (per-turn input; sync array, single message, or AsyncIterable) -``` - -**Constructor configures the agent.** `.chat()` invokes it. Multiple `.chat()` calls on the same agent share a session — each call is a turn in the same conversation. - -**Positioning.** *"Run any GAP agent, anywhere, with any loop."* The novel artifact is not the SDK itself — it's the two protocols underneath it that turn harnesses and runtimes into pluggable commodities. SDK is the reference client; the protocols are the standard. Comparable: LSP (editors ↔ language servers), MCP (LLMs ↔ tools), OCI (registries ↔ runtimes). - -## Engineering Principles - -This codebase is held to a small set of rules. Every module honors them; reviews enforce them. - -### SOLID, applied to this architecture - -- **Single Responsibility.** Every package owns one concern; every file ≤ 250 LOC; every class ≤ 5 public methods. If a module grows past those thresholds, split it. Examples in this repo: - - `Session` only holds per-session state. SSE serialization, replay, and routing live in separate files. - - `IdentityLoader` translates; the engine consumes; neither knows the other's internals. -- **Open/Closed.** New engines and loaders are added by *implementing an interface*, never by modifying `harness-server`. The framework is closed to modification, open to extension. -- **Liskov Substitution.** Any `EngineDriver` works wherever any other does. The conformance suite is the LSP enforcer — failing it = breaking substitution. -- **Interface Segregation.** Small interfaces. `EngineDriver` is 2 fields + 1 method. `IdentityLoader` is 1 method. `SessionStore` (re-exported from claude-agent-sdk) is 4 methods. Never bundle unrelated concerns. -- **Dependency Inversion.** `harness-server` imports *types only* from `@computeragent/protocol`, never concrete plug-ins. Plug-ins are wired by the user at server boot. There is no `import { ClaudeAgentEngine } from "engine-claude-agent-sdk"` anywhere inside `harness-server`. - -### Design patterns we use deliberately - -| Pattern | Where | Why | -|---|---|---| -| **Strategy** | `EngineDriver`, `IdentityLoader`, `SessionStore`, `AuthHandler`, `AuditSink` | Pluggable behavior; swap at boot | -| **Adapter** | `identity-gitagentprotocol`'s per-engine adapters (`gapToClaudeAgentOptions`, future `gapToCodexOptions`, …) | One loader fans out to many engine targets | -| **Factory** | `createHarnessServer({...})`, `Session.create()` | Hide construction; centralize defaults; return interface, not class | -| **Registry** | `SessionRegistry` (in-memory map + TTL) | Lookup by id; lifecycle owner | -| **Observer / event stream** | `AsyncIterable` from engines, SSE stream to client | Push-based, backpressure-friendly, cancelable via abort | -| **Command** | One thin route handler per REST endpoint, dispatching to a service method | Routes stay dumb; logic stays unit-testable without HTTP fixtures | -| **Ports & adapters (hexagonal)** | `harness-server` is the hexagon; `EngineDriver`/`IdentityLoader`/`SessionStore` are ports; concrete plug-ins are adapters | Whole architecture | -| **`Promise.withResolvers`** | Permission round-trip plumbing | Server holds open promise until client POSTs decision | -| **Ring buffer** | Per-session SSE replay buffer (Wedge 1.5) | `Last-Event-ID` resume without unbounded memory | - -### Code rules - -1. **No file over 250 LOC.** Hard cap. When a file approaches the cap, split by responsibility. -2. **No abstraction without a second consumer.** YAGNI is non-negotiable. We do not add a strategy interface for "future-proofing" — only when the second concrete implementation is on the same PR or imminently next. -3. **Pure functions for translations.** `gapToClaudeAgentOptions`, SSE event serialization, schema parsing — all pure. Side-effects (fs, network) live at the *edges* of routes. -4. **Errors are values at boundaries.** Validation returns `Result`, not throws. Inside the request lifecycle, throws are mapped to typed HTTP responses by one error-mapper middleware. -5. **Zod at the wire, types inside.** All inbound HTTP and outbound SSE bodies pass through zod. Internal code uses inferred types and trusts them — no defensive re-validation. -6. **No `any`.** Use `unknown`, narrow before use. The TypeScript compiler is the second reviewer. -7. **No `console.log`; structured logging only.** `pino` with levels. No log lines on hot paths. -8. **Tests live next to source.** `foo.ts` + `foo.test.ts`. Vitest. Snapshots only for translation outputs (deterministic). -9. **No emojis in source, logs, or generated output.** Plain text everywhere. -10. **Imports go down the dependency graph.** `engine-*` and `identity-*` import from `protocol`. `harness-server` imports only from `protocol`. No cross-imports between engine and identity packages. -11. **No barrel re-exports for internals.** Each package's `src/index.ts` exports only its public surface. Internals stay internal. -12. **Boring tech.** `hono`, `zod`, `pino`, `vitest`, `tsup` (or `tsc`). No experimental frameworks; we want the standard to outlive us. - -### Definition of "done" for any module - -- Unit tests pass with ≥ 80% line coverage on logic (skip dumb wiring). -- Public API has a JSDoc one-liner per export. -- No file > 250 LOC. -- Typecheck clean with `strict: true` and `noUncheckedIndexedAccess: true`. -- One example or test that exercises the happy path end-to-end. - -## Core Architecture: Two Stacked Protocols - -``` - ┌────────────────────────────────────────────────────────────────┐ - │ User's TypeScript code │ - │ import { ComputerAgent } from "@computeragent/sdk"; │ - └────────────────────────────────────────────────────────────────┘ - │ - │ Harness Protocol (SSE + POST over HTTP/2) - │ ──────────────────────────────────── - │ Client → Server (POST): - │ /v1/sessions, /messages, /permission, /cancel - │ Server → Client (SSE): - │ sdk_message events (forwarded SDKMessage union) - │ ca_* events (ComputerAgent framing layer) - ▼ - ┌────────────────────────────────────────────────────────────────┐ - │ Harness Server (runs INSIDE the runtime, port :7700) │ - │ • Materializes GAP repo on disk (clone or unpack) │ - │ • Translates GAP manifest → harness-native options │ - │ • Calls underlying agent loop (e.g., claude-agent-sdk.query) │ - │ • Forwards yielded messages over SSE; receives input via POST│ - │ • Distributed as Docker image: computeragent/harness- │ - └────────────────────────────────────────────────────────────────┘ - │ - │ Substrate Protocol - │ ──────────────────────────────────── - │ exec / fs / port_forward / lifecycle - ▼ - ┌────────────────────────────────────────────────────────────────┐ - │ Substrate (local subprocess | e2b | lyzrcompute | …) │ - └────────────────────────────────────────────────────────────────┘ -``` - -## Why SSE + POST (not WebSocket) - -The harness is **half-duplex by nature**: the server emits a high-volume event stream; the client occasionally injects user messages or permission decisions. SSE matches this shape exactly. - -| Concern | SSE + POST | WebSocket | -|---|---|---| -| Native browser support | ✅ `EventSource` | Needs library | -| HTTP/2 multiplexing | ✅ | ❌ (HTTP/1.1 upgrade) | -| Reconnect with replay | ✅ `Last-Event-ID` header (built into spec) | Manual sequence-tracking | -| Reverse proxy / CDN friendly | ✅ Cloudflare, Vercel, Cloud Run all support | Often blocked or expensive | -| Debuggable with `curl` | ✅ | ❌ (need `wscat`) | -| Server libs (Node) | `fastify-sse-v2`, `hono/streaming` — boring, mature | `ws` works but more state | -| Auth | Standard HTTP headers/bearer/cookies | Same, plus subprotocol fiddling | -| Mirrors Anthropic API streaming | ✅ Same wire format | Different | - -**The Anthropic Messages API itself streams over SSE.** When `includePartialMessages: true`, the Claude Agent SDK exposes `SDKPartialAssistantMessage` with raw `BetaRawMessageStreamEvent` deltas — those are SSE events from the LLM API. By using SSE end-to-end, our wire format stays homogeneous from LLM → SDK → harness-server → client. - -**Resumability.** Each SSE event has a monotonic `id`. If the client disconnects, it reconnects with `Last-Event-ID: ` and the harness server replays from a per-session ring buffer (default: last 1,000 events or 5 minutes). This is critical when running on lyzrcompute or E2B over flaky networks. - -## The Harness Protocol (concrete) - -The protocol exposes **two parallel surfaces** at the same paths so a user can pick the model that fits their use case: - -- **`/v1/chat`** — one-shot convenience. *One POST, one SSE response.* The server creates the session internally, runs the agent to completion (or until disconnect), and cleans up. This is the 80% case: chat UIs, automation scripts, single-prompt jobs. Mental model: same as OpenAI / Anthropic chat-completions, but the response stream is the full agentic event sequence, not just text deltas. -- **`/v1/sessions`** — long-lived primitive. Explicit session lifecycle. Use when you need mid-flight control: queueing follow-up messages over hours, interrupting a long-running agent, attaching multiple clients to one session, fine-grained permission round-trips with human approval, cancel + resume. - -`/v1/chat` is implemented internally as `POST /sessions` + auto-`GET /events` + auto-`DELETE` on stream end. There is no behavioral divergence between them — same engine, same loader, same SSE event types. The convenience endpoint is sugar. - -### `POST /v1/chat` — one-shot (the 80% case) - -``` -POST /v1/chat -Headers: Content-Type: application/json - Accept: text/event-stream -Body: { - engine: "claude-agent-sdk", - identity: { - loader: "gitagentprotocol", - source: { type: "git", url } | { type: "local", path } | { type: "inline", manifest, files } - }, - envs?: { ANTHROPIC_API_KEY: "..." }, - messages?: [{ role: "user", content: string | ContentBlock[] }], - options?: { # optional engine-level overrides - model, maxTurns, permissionMode, includePartials, maxBudgetUsd, ... - }, - sessionId?: string # if provided, server reuses; else generates - } -Resp: text/event-stream # the SSE stream IS the response body - # First event always carries the sessionId so a client can still POST to - # /v1/sessions/:id/permission or /cancel mid-stream if needed. -``` - -Curl example: -```bash -curl -N -X POST http://127.0.0.1:7700/v1/chat \ - -H "Content-Type: application/json" \ - -d '{ - "engine": "claude-agent-sdk", - "identity": { "loader": "gitagentprotocol", "source": { "type": "git", "url": "github.com/open-gitagent/gitagent-protocol/examples/standard" } }, - "envs": { "ANTHROPIC_API_KEY": "sk-..." }, - "messages": [{ "role": "user", "content": "Review the README for inconsistencies" }], - "options": { "maxTurns": 20 } - }' -``` - -This single command boots a session, drives the full agentic loop, and streams every SDK message + ComputerAgent framing event back. When the agent emits `result`, the server emits `ca_session_ended` and closes the stream. - -### Session API — long-lived primitive - -``` -POST /v1/sessions -Body: same shape as /v1/chat body (without `messages` if you want to start empty) -Resp: { sessionId, engine, identity: { name, version, sha }, capabilities, eventsUrl } - -GET /v1/sessions/:id/events # SSE — open separately -Headers: Accept: text/event-stream - Last-Event-ID: - -POST /v1/sessions/:id/messages -Body: { message: { role: "user", content: string | ContentBlock[] } } -Resp: { ack: true, messageId } # pushes onto the engine's user-message queue - -POST /v1/sessions/:id/permission/:callId -Body: { decision: "allow" | "deny" | "modify", input?: any, reason?: string } -Resp: { ack: true } - -POST /v1/sessions/:id/cancel -Resp: { ok: true } # AbortController.abort() upstream - -DELETE /v1/sessions/:id -Resp: { ok: true } # releases resources - -GET /v1/sessions/:id # metadata only (status, usage so far) -GET /v1/health # { ok, version, engines: {...capabilities}, loaders: [...] } -``` - -### Implementation note (Wedge 1) - -Both surfaces share the same underlying `Session` + `SessionRegistry` machinery. `POST /v1/chat` is implemented in `src/routes/chat.ts` as a thin handler that: - -1. Calls the same `createSession` service the session route uses. -2. Pipes the SSE encoder directly into the response body (instead of buffering and returning a sessionId). -3. Registers an `AbortSignal` on the response stream so client disconnect aborts the engine. -4. Auto-deletes the session from the registry when the stream completes or the client disconnects. - -This is ~40 LOC. No duplicated logic; just a different transport of the same plumbing. Ports & adapters in action. - -### Filesystem API — the session workdir over HTTP - -Every session has a **workdir** — the directory the agent runs in, where it reads, writes, and edits files. Exposing that workdir through HTTP turns the harness server from "agent event router" into "complete agent workspace exposed over an API." Same category as Codespaces / Replit / E2B, but agent-shaped. - -All paths are **jailed** to the session's workdir. Absolute paths, `..` traversal, and symlinks pointing outside the workdir are rejected with `400 PATH_ESCAPE`. Auth is loopback-only in MVP; pluggable `AuthHandler` lands in Wedge 1.5. - -``` -GET /v1/sessions/:id/fs/tree?path=&depth= -Resp: { entries: [{ path, type: "file"|"dir", size, mtime, mode }, ...] } - — `path` defaults to "/" (workdir root); `depth` defaults to 1. - -GET /v1/sessions/:id/fs/file?path=[&format=text|binary] -Resp: raw bytes (Content-Type sniffed) or utf-8 text body. - Range requests supported for large files. - -PUT /v1/sessions/:id/fs/file?path= -Body: raw bytes -Resp: { ok: true, sha, size } - — Creates parent dirs if missing. Writes are atomic (tmp + rename). - -POST /v1/sessions/:id/fs/edit -Body: { path, old_string, new_string, replace_all?: boolean } -Resp: { ok: true, replacements } - — Same string-replace semantics as Claude Code's Edit tool. Useful for diffs. - -DELETE /v1/sessions/:id/fs/file?path=[&recursive=true] -Resp: { ok: true } - -POST /v1/sessions/:id/fs/mkdir -Body: { path, recursive?: boolean } -Resp: { ok: true } - -POST /v1/sessions/:id/fs/move -Body: { from, to } -Resp: { ok: true } -``` - -**Wedge 1.5 — live workspace watch** (deferred): - -``` -GET /v1/sessions/:id/fs/watch?path= - Accept: text/event-stream -SSE: event: fs.change - data: { path, kind: "create"|"modify"|"delete" } -``` - -Pair `/fs/watch` with `/sessions/:id/events` and a client gets the **complete picture**: what the agent is *thinking* (events stream) and what it's *producing* (file changes). This is the killer DX for agent UIs — open both streams side by side. - -### Architectural fit: Substrate FS port - -In Wedge 1 the substrate is implicit-local — `runtime-local` is the only substrate, the workdir is a real path, FS routes call Node `fs/promises` directly. In Wedge 3 (real substrates: E2B, Lyzr Compute), the same routes call the abstract `substrate.fs.{read,write,list,…}` port. **The HTTP surface doesn't change.** Code path: - -``` -HTTP route (routes/fs.ts) - ↓ -service (services/workspace-fs.ts) ← jails paths, validates input - ↓ -substrate.fs port ← in Wedge 1: just Node fs; in Wedge 3: pluggable -``` - -This keeps Wedge 1's FS implementation small (~250 LOC across route + service + path-jailer) while not painting the protocol into a corner for remote substrates. - -### SSE stream (Server → Client) - -``` -GET /v1/sessions/:id/events -Headers: Accept: text/event-stream - Last-Event-ID: -``` - -Two event families: - -**`event: sdk_message`** — verbatim forwarding of the underlying SDK's message union. The data is the JSON-serialized `SDKMessage` from `@anthropic-ai/claude-agent-sdk`: - -``` -event: sdk_message -id: 0042 -data: {"type":"assistant","uuid":"...","session_id":"...","message":{...},"parent_tool_use_id":null} - -event: sdk_message -id: 0043 -data: {"type":"stream_event","event":{...BetaRawMessageStreamEvent...},"parent_tool_use_id":null,...} -``` - -This means **clients that already know the Claude Agent SDK's types can consume our stream with zero translation**. Other harnesses (gitclaw, codex) emit their own `sdk_message` shapes; we ship typed adapters for each in `@computeragent/sdk`. - -**`event: ca_*`** — ComputerAgent-level framing the underlying SDKs don't emit: - -``` -event: ca_session_started -data: { sessionId, harness, gap: {...}, model, capabilities: ["partials","hitl","sessions"] } - -event: ca_permission_request -data: { callId, toolName, input, risk: "low|medium|high|destructive", - gap_compliance: { framework, rule_citations? } } - -event: ca_substrate_event -data: { kind: "port_forward", port, public_url } // for serving demos out of sandboxes - -event: ca_usage_snapshot -data: { input_tokens, output_tokens, cache_*, cost_usd, compute_seconds_substrate } - -event: ca_session_ended -data: { reason: "complete"|"cancelled"|"error"|"budget_exceeded", final?: SDKResultMessage } -``` - -The `ca_*` family is **opt-in metadata layered onto SDK passthrough**. Clients that ignore these events still get a fully usable stream. - -## The Harness Server: a Reusable Framework with Two Pluggable Interfaces - -The Harness Server is **not bound to Claude Code or to GAP**. It's a generic SSE+POST framework for *any* agent loop driving *any* identity format. Engines and identity loaders are plug-ins that satisfy two interfaces. - -```ts -// @computeragent/harness-server/src/contracts.ts - -/** An EngineDriver wraps an agent loop (Claude Agent SDK, Codex, gitclaw, …). */ -export interface EngineDriver { - name: string; // "claude-agent-sdk" | "codex" | "gitclaw" | … - capabilities: { - streamingInput: boolean; // accepts user msgs mid-session - partialMessages: boolean; // emits token-level deltas - permissionCallback: boolean; // supports per-tool HITL - sessions: boolean; // resume/fork - budget: boolean; // hard cost cap - }; - - startSession(ctx: EngineContext): AsyncIterable; - // EngineContext bundles: options, userMessageQueue, onPermissionRequest, - // abortSignal, budget, workdir, envs, sessionStore -} - -/** An IdentityLoader translates a "what is this agent" source into engine options. */ -export interface IdentityLoader { - name: string; // "gitagentprotocol" | "openai-assistants" | "crewai" | … - - load(args: { - source: IdentitySource; // git URL | local path | inline manifest - targetEngine: string; // engine the result must feed - workdir: string; // substrate-provided dir - }): Promise>; - // LoadResult: { options, metadata: {name,version,sha?}, cleanup? } -} - -/** Spin up a server with whatever engines + loaders you registered. */ -export function createHarnessServer(config: { - engines: Record; - identityLoaders: Record; - port?: number; - replayBufferSize?: number; // for Last-Event-ID resume - auth?: AuthHandler; -}): HarnessServer; -``` - -**Usage:** -```ts -import { createHarnessServer } from "@computeragent/harness-server"; -import { ClaudeAgentEngine } from "@computeragent/engine-claude-agent-sdk"; -import { GitAgentProtocolLoader } from "@computeragent/identity-gitagentprotocol"; - -const server = createHarnessServer({ - engines: { "claude-agent-sdk": new ClaudeAgentEngine() }, - identityLoaders: { gitagentprotocol: new GitAgentProtocolLoader() }, - port: 7700, -}); - -await server.listen(); -``` - -A client POSTs: -``` -POST /v1/sessions -{ - "engine": "claude-agent-sdk", - "identity": { "loader": "gitagentprotocol", "source": { "type": "git", "url": "github.com/org/my-agent" } }, - "envs": { "ANTHROPIC_API_KEY": "sk-..." }, - "options": { "maxTurns": 50 } -} -``` - -The server: validates → calls `loader.load()` → calls `engine.startSession()` → forwards events over SSE. **Adding a new engine or identity format = ship a small package implementing one interface.** No fork, no upstream PR. - -### What the Harness Server Owns (the value of the framework) - -The framework — not the engine plug-ins — is responsible for: - -1. **HTTP routing** (Hono) — `/v1/sessions`, `/messages`, `/permission`, `/cancel`, `/events`, `/health` -2. **SSE streaming** with monotonic event IDs -3. **`Last-Event-ID` replay buffer** (per-session ring buffer) -4. **Session registry** with TTLs -5. **Permission round-trip** — server holds `Promise` open while `ca_permission_request` is on the wire; resolves when client POSTs to `/permission/:callId` -6. **Abort wiring** — `/cancel` POST → `AbortController.abort()` → engine sees `abortSignal` -7. **Budget enforcement** — wraps engine event stream; emits `ca_session_ended { reason: "budget_exceeded" }` when usage events cross threshold -8. **Audit log tee** — when configured, every SSE event is also persisted to a write-only log (substrate-mounted file, S3, etc.) for compliance -9. **Auth** — bearer / signed JWT / mTLS hooks (pluggable) -10. **Capability negotiation** — `GET /v1/health` returns the engine's `capabilities` so the client SDK can avoid asking for unsupported features - -**The framework is the standard.** Engines and loaders are commodity adapters built on top. - -## First Engine: `@computeragent/engine-claude-agent-sdk` - -The Claude Agent SDK exposes everything we need natively. We do not parse stdout. We do not run the CLI. We `import { query } from "@anthropic-ai/claude-agent-sdk"` directly inside the engine plug-in: - -```ts -// engine-claude-agent-sdk/src/index.ts (abridged) -import { query, type SDKMessage, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk"; -import type { EngineDriver, EngineContext, EngineEvent } from "@computeragent/harness-server"; - -export class ClaudeAgentEngine implements EngineDriver { - name = "claude-agent-sdk"; - capabilities = { streamingInput: true, partialMessages: true, - permissionCallback: true, sessions: true, budget: true }; - - async *startSession(ctx: EngineContext): AsyncIterable { - async function* userIter(): AsyncIterable { - for await (const m of ctx.userMessageQueue) yield m; - } - - const q = query({ - prompt: userIter(), - options: { - ...ctx.options, - cwd: ctx.workdir, - env: ctx.envs, - includePartialMessages: true, - abortController: ctx.abortController, - maxBudgetUsd: ctx.budget?.maxUsd, - sessionStore: ctx.sessionStore, - canUseTool: async (toolName, input, opts) => { - const decision = await ctx.onPermissionRequest({ - callId: opts.tool_use_id, toolName, input, - }); - return decision; // SDK's PermissionResult shape - }, - }, - }); - - for await (const msg of q) { - yield { kind: "sdk_message", payload: msg }; // framework forwards to SSE verbatim - } - } -} -``` - -That's the whole engine. ~150 LOC including the option translation. **Future engines (Codex, gitclaw, OpenCode, Gemini) are the same shape: 100–300 LOC each.** - -## First Identity Loader: `@computeragent/identity-gitagentprotocol` - -Wraps `@open-gitagent/gapman` to validate a GAP repo and translate `agent.yaml` + `SOUL.md` + `RULES.md` + `skills/` + `tools/` + `compliance` into the Claude Agent SDK's `Options` shape (or any other engine's option shape via `targetEngine`). - -```ts -// identity-gitagentprotocol/src/index.ts (abridged) -import { validate, materialize } from "@open-gitagent/gapman"; -import type { IdentityLoader } from "@computeragent/harness-server"; -import { gapToClaudeAgentOptions } from "./adapters/claude-agent-sdk"; -import { gapToCodexOptions } from "./adapters/codex"; - -export class GitAgentProtocolLoader implements IdentityLoader { - name = "gitagentprotocol"; - - async load({ source, targetEngine, workdir }) { - const repoPath = await materialize(source, workdir); - const manifest = await validate(repoPath); - const adapter = adapters[targetEngine] ?? throwUnsupported(targetEngine); - const options = await adapter(manifest, repoPath); - return { - options, - metadata: { name: manifest.name, version: manifest.version, sha: manifest.sha }, - cleanup: async () => { /* optional unmount */ }, - }; - } -} - -const adapters: Record Promise> = { - "claude-agent-sdk": gapToClaudeAgentOptions, - "codex": gapToCodexOptions, // future -}; -``` - -The GAP→engine translation table from rev 2 lives inside `gapToClaudeAgentOptions`. **One IdentityLoader, multiple per-engine adapters.** A new engine joining the ecosystem can either ship its own loader or contribute an adapter to ours. - -## GAP → Claude Agent SDK Options Translation - -The non-trivial work in `harness-claudecode` is mapping a GAP repo to the SDK's `Options`. Owned by `@computeragent/gap-loader` (used by every harness). - -| GAP source | Claude Agent SDK option | Notes | -|---|---|---| -| `agent.yaml.model.preferred` | `model` | Direct | -| `agent.yaml.model.fallback[]` | `fallbackModel` | First entry | -| `agent.yaml.model.constraints.temperature` etc. | `extraArgs` | SDK exposes via `extraArgs` for non-canonical knobs | -| `agent.yaml.runtime.max_turns` | `maxTurns` | Direct | -| `agent.yaml.runtime.timeout` | `abortController` | We set a `setTimeout(controller.abort)` | -| `SOUL.md` + `RULES.md` | `systemPrompt: { type: "preset", preset: "claude_code", append: stitched }` | Concatenate, prepend section headers | -| `AGENTS.md` | Written to `CLAUDE.md` + `settingSources: ["project"]` | Native file Claude Code already reads | -| `skills/` | Symlink/copy → `.claude/skills/` (`settingSources: ["project"]`) | Claude Code auto-discovers | -| `tools/.yaml` (script impl) | Convert to `mcpServers["gap_tools"]` via `createSdkMcpServer` + `tool()` | We generate one in-process MCP server per GAP repo | -| `tools/.yaml` (built-in mapping) | `allowedTools: [...]` | If `name` matches a Claude built-in (Read, Edit, …) | -| `agents//` | `agents: { : AgentDefinition }` | Recursive load | -| `hooks/hooks.yaml` | `hooks: { ... }` | Direct mapping; same JSON-in/JSON-out script protocol | -| `compliance.supervision.human_in_the_loop: always` | `permissionMode: 'default'` + `canUseTool` enforced | Always prompt | -| `compliance.supervision.human_in_the_loop: none` | `permissionMode: 'bypassPermissions'` | Auto-approve (only allowed if not regulated) | -| `compliance.supervision.escalation_triggers[]` | `canUseTool` checks confidence/action_type and emits `ca_permission_request` | Caller decides | -| `compliance.supervision.kill_switch: true` | `abortController` always wired; `/cancel` POST aborts | Required | -| `compliance.recordkeeping.audit_logging: true` | Tee SSE stream to a write-only audit log inside the substrate | Substrate-level concern | -| `agent.yaml.runtime.budget_usd` (proposed) | `maxBudgetUsd` | Hard-capped by SDK | - -**Key insight:** the GAP `compliance` block is *enforceable* because the Claude Agent SDK exposes the right primitives. `human_in_the_loop: always` becomes mandatory `canUseTool` interception. `kill_switch` becomes a wired `abortController`. `budget` becomes `maxBudgetUsd`. The compliance contract of GAP isn't decorative — it has runtime teeth. - -## Module Layout (TypeScript monorepo, pnpm + turbo) - -**Tier 1 — The Standard (what ships first):** -``` -@computeragent/protocol # Type defs + zod schemas + JSON schemas - # — Harness Protocol (REST endpoints + SSE event union) - # — Substrate Protocol (exec, fs, lifecycle) - # — EngineDriver + IdentityLoader interfaces - # — Re-exports SDKMessage from claude-agent-sdk - # (only used when payload is forwarded as-is) - -@computeragent/harness-server # The reusable framework — heart of the project - # — HTTP routing (hono) + SSE streaming - # — Last-Event-ID replay buffer - # — Session registry + TTLs - # — Permission round-trip plumbing - # — Abort + budget wiring - # — Audit-log tee - # — Capability negotiation - # — NO engine, NO loader bound — pure framework -``` - -**Tier 2 — First Plug-ins:** -``` -@computeragent/engine-claude-agent-sdk # Implements EngineDriver - # Wraps query() from @anthropic-ai/claude-agent-sdk - # — ~150 LOC - -@computeragent/identity-gitagentprotocol # Implements IdentityLoader - # Wraps @open-gitagent/gapman - # — Source resolution (git/local/inline) - # — Per-target-engine adapters (Claude SDK, Codex, …) -``` - -**Tier 3 — Client + Substrates + Surface:** -``` -@computeragent/sdk # User-facing client - # — ComputerAgent class - # — SSE client w/ Last-Event-ID resume - # — Streaming events as AsyncIterable - # — Permission callback wiring - # — Re-exports SessionStore from claude-agent-sdk - -@computeragent/cli # Thin CLI wrapper (citty) - -@computeragent/runtime-local # Substrate: child_process + fs - # — Spawns harness-server as subprocess - # — No Docker required for dev - -@computeragent/runtime-e2b # Substrate: @e2b/sdk wrapper - # — Pulls/runs harness image in sandbox - # — port_forward via getHost() - -@computeragent/runtime-lyzrcompute # Later -``` - -**Tier 4 — Future Plug-ins (not us, the ecosystem):** -``` -@computeragent/engine-gitclaw # Your harness -@computeragent/engine-codex # OpenAI Codex CLI / SDK -@computeragent/engine-opencode # OpenCode -@computeragent/engine-gemini-cli # Google Gemini CLI -@computeragent/identity-openai-assistants # OpenAI Assistants spec -@computeragent/identity-crewai # CrewAI YAML -``` - -**Tier 5 — Testing:** -``` -@computeragent/testing # Mock substrate + mock engine + mock loader - # — Conformance suite engine/loader authors run - # — Same suite ComputerAgent CI runs against itself -``` - -## Public API (TypeScript) - -```ts -import { ComputerAgent } from "@computeragent/sdk"; - -// ── Configure the agent (constructor = identity + engine + runtime + policy) ── -const agent = new ComputerAgent({ - source: "github.com/org/my-agent", // GAP repo: URL | path | inline - envs: { ANTHROPIC_API_KEY: "sk-..." }, - sessionId: "abc-123", // optional; auto-generated if omitted - harness: "claude-agent-sdk", // or "gitclaw", "codex", … - runtime: "local", // or "e2b", "lyzrcompute" - - // Permission flow — wired through to canUseTool inside the harness - permissionMode: "auto", // "auto" | "ask" | "deny_destructive" - onToolCall: async (call) => ({ decision: "allow" }), - - // Optional knobs forwarded to underlying SDK - maxBudgetUsd: 5, - maxTurns: 50, - includePartials: true, - sessionStore: new FileSessionStore("./sessions"), -}); - -// ── One-shot: await for the final result ── -const { result, usage, messages } = await agent.chat([ - { role: "user", content: "Build a TODO app" }, -]); - -// ── Streaming: iterate events as they arrive ── -const handle = agent.chat([ - { role: "user", content: "Build a TODO app" }, -]); -for await (const ev of handle) { - if (ev.kind === "sdk_message" && ev.message.type === "assistant") render(ev.message.message); - if (ev.kind === "ca_permission_request") { - await handle.respondToPermission(ev.callId, { decision: "allow" }); - } - if (ev.kind === "ca_usage_snapshot") console.log(`$${ev.cost_usd} so far`); -} -const final = await handle.result(); // or just `await handle` — same thing - -// ── Multi-turn: each .chat() is a turn over the same session ── -const r1 = await agent.chat([{ role: "user", content: "Build a TODO app" }]); -const r2 = await agent.chat([{ role: "user", content: "Now add tests" }]); - -// ── Streaming input within one turn (advanced — mirrors claude-agent-sdk) ── -async function* userInput() { - yield { role: "user", content: "Start working" }; - await waitForCondition(); - yield { role: "user", content: "Actually, also add docs" }; -} -await agent.chat(userInput()); - -// ── Mid-flight controls ── -await handle.cancel(); -``` - -### `.chat()` returns a `ChatHandle` - -A `ChatHandle` is simultaneously: -- **`AsyncIterable`** — `for await (const ev of handle)` streams events -- **`Promise`** — `await handle` (or `await handle.result()`) resolves to the final `{ result, usage, messages }` -- Has methods: `.cancel()`, `.respondToPermission(callId, decision)`, `.sessionId` getter - -This is the same dual-shape pattern as Anthropic's `client.messages.stream(...)`. - -### Input shapes accepted by `.chat()` - -```ts -agent.chat({ role: "user", content: "..." }) // single Message -agent.chat([msg1, msg2, ...]) // array of Messages -agent.chat(asyncGenerator) // AsyncIterable — streaming input -agent.chat("just a string") // sugar for [{ role: "user", content: "..." }] -``` - -### Design decisions - -- **Constructor = config; `.chat()` = invocation.** Configuration (who, how, where) is bound at agent construction; turns happen via `.chat()`. No `messages` field on the constructor. -- **One agent = one session.** Multiple `.chat()` calls on the same agent share its session. To start a fresh conversation, construct a new agent. (`agent.fork()` later if needed.) -- **Single verb.** `.chat()` replaces the old `.run()` / `.stream()` / `.send()` triplet. The same return value handles both one-shot (`await`) and streaming (`for await`). -- **Messages = Anthropic content-block shape** — same as Claude Agent SDK. Forward verbatim across the wire. -- **Stateless caller, optional pluggable session store.** We don't invent a session protocol — we expose the SDK's `SessionStore` interface verbatim through the `sessionStore` constructor option. -- **Permission enforcement is layered**: SDK config + GAP `compliance.supervision`. Strictest wins. If `agent.yaml` says `human_in_the_loop: always`, the caller cannot override to `auto`. -- **`includePartials: true`** turns on token-by-token deltas via `SDKPartialAssistantMessage`. The harness server flips on `includePartialMessages` upstream; clients see them in the SSE stream. - -### Wire mapping - -| SDK call | HTTP | -|---|---| -| `new ComputerAgent({...})` | No request — constructor only stores config | -| `agent.chat(msgs)` (first call) | `POST /v1/chat` (one-shot transport) — body carries config + messages, response is the SSE stream | -| `agent.chat(msgs)` (subsequent calls — same agent) | `POST /v1/sessions/:id/messages` + reuse open `/events` stream | -| `handle.cancel()` | `POST /v1/sessions/:id/cancel` | -| `handle.respondToPermission(callId, decision)` | `POST /v1/sessions/:id/permission/:callId` | - -The first-call vs subsequent-call distinction is an SDK-internal optimization — users see one method. - -## Substrate Protocol (refined) - -```ts -interface Substrate { - // Process lifecycle for the harness server itself - bootHarness(opts: { - image: string; // "computeragent/harness-claudecode:latest" - envs: Record; - workdir?: string; // GAP repo will be mounted here - }): Promise<{ id: string; baseUrl: string; ready: Promise }>; - - // GAP repo materialization - mountGapRepo(id: string, source: GapSource): Promise<{ path: string; sha: string }>; - - // Execution primitives (used BY the harness inside the runtime, not by the SDK) - exec(opts: ExecOpts): AsyncIterable; - - fs: { - read(path: string): Promise; - write(path: string, data: Buffer | string): Promise; - mkdir(path: string, opts?: { recursive: boolean }): Promise; - rm(path: string, opts?: { recursive: boolean }): Promise; - list(path: string): Promise; - }; - - network: { - portForward(internalPort: number): Promise<{ url: string; close: () => Promise }>; - }; - - shutdown(id: string): Promise; - health(id: string): Promise<"healthy" | "unhealthy" | "unknown">; -} -``` - -`runtime-local` implements with `child_process` + `fs/promises` (port-forward is a no-op — server binds to 127.0.0.1). `runtime-e2b` wraps `@e2b/sdk`'s `Sandbox` API and uses `sandbox.getHost(port)` for port forwarding. - -## Build Order — Three Sequential Wedges - -We don't build everything at once. Each wedge is independently demoable and useful. - -### Wedge 1 — The Standard ("curl can drive an agent") - -**Ships:** `@computeragent/protocol`, `@computeragent/harness-server`, `@computeragent/engine-claude-agent-sdk`, `@computeragent/identity-gitagentprotocol` - -**No client SDK yet, no substrates yet.** Just the server framework + first engine + first loader. - -#### Wedge 1 MVP — what's IN - -The minimum that proves the standard works end-to-end with `curl`: - -- `@computeragent/protocol`: REST + SSE zod schemas, `EngineDriver` + `IdentityLoader` interfaces, `IdentitySource` discriminated union, SDK passthrough re-exports -- `@computeragent/harness-server`: - - `createHarnessServer({ engines, identityLoaders })` factory - - Hono routes for `/v1/chat`, `/v1/sessions`, `/messages`, `/permission/:callId`, `/cancel`, `/events` (SSE), `/fs/*` (workspace filesystem), `/health` - - In-memory `SessionRegistry` with TTL eviction - - Per-session: workdir, user-message queue, abort controller, permission promise map - - Path-jailed workspace FS service (Node `fs/promises` in MVP; same routes will plug into `substrate.fs` port in Wedge 3) - - Capability negotiation (echo plug-in capabilities on `/health` + `ca_session_started`) -- `@computeragent/engine-claude-agent-sdk`: ~150 LOC wrapper around `query()`. `canUseTool` bridge. Forwards `SDKMessage` verbatim. -- `@computeragent/identity-gitagentprotocol` (minimal translation table only): - - Source resolution: `git` (clone via `simple-git`), `local` (copy), `inline` (write to disk) - - GAP → Claude Agent SDK options: `model`, `maxTurns`, stitched `systemPrompt` (SOUL.md + RULES.md + AGENTS.md), `cwd = workdir`, `skills/` symlinked to `.claude/skills/` - - Wraps `@open-gitagent/gapman` for validation -- `@computeragent/testing`: `MockEngine` (deterministic), `MockLoader`, supertest helpers -- One end-to-end demo: `examples/wedge1-curl.sh` runs the full flow against GAP `examples/standard` and asserts a `result` SDK message arrives. - -#### Wedge 1 — what's OUT (deferred to 1.5) - -These are real, intended features but they don't belong on the *first* ship — they would slow down proof of the core architecture: - -- `Last-Event-ID` replay buffer (clients must reconnect-from-scratch in MVP — fine for `curl` demo) -- `AuditSink` strategy + audit-log tee -- `AuthHandler` strategy + bearer/JWT -- Budget enforcement *at the framework level* (Claude SDK already enforces `maxBudgetUsd` internally; framework wrap-and-emit comes later) -- GAP → MCP translation (`tools/*.yaml` → `createSdkMcpServer`) — agents that use only built-in Claude tools (Read, Edit, Bash, …) work without it; GAP `examples/standard` does -- GAP `agents/` recursive sub-agent loading -- GAP `hooks/` translation -- GAP `compliance.supervision.human_in_the_loop` *enforcement* (passed through to caller for now; mandatory enforcement lands with audit) -- Full conformance suite (MVP ships ~6 conformance tests covering the happy path; full suite — ~30 tests, all error paths — lands in 1.5) -- `pino` structured logging (MVP can use a tiny `logger.ts` wrapper that no-ops in tests) - -This is not corner-cutting. It's discipline: a smaller surface ships faster, gets feedback faster, and forces us to confront whether the core abstractions are right *before* we commit to features that depend on them. - -#### Wedge 1 build sequence (the order we actually write code) - -Each step ends with something runnable. Don't proceed to step N+1 until N green. - -1. **Workspace skeleton.** pnpm workspace, turbo, tsconfig.base.json, `.gitignore`, README. `pnpm install` clean. -2. **`@computeragent/protocol`.** Zod schemas + interfaces only. No runtime logic. `pnpm --filter protocol build` and `test` green. ≤ 6 files. -3. **`@computeragent/testing`** (`MockEngine` + `MockLoader` only — no conformance suite yet). Trivial deterministic implementations. Used by harness-server tests next. ≤ 3 files. -4. **`@computeragent/harness-server` skeleton.** `createHarnessServer` factory + `/health` route only. Test: GET /health returns capabilities of registered MockEngine. -5. **Sessions + events.** Add `POST /v1/sessions`, `GET /v1/sessions/:id/events` (SSE). Extract `services/create-session.ts` and `services/run-session.ts` from the start so step 6 can reuse them. Test: create a session backed by MockEngine, open events, see `ca_session_started` + mock events flow. -6. **`POST /v1/chat`** (one-shot convenience). Reuses `create-session` + `run-session` services from step 5; the route handler is ~40 LOC. Test: single POST returns SSE stream, first event includes `sessionId`, MockEngine result reaches client, session is auto-deleted on stream end. -7. **Messages + cancel.** Add `POST /messages`, `POST /cancel`. Test: messages reach MockEngine's user-message queue; cancel ends the stream with `ca_session_ended`. -8. **Permissions.** Add `POST /permission/:callId`. Test: MockEngine emits permission-needed; SSE event arrives; POST decision; engine resumes. Verify works through both `/v1/chat` and the session API. -9. **Filesystem API.** Build `path-jail.ts` first (with hostile-input tests: `..`, `/etc`, symlink-out, null bytes), then `services/workspace-fs.ts`, then `routes/fs.ts`. Tests: tree of a fixture workdir; read/write round-trip; edit string-replace; delete; mkdir; move; every path-traversal vector rejected. -10. **`@computeragent/engine-claude-agent-sdk`.** Real engine. Mock `query()` in tests. ≤ 4 files. -11. **`@computeragent/identity-gitagentprotocol`.** Source resolver + minimal translator. Snapshot tests against checked-in fixture of GAP `examples/minimal` and `examples/standard`. ≤ 6 files. -12. **`examples/wedge1-server.ts`** + **`examples/wedge1-curl.sh`.** The curl demo uses `POST /v1/chat` for the one-liner. A second `examples/wedge1-session-curl.sh` demonstrates the session API. A third `examples/wedge1-fs-tour.sh` runs an agent and then `curl`s the FS endpoints to read what it produced. Both run against real Anthropic API gated by `ANTHROPIC_API_KEY`. - -**Demo:** -```bash -# Terminal 1 -$ pnpm exec harness-server # boots on :7700 -listening on http://127.0.0.1:7700 - -# Terminal 2 -$ curl -X POST http://127.0.0.1:7700/v1/sessions \ - -H "Content-Type: application/json" \ - -d '{ - "engine": "claude-agent-sdk", - "identity": { - "loader": "gitagentprotocol", - "source": { "type": "git", "url": "github.com/open-gitagent/gitagent-protocol/examples/standard" } - }, - "envs": { "ANTHROPIC_API_KEY": "sk-..." }, - "options": { "maxTurns": 20 } - }' -{"sessionId":"sess_01","engine":"claude-agent-sdk","gap":{"name":"code-reviewer",...}, - "eventsUrl":"/v1/sessions/sess_01/events"} - -# Terminal 3 — open the SSE stream -$ curl -N http://127.0.0.1:7700/v1/sessions/sess_01/events -event: ca_session_started -id: 0 -data: {"sessionId":"sess_01",...} - -# Terminal 2 — push a user message -$ curl -X POST http://127.0.0.1:7700/v1/sessions/sess_01/messages \ - -d '{"message":{"role":"user","content":"Review the README for inconsistencies"}}' -{"ack":true} - -# Terminal 3 receives: -event: sdk_message -id: 1 -data: {"type":"system","subtype":"init",...} - -event: sdk_message -id: 2 -data: {"type":"assistant","message":{"content":[{"type":"text","text":"I'll review..."}],...},...} - -event: sdk_message -id: 3 -data: {"type":"assistant","message":{"content":[{"type":"tool_use","name":"Read","input":{"file_path":"README.md"}}],...},...} -... (full agentic loop streams through) ... - -event: sdk_message -id: N -data: {"type":"result","subtype":"success","result":"...","total_cost_usd":0.04,...} - -event: ca_session_ended -data: {"reason":"complete"} -``` - -**Why this is the right first wedge:** the standard exists and is proveably useful with `curl`. Anyone — Python, Go, Rust, browser, Postman — can drive an agent over a stable HTTP+SSE contract. The client SDK is just sugar. - -**Conformance suite ships with this wedge** so future engine and loader authors validate compliance from day one. - -### Wedge 2 — The Client ("ergonomic TypeScript") - -**Ships:** `@computeragent/sdk`, `@computeragent/cli` - -**Adds:** -```ts -const agent = new ComputerAgent({ - source: "github.com/open-gitagent/gitagent-protocol/examples/standard", - harness: "claude-agent-sdk", - runtime: "local", // implicit: localhost:7700 - envs: { ANTHROPIC_API_KEY: "..." }, - messages: [{ role: "user", content: "Review the README for inconsistencies" }], -}); - -for await (const ev of agent.stream()) { /* … */ } -``` - -CLI: -```bash -computeragent run github.com/.../examples/standard --message "Review the README" -``` - -**Demo proves:** equivalent UX to wedge 1 but ergonomic, typed, and with `Last-Event-ID` resume on connection drops. - -### Wedge 3 — The Substrates ("run anywhere") - -**Ships:** `@computeragent/runtime-local`, `@computeragent/runtime-e2b` - -**Adds:** the harness server can run *outside* the user's process — inside an E2B sandbox or subprocess pool. The Substrate Protocol is implemented per runtime. - -**Demo:** -```bash -$ computeragent run --runtime local --message "..." -$ computeragent run --runtime e2b --message "..." -``` -Same streamed output. Substrate swap is a string change. - -**This is where the 3-axis decomposition pays off in practice.** - -### Out of scope until later - -- `engine-gitclaw`, `engine-codex`, `engine-opencode`, `engine-gemini-cli` — ecosystem builds these -- `runtime-lyzrcompute`, `runtime-openshell` — wedge 4 -- `identity-openai-assistants`, `identity-crewai` — ecosystem -- Multi-tenant server mode, A2A, audit-log persistence, billing — post-v1.0 - -## Roadmap - -| Phase | Adds | Why | -|---|---|---| -| **v0.1** (Wedge 1 MVP) | protocol + harness-server (core) + engine-claude-agent-sdk + identity-gitagentprotocol (minimal translation) + MockEngine/MockLoader | The standard exists; curl drives an agent end-to-end | -| **v0.1.5** (Wedge 1.5) | `Last-Event-ID` replay buffer, `AuditSink`, `AuthHandler`, full conformance suite, GAP→MCP tool translation, `pino` logging, GAP sub-agents + hooks | Production-ready standard; conformance gates third-party plug-ins | -| **v0.2** (Wedge 2) | sdk (client) + cli | Ergonomic TS surface on top of the standard | -| **v0.3** (Wedge 3) | runtime-local + runtime-e2b | 3-axis decomposition demonstrated; same call shape, swappable substrate | -| **v0.4** | engine-gitclaw, full HITL UX (CLI prompt + webhook), `FileSessionStore` | Validate engine pluggability with your 2nd harness; unblock regulated agents | -| **v0.5** | runtime-lyzrcompute, `ca_usage_snapshot` cost telemetry, GAP `compliance.recordkeeping` enforcement | Lyzr-native deployment; compliance teeth | -| **v0.6** | runtime-openshell (OSS reference substrate), Python client SDK (sibling, shared protocol) | Open-source the runtime; second-language client | -| **v0.7** | `computeragent serve` (multi-tenant gateway), session attach/detach, A2A adapter | Production deployments, agent-to-agent calls | -| **v1.0** | Stable Harness Protocol + Substrate Protocol RFC, third-party engine/identity/runtime ecosystem, `computeragent` registry | The OCI moment | - -## Critical Reused Building Blocks - -- **`@anthropic-ai/claude-agent-sdk`** — entire `claudecode` harness is built on `query()`, `tool()`, `createSdkMcpServer()`. Don't reinvent. -- **`@open-gitagent/gapman`** — validation, GAP loading, dependency resolution. Wrap as library. -- **`@e2b/sdk`** — substrate primitives for E2B. Don't reinvent sandboxing. -- **`hono` + `hono/streaming`** — small, fast HTTP + SSE for harness servers; runs on Node, Bun, Deno, Cloudflare. -- **`zod`** — runtime-validated protocol messages; emits JSON Schema for `@computeragent/protocol`. -- **`undici` `EventSource`** (or `eventsource` polyfill) — SSE client in `@computeragent/sdk` with `Last-Event-ID`. - -## File / Module Skeleton (Wedge 1 MVP — what we build first) - -Each file ≤ 250 LOC. If a file approaches the cap during build, split before merging. - -``` -/Users/zeus/ComputerAgent/ -├── package.json # pnpm workspace root -├── pnpm-workspace.yaml -├── turbo.json -├── tsconfig.base.json -├── PLAN.md # this plan, copied -├── README.md -├── packages/ -│ ├── protocol/ # types + zod schemas only; no runtime logic -│ │ ├── src/harness-rest.ts # zod schemas for REST request/response bodies -│ │ ├── src/sse-events.ts # SSE event union (sdk_message + ca_*) -│ │ ├── src/contracts.ts # EngineDriver, IdentityLoader interfaces -│ │ ├── src/identity-source.ts # IdentitySource discriminated union -│ │ ├── src/sdk-passthrough.ts # re-export SDKMessage from claude-agent-sdk -│ │ └── src/index.ts # public surface only -│ ├── harness-server/ -│ │ ├── src/app.ts # createHarnessServer factory -│ │ ├── src/routes/chat.ts # POST /v1/chat (one-shot; SSE response) -│ │ ├── src/routes/sessions.ts # POST + DELETE /v1/sessions -│ │ ├── src/routes/messages.ts # POST /v1/sessions/:id/messages -│ │ ├── src/routes/permission.ts # POST /v1/sessions/:id/permission/:callId -│ │ ├── src/routes/cancel.ts # POST /v1/sessions/:id/cancel -│ │ ├── src/routes/events.ts # GET /v1/sessions/:id/events (SSE) -│ │ ├── src/routes/fs.ts # FS routes (tree, file GET/PUT, edit, delete, mkdir, move) -│ │ ├── src/routes/health.ts # GET /v1/health -│ │ ├── src/services/create-session.ts # shared by /chat and /sessions — single source of truth -│ │ ├── src/services/run-session.ts # drives the engine; owned by /chat (auto) and /sessions (on-demand) -│ │ ├── src/services/workspace-fs.ts # jailed FS ops (Wedge 1: Node fs; Wedge 3: substrate.fs port) -│ │ ├── src/path-jail.ts # path validation: rejects ..; absolute; outside-workdir symlinks -│ │ ├── src/session.ts # Session class — queue, abort, permission map -│ │ ├── src/registry.ts # SessionRegistry — in-memory map + TTL -│ │ ├── src/sse-encoder.ts # pure SSE serialization (Wedge 1.5: + replay) -│ │ ├── src/error-mapper.ts # one middleware that maps throws → HTTP responses -│ │ └── src/index.ts -│ ├── engine-claude-agent-sdk/ -│ │ ├── src/engine.ts # ClaudeAgentEngine implements EngineDriver -│ │ ├── src/permission-bridge.ts # canUseTool ↔ ctx.onPermissionRequest -│ │ └── src/index.ts -│ ├── identity-gitagentprotocol/ -│ │ ├── src/loader.ts # GitAgentProtocolLoader implements IdentityLoader -│ │ ├── src/source-resolver.ts # git | local | inline → workdir -│ │ ├── src/adapters/claude-agent-sdk.ts # gapToClaudeAgentOptions (pure) -│ │ ├── src/skills.ts # skills/ → .claude/skills/ symlink (pure-ish) -│ │ └── src/index.ts -│ └── testing/ -│ ├── src/mock-engine.ts # deterministic EngineDriver -│ ├── src/mock-loader.ts # deterministic IdentityLoader -│ ├── src/supertest-helpers.ts # SSE-aware request helpers -│ └── src/index.ts -├── examples/ -│ ├── wedge1-server.ts # boot script: registers real engine + loader -│ └── wedge1-curl.sh # the curl demo from the plan -└── docs/ - └── protocol.md # human-readable protocol spec (full doc + author guides land in 1.5) -``` - -**Deferred to Wedge 1.5:** `replay-buffer.ts`, `audit.ts`, `auth.ts` in harness-server; `tools.ts` (GAP→MCP) in identity-gitagentprotocol; full conformance suite in testing; engine/identity author guides. - -**Deferred to Wedge 2/3:** Tier 3 packages (sdk + cli + runtime-local + runtime-e2b). - -## Verification Plan - -### Wedge 1 (the standard) - -**Unit:** -- `protocol` — zod round-trip on every REST body and SSE event; type-narrowing tests on the event union -- `harness-server` — supertest-driven end-to-end against a `MockEngine` + `MockLoader` from `@computeragent/testing`: - - POST `/v1/sessions` → 201 with sessionId - - POST `/v1/sessions/:id/messages` while SSE stream open → message appears in event stream - - GET `/v1/sessions/:id/events` with `Last-Event-ID: 5` → resumes from event 6 - - POST `/v1/sessions/:id/cancel` → SSE stream closes with `ca_session_ended { reason: "cancelled" }` - - Permission flow: mock engine emits permission request → `ca_permission_request` SSE event → POST `/permission/:callId` → engine resumes - - Replay buffer eviction past TTL -- `engine-claude-agent-sdk` — mock the SDK's `query()` (substitute an iterable that yields known SDKMessages); verify SDKMessage → EngineEvent forwarding and permission round-trip -- `identity-gitagentprotocol` — fixture tests against GAP `examples/minimal`, `standard`, `full`. Snapshot the resulting `ClaudeAgentOptions`. Verify skills/tools/agents/compliance translation. - -**Integration:** -1. `pnpm exec harness-server` boots locally with `engine-claude-agent-sdk` + `identity-gitagentprotocol` registered -2. `examples/wedge1-curl.sh` runs the full curl flow against GAP `examples/standard`, asserts a `result` event arrives with non-empty `result` text -3. CI runs against real Anthropic API gated by `ANTHROPIC_API_KEY` - -**Conformance suite (the publishable artifact):** -- `@computeragent/testing` ships a black-box suite that drives an arbitrary harness server through the protocol — every endpoint, edge case, error path -- Wedge-1 ships pass-on-day-one against this suite -- Future engine/loader contributors run the same suite locally before opening a PR -- The suite + protocol spec doc is what makes this a *standard* and not just our codebase - -### Wedge 2 (the client) - -**Unit:** -- `sdk` — mock SSE server; assert `agent.stream()` decodes the union correctly; assert dropped-connection + `Last-Event-ID` resume actually replays missed events - -**Integration:** -- `pnpm dev` boots harness-server + uses `@computeragent/sdk` against it -- TS demo script produces identical outcome to the curl demo from Wedge 1 - -### Wedge 3 (the substrates) - -**Unit:** -- `runtime-local` — exec/fs fixture tests; subprocess lifecycle on SIGINT -- `runtime-e2b` — mock E2B client; live integration test gated by `E2B_API_KEY` - -**Integration:** -1. `examples/wedge3-local.ts` and `examples/wedge3-e2b.ts` run the same GAP repo through both substrates -2. Output messages structurally identical (modulo timing and uuids) -3. CI matrix `{ runtime: local, e2b } × { engine: claude-agent-sdk }` — same expected `result` content - -## Risks / Open Questions (defer until after wedge) - -1. **`SettingSources` interaction.** Claude Code reads `CLAUDE.md`, `.claude/`, user/global settings. We must pin `settingSources: ["project"]` inside the substrate to avoid leaking the host machine's Claude config. Verify this works in E2B's container. -2. **Skills discovery path collisions.** GAP's `skills//SKILL.md` and Claude Code's `.claude/skills//SKILL.md` use the same Agent Skills standard, so a symlink is sufficient. Verify the SDK respects them. -3. **MCP server lifetime.** GAP `tools/` → in-process MCP via `createSdkMcpServer`. They live for the duration of `query()`. If the user calls `agent.send()` for a 2nd turn, we keep the same server. Confirmed by streaming-input mode. -4. **`canUseTool` + `permissionMode`.** When mode is `bypassPermissions`, `canUseTool` is *not* invoked. We must enforce GAP compliance at the harness level — if `human_in_the_loop: always`, override caller's mode to `default` regardless of what they passed. -5. **GAP repo caching.** Content-addressed cache keyed by commit SHA. Live next to `.gitagent/` runtime state. -6. **Secret injection.** Short-lived envs vs. mounted secret files vs. metadata service per substrate. v0 uses envs; v0.3 adds optional secret-mount when `gap.compliance.data_governance.pii_handling: redact|encrypt`. -7. **Long-running session reattach.** Does `sessionId` map to a live container, or always rehydrate from event log + SDK `resume`? v0: rehydrate via SDK `resume`. v0.5: live attach to running containers. -8. **Cost telemetry standard.** SDK gives token counts and `total_cost_usd`. Substrate compute-seconds need a unified `ca_usage_snapshot.compute_seconds_substrate`. -9. **Tool-call permission UX in CLI mode.** TTY prompt vs. webhook vs. fail-closed. v0.2 ships TTY; webhook later. -10. **Multi-harness session portability.** Out of scope for v1.0. Sessions are bound to a harness because the SDK's session format is harness-specific. diff --git a/agentos/package.json b/agentos/package.json index fc74d65..585e8a6 100644 --- a/agentos/package.json +++ b/agentos/package.json @@ -9,8 +9,35 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^5.4.0", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^1.17.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-hook-form": "^7.76.1", + "react-resizable-panels": "^4.11.2", + "recharts": "^3.8.1", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^4.4.3" }, "devDependencies": { "@types/react": "^18.3.12", diff --git a/agentos/pnpm-lock.yaml b/agentos/pnpm-lock.yaml index 0552e90..e0db2bd 100644 --- a/agentos/pnpm-lock.yaml +++ b/agentos/pnpm-lock.yaml @@ -8,12 +8,93 @@ importers: .: dependencies: + '@hookform/resolvers': + specifier: ^5.4.0 + version: 5.4.0(react-hook-form@7.76.1(react@18.3.1)) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': + specifier: ^2.1.8 + version: 2.1.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lucide-react: + specifier: ^1.17.0 + version: 1.17.0(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-hook-form: + specifier: ^7.76.1 + version: 7.76.1(react@18.3.1) + react-resizable-panels: + specifier: ^4.11.2 + version: 4.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.6)(react@18.3.1)(redux@5.0.1) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + zod: + specifier: ^4.4.3 + version: 4.4.3 devDependencies: '@types/react': specifier: ^18.3.12 @@ -46,87 +127,87 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.3': - resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} '@esbuild/aix-ppc64@0.25.12': @@ -285,6 +366,26 @@ packages: cpu: [x64] os: [win32] + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@hookform/resolvers@5.4.0': + resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==} + peerDependencies: + react-hook-form: ^7.55.0 + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -313,6 +414,499 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -441,6 +1035,12 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -453,6 +1053,33 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -467,6 +1094,9 @@ packages: '@types/react@18.3.29': resolution: {integrity: sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -483,6 +1113,10 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + autoprefixer@10.5.0: resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} engines: {node: ^10 || ^12 || >=14} @@ -519,6 +1153,19 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -534,6 +1181,50 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -543,19 +1234,28 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - electron-to-chromium@1.5.361: - resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} + electron-to-chromium@1.5.363: + resolution: {integrity: sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==} es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-toolkit@1.47.0: + resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -565,6 +1265,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -600,6 +1303,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -612,6 +1319,16 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -663,6 +1380,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@1.17.0: + resolution: {integrity: sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -775,10 +1497,67 @@ packages: peerDependencies: react: ^18.3.1 + react-hook-form@7.76.1: + resolution: {integrity: sha512-rYM7tPiWlu3nZchkR/ex7piyzui2vFPyaLnXnI/RnblB/L4qfMmyses8llJVtF1NpE9WBBsJlGtcSZzPCXW1qQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-resizable-panels@4.11.2: + resolution: {integrity: sha512-+kfFbDZ8mygc7g0vxOcDzCVGuwiIUOnILqPoUHo6/uP+Mmyx6HzZU+kj1aOPDlktXuobYbr6BtQekvJwHRX4Eg==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -790,6 +1569,25 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -814,6 +1612,12 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -827,6 +1631,14 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.19: resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} engines: {node: '>=14.0.0'} @@ -839,6 +1651,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} @@ -850,6 +1665,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -861,9 +1679,37 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vite@6.4.2: resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -907,29 +1753,32 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + snapshots: '@alloc/quick-lru@5.2.0': {} - '@babel/code-frame@7.29.0': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.3': {} + '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.0': + '@babel/core@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -939,89 +1788,89 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.1': + '@babel/generator@7.29.7': dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.28.6': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/compat-data': 7.29.3 - '@babel/helper-validator-option': 7.27.1 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} - '@babel/helpers@7.29.2': + '@babel/helpers@7.29.7': dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 - '@babel/parser@7.29.3': + '@babel/parser@7.29.7': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/template@7.28.6': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - '@babel/traverse@7.29.0': + '@babel/traverse@7.29.7': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.29.0': + '@babel/types@7.29.7': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@esbuild/aix-ppc64@0.25.12': optional: true @@ -1062,75 +1911,598 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.25.12': - optional: true + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.11': {} + + '@hookform/resolvers@5.4.0(react-hook-form@7.76.1(react@18.3.1))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.76.1(react@18.3.1) + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@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 + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.29)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 + + '@radix-ui/react-context@1.1.2(@types/react@18.3.29)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.29)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.29)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.29)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-id@1.1.1(@types/react@18.3.29)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 + + '@radix-ui/react-label@2.1.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.29)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.29)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/linux-riscv64@0.25.12': - optional: true + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/linux-s390x@0.25.12': - optional: true + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/linux-x64@0.25.12': - optional: true + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.29)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/netbsd-arm64@0.25.12': - optional: true + '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/netbsd-x64@0.25.12': - optional: true + '@radix-ui/react-slot@1.2.3(@types/react@18.3.29)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@esbuild/openbsd-arm64@0.25.12': - optional: true + '@radix-ui/react-slot@1.2.4(@types/react@18.3.29)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@esbuild/openbsd-x64@0.25.12': - optional: true + '@radix-ui/react-switch@1.2.6(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/openharmony-arm64@0.25.12': - optional: true + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/sunos-x64@0.25.12': - optional: true + '@radix-ui/react-toggle@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@esbuild/win32-arm64@0.25.12': - optional: true + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.29)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@esbuild/win32-ia32@0.25.12': - optional: true + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.29)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@esbuild/win32-x64@0.25.12': - optional: true + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.29)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@jridgewell/gen-mapping@0.3.13': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.29)(react@18.3.1)': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@jridgewell/remapping@2.3.5': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.29)(react@18.3.1)': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@jridgewell/resolve-uri@3.1.2': {} + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.29)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@jridgewell/sourcemap-codec@1.5.5': {} + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.29)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@jridgewell/trace-mapping@0.3.31': + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.29)(react@18.3.1)': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.29 - '@nodelib/fs.scandir@2.1.5': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@nodelib/fs.stat@2.0.5': {} + '@radix-ui/rect@1.1.1': {} - '@nodelib/fs.walk@1.2.8': + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@18.3.29)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.8 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 18.3.1 + react-redux: 9.3.0(@types/react@18.3.29)(react@18.3.1)(redux@5.0.1) '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -1209,26 +2581,54 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.4': optional: true + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} '@types/estree@1.0.8': {} @@ -1243,11 +2643,13 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/use-sync-external-store@0.0.6': {} + '@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@1.21.7))': dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 @@ -1264,6 +2666,10 @@ snapshots: arg@5.0.2: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + autoprefixer@10.5.0(postcss@8.5.15): dependencies: browserslist: 4.28.2 @@ -1285,7 +2691,7 @@ snapshots: dependencies: baseline-browser-mapping: 2.10.32 caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.361 + electron-to-chromium: 1.5.363 node-releases: 2.0.46 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -1305,6 +2711,24 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + commander@4.1.1: {} convert-source-map@2.0.0: {} @@ -1313,18 +2737,62 @@ snapshots: csstype@3.2.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + + detect-node-es@1.1.0: {} + didyoumean@1.2.2: {} dlv@1.1.3: {} - electron-to-chromium@1.5.361: {} + electron-to-chromium@1.5.363: {} es-errors@1.3.0: {} + es-toolkit@1.47.0: {} + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -1356,6 +2824,8 @@ snapshots: escalade@3.2.0: {} + eventemitter3@5.0.4: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1385,6 +2855,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-nonce@1.0.1: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -1397,6 +2869,12 @@ snapshots: dependencies: function-bind: 1.1.2 + immer@10.2.0: {} + + immer@11.1.8: {} + + internmap@2.0.3: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -1433,6 +2911,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@1.17.0(react@18.3.1): + dependencies: + react: 18.3.1 + merge2@1.4.1: {} micromatch@4.0.8: @@ -1515,8 +2997,55 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-hook-form@7.76.1(react@18.3.1): + dependencies: + react: 18.3.1 + + react-is@19.2.6: {} + + react-redux@9.3.0(@types/react@18.3.29)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + redux: 5.0.1 + react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@18.3.29)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.29)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.29 + + react-remove-scroll@2.7.2(@types/react@18.3.29)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.29)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.29)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.29)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.29)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.29 + + react-resizable-panels@4.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-style-singleton@2.2.3(@types/react@18.3.29)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.29 + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -1529,6 +3058,34 @@ snapshots: dependencies: picomatch: 2.3.2 + recharts@3.8.1(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react-is@19.2.6)(react@18.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@18.3.29)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.47.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 19.2.6 + react-redux: 9.3.0(@types/react@18.3.29)(react@18.3.1)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@18.3.1) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reselect@5.1.1: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -1579,6 +3136,11 @@ snapshots: semver@6.3.1: {} + sonner@2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + source-map-js@1.2.1: {} sucrase@3.35.1: @@ -1593,6 +3155,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@3.6.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + tailwindcss@3.4.19: dependencies: '@alloc/quick-lru': 5.2.0 @@ -1629,6 +3197,8 @@ snapshots: dependencies: any-promise: 1.3.0 + tiny-invariant@1.3.3: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -1640,6 +3210,8 @@ snapshots: ts-interface-checker@0.1.13: {} + tslib@2.8.1: {} + typescript@5.9.3: {} update-browserslist-db@1.2.3(browserslist@4.28.2): @@ -1648,8 +3220,44 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + use-callback-ref@1.3.3(@types/react@18.3.29)(react@18.3.1): + dependencies: + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.29 + + use-sidecar@1.1.3(@types/react@18.3.29)(react@18.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.29 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite@6.4.2(jiti@1.21.7): dependencies: esbuild: 0.25.12 @@ -1663,3 +3271,5 @@ snapshots: jiti: 1.21.7 yallist@3.1.1: {} + + zod@4.4.3: {} diff --git a/agentos/src/App.tsx b/agentos/src/App.tsx index 862ebd5..73bccb1 100644 --- a/agentos/src/App.tsx +++ b/agentos/src/App.tsx @@ -1,4 +1,15 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { + Home as HomeIcon, + Activity, + Shield, + ChevronRight, + ChevronDown, + Folder, + FolderOpen, + Search, + X, +} from "lucide-react"; import { api, type Agent } from "./api.ts"; import { LogsTab } from "./components/LogsTab.tsx"; import { WorkspaceTab } from "./components/WorkspaceTab.tsx"; @@ -6,9 +17,21 @@ import { SchedulesTab } from "./components/SchedulesTab.tsx"; import { HomePage } from "./components/HomePage.tsx"; import { PolicyTab } from "./components/PolicyTab.tsx"; import { PoliciesPage } from "./components/PoliciesPage.tsx"; +import { ObservabilityTab } from "./components/observability/ObservabilityTab.tsx"; +import { AgentCard } from "./components/AgentCard.tsx"; +import { RegisterAgentForm } from "./components/RegisterAgentForm.tsx"; +import { Tabs, TabsList, TabsTrigger } from "./components/ui/tabs.tsx"; +import { Badge } from "./components/ui/badge.tsx"; +import { ScrollArea } from "./components/ui/scroll-area.tsx"; +import { Skeleton } from "./components/ui/skeleton.tsx"; +import { Separator } from "./components/ui/separator.tsx"; +import { Input } from "./components/ui/input.tsx"; +import { StatusDot } from "./components/composite/StatusDot.tsx"; +import { PageHeader } from "./components/composite/PageHeader.tsx"; +import { cn } from "./lib/cn.ts"; type Tab = "chat" | "schedules" | "policy" | "logs"; -type View = "home" | "dashboard" | "policies"; +type View = "home" | "observability" | "policies" | "dashboard"; function timeAgo(iso: string | null): string { if (!iso) return "never"; @@ -19,7 +42,6 @@ function timeAgo(iso: string | null): string { return `${Math.floor(s / 86400)}d ago`; } -// The agent's name comes from its repo — "…/general-agent" → "General Agent". const NAME_OVERRIDES: Record = { "general-agent": "General Agent", "agentos-builder": "AgentOS Builder", @@ -31,7 +53,6 @@ function agentNameFromSource(source: string): string { return NAME_OVERRIDES[slug] ?? slug.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } -// Logo for an agent type (harness). function typeLogo(harness: string): string | null { if (harness === "gitagent") return "/logos/gitagent.png"; if (harness === "claude-agent-sdk") return "/logos/claude.svg"; @@ -39,18 +60,29 @@ function typeLogo(harness: string): string | null { return null; } -// Type pill: small logo + label (GitAgent / Claude Code / Deep Agent). +function SectionLabel({ name, count }: { name: string; count: number }) { + return ( +
+ + {name} + + + {count} +
+ ); +} + function TypeBadge({ agent, className = "" }: { agent: Agent; className?: string }) { const logo = typeLogo(agent.harness); return ( - + {logo && ( - + )} - {agent.label} - + {agent.label} + ); } @@ -62,6 +94,26 @@ export default function App() { const [err, setErr] = useState(null); const [launchMessage, setLaunchMessage] = useState(null); const [agentsOpen, setAgentsOpen] = useState(true); + const [search, setSearch] = useState(""); + + const filteredAgents = useMemo(() => { + if (!search.trim()) return agents; + const q = search.trim().toLowerCase(); + return agents.filter((a) => + a.name.toLowerCase().includes(q) || + (a.label ?? "").toLowerCase().includes(q) || + (a.sourceUrl ?? "").toLowerCase().includes(q) || + a.harness.toLowerCase().includes(q), + ); + }, [agents, search]); + + // Group by origin so Hosted (server-config'd) and Library (registry, + // dashboard- or SDK-registered) are visually separated. + const grouped = useMemo(() => { + const hosted = filteredAgents.filter((a) => (a.origin ?? "in-memory") === "in-memory"); + const library = filteredAgents.filter((a) => a.origin === "registry"); + return { hosted, library }; + }, [filteredAgents]); useEffect(() => { api.agents().then(setAgents).catch((e) => setErr(String(e))); @@ -69,10 +121,12 @@ export default function App() { const agent = agents.find((a) => a.name === selected) ?? null; - // Clicking an agent defaults to the Chat workspace (session list + chat). - const openAgent = (name: string) => { setSelected(name); setTab("chat"); setView("dashboard"); }; + const openAgent = (name: string) => { + setSelected(name); + setTab("chat"); + setView("dashboard"); + }; - // From Home: open the agent (type) and auto-send the prompt. const launchFromHome = (agentName: string, message: string) => { setSelected(agentName); setTab("chat"); @@ -81,76 +135,132 @@ export default function App() { }; return ( -
+
{/* Left rail */} - {/* Main */} @@ -159,33 +269,36 @@ export default function App() { ) : view === "home" ? ( agents[0] && openAgent(agents[0].name)} /> + ) : view === "observability" ? ( + ) : agent ? ( <> -
-
-
- {agentNameFromSource(agent.source)} + + {agentNameFromSource(agent.sourceUrl ?? "")} -
-
+ + } + description={ + <> {agent.harness} · {agent.model ?? "default model"} - {!agent.sandboxCapable && one-shot · no memory across turns} -
-
- -
+ {!agent.sandboxCapable && ( + one-shot · no memory across turns + )} + + } + actions={ + setTab(v as Tab)}> + + Chat + Schedules + Policy + Logs + + + } + />
{tab === "chat" && ( ) : ( -
- {err ? {err} : "Select an agent"} +
+ {err ? {err} : "Select an agent"}
)}
); } + +function RailButton({ + icon: Icon, + label, + active, + onClick, +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + active: boolean; + onClick: () => void; +}) { + return ( + + ); +} diff --git a/agentos/src/api.test.ts b/agentos/src/api.test.ts new file mode 100644 index 0000000..57d450b --- /dev/null +++ b/agentos/src/api.test.ts @@ -0,0 +1,197 @@ +/** + * Pure-function unit tests for `displaySource`. No DOM, no fetch, no React. + * + * displaySource is the load-bearing piece behind in the agent + * rail: it turns the variable-shaped `agent.source` (git/local/inline object + * OR a legacy bare string) into a render-ready `{kind, primary, secondary, + * href?}` triple. Wrong output here = the dashboard renders the wrong title + * or links to the wrong repo, so we exercise every recognized shape + every + * fallback branch. + */ +import { describe, expect, it } from "vitest"; +import { displaySource } from "./api.js"; + +describe("displaySource", () => { + describe("null / undefined input", () => { + it("returns the (no source) unknown sentinel for undefined", () => { + expect(displaySource(undefined)).toEqual({ + kind: "unknown", + primary: "(no source)", + secondary: "", + }); + }); + it("returns the (no source) unknown sentinel for null", () => { + expect(displaySource(null)).toEqual({ + kind: "unknown", + primary: "(no source)", + secondary: "", + }); + }); + it("returns the (no source) unknown sentinel for empty string", () => { + expect(displaySource("")).toEqual({ + kind: "unknown", + primary: "(no source)", + secondary: "", + }); + }); + }); + + describe("structured local source", () => { + it("uses the last two path segments as the primary label", () => { + expect(displaySource({ type: "local", path: "/Users/zeus/repos/devsupport-agent" })).toEqual({ + kind: "local", + primary: "repos/devsupport-agent", + secondary: "/Users/zeus/repos/devsupport-agent", + }); + }); + it("falls back to the full path when there's only one segment", () => { + expect(displaySource({ type: "local", path: "/agent" })).toEqual({ + kind: "local", + primary: "agent", + secondary: "/agent", + }); + }); + it("handles a trailing slash without producing an empty primary", () => { + const out = displaySource({ type: "local", path: "/Users/zeus/x/" }); + expect(out.kind).toBe("local"); + expect(out.primary.length).toBeGreaterThan(0); + }); + }); + + describe("structured inline source", () => { + it("uses manifest.name when present", () => { + expect( + displaySource({ type: "inline", manifest: { name: "ad-hoc-bot" } }), + ).toEqual({ kind: "inline", primary: "ad-hoc-bot", secondary: "inline manifest" }); + }); + it("falls back to 'inline' when manifest has no name", () => { + expect(displaySource({ type: "inline", manifest: { spec_version: "0.1.0" } })).toEqual({ + kind: "inline", + primary: "inline", + secondary: "inline manifest", + }); + }); + it("falls back to 'inline' when manifest.name is not a string", () => { + expect( + displaySource({ + type: "inline", + manifest: { name: 42 as unknown as string }, + }), + ).toEqual({ kind: "inline", primary: "inline", secondary: "inline manifest" }); + }); + }); + + describe("structured git source", () => { + it("parses an https URL with .git suffix into owner/repo + host + href", () => { + expect( + displaySource({ + type: "git", + url: "https://github.com/open-gitagent/ComputerAgent.git", + }), + ).toEqual({ + kind: "git", + primary: "open-gitagent/ComputerAgent", + secondary: "github.com", + href: "https://github.com/open-gitagent/ComputerAgent", + }); + }); + it("parses an ssh git@ URL into owner/repo + host + https href", () => { + expect( + displaySource({ type: "git", url: "git@github.com:open-gitagent/opengap.git" }), + ).toEqual({ + kind: "git", + primary: "open-gitagent/opengap", + secondary: "github.com", + href: "https://github.com/open-gitagent/opengap", + }); + }); + it("appends /tree/ to the href when ref is provided", () => { + expect( + displaySource({ + type: "git", + url: "https://github.com/open-gitagent/ComputerAgent", + ref: "main", + }), + ).toEqual({ + kind: "git", + primary: "open-gitagent/ComputerAgent", + secondary: "github.com", + href: "https://github.com/open-gitagent/ComputerAgent/tree/main", + }); + }); + it("url-encodes a ref containing a slash", () => { + const out = displaySource({ + type: "git", + url: "https://github.com/o/r", + ref: "feat/abc", + }); + expect(out.href).toBe("https://github.com/o/r/tree/feat%2Fabc"); + }); + it("recognizes gitlab.com hosts", () => { + expect( + displaySource({ type: "git", url: "https://gitlab.com/my-org/my-repo" }), + ).toEqual({ + kind: "git", + primary: "my-org/my-repo", + secondary: "gitlab.com", + href: "https://gitlab.com/my-org/my-repo", + }); + }); + it("recognizes bitbucket.org hosts", () => { + expect( + displaySource({ type: "git", url: "https://bitbucket.org/team/proj.git" }), + ).toEqual({ + kind: "git", + primary: "team/proj", + secondary: "bitbucket.org", + href: "https://bitbucket.org/team/proj", + }); + }); + it("parses a scheme-less host/owner/repo", () => { + expect(displaySource({ type: "git", url: "github.com/o/r" })).toEqual({ + kind: "git", + primary: "o/r", + secondary: "github.com", + href: "https://github.com/o/r", + }); + }); + it("treats a bare owner/repo (no host) as github by default", () => { + expect(displaySource({ type: "git", url: "open-gitagent/opengap" })).toEqual({ + kind: "git", + primary: "open-gitagent/opengap", + secondary: "github.com", + href: "https://github.com/open-gitagent/opengap", + }); + }); + }); + + describe("legacy string source", () => { + it("treats a full https URL the same as the structured form", () => { + const expected = { + kind: "git" as const, + primary: "open-gitagent/ComputerAgent", + secondary: "github.com", + href: "https://github.com/open-gitagent/ComputerAgent", + }; + expect(displaySource("https://github.com/open-gitagent/ComputerAgent")).toEqual(expected); + }); + it("treats a bare owner/repo string as github", () => { + expect(displaySource("open-gitagent/opengap")).toEqual({ + kind: "git", + primary: "open-gitagent/opengap", + secondary: "github.com", + href: "https://github.com/open-gitagent/opengap", + }); + }); + }); + + describe("unrecognized shapes fall back cleanly", () => { + it("returns kind=unknown with raw primary when there's only one path segment", () => { + expect(displaySource("just-a-name")).toEqual({ + kind: "unknown", + primary: "just-a-name", + secondary: "", + }); + }); + }); +}); diff --git a/agentos/src/api.ts b/agentos/src/api.ts index 15a09bc..48cfdb4 100644 --- a/agentos/src/api.ts +++ b/agentos/src/api.ts @@ -2,17 +2,88 @@ // and handles auth (the subdomain is gated by Caddy basic_auth). In dev, Vite // proxies /api with an injected Basic Auth header. So the bundle never holds creds. +/** Mirrors the protocol's IdentitySource zod schema. The server narrows on + * read; the dashboard renders the structured form when present. */ +export type IdentitySource = + | { type: "git"; url: string; ref?: string; subdir?: string } + | { type: "local"; path: string } + | { type: "inline"; manifest: Record; files?: Record }; + export interface Agent { name: string; label: string; harness: string; - source: string; + /** Structured IdentitySource for registry agents (preferred); legacy string + * for in-memory agents from the hardcoded config. Use `sourceUrl` for the + * canonical identity / display URL. */ + source: IdentitySource | string; + /** Canonical URL/path for this agent. Git: the repo URL. Local: the path. + * Inline: the literal string "inline". The dashboard treats this as the + * de-duplication key alongside `name`. */ + sourceUrl: string | null; model: string | null; sandboxCapable: boolean; sessionCount: number; activeSandboxes: number; lastActivity: string | null; logCount: number; + /** "in-memory" = configured at server startup (Slack bots, built-ins). + * "registry" = registered dynamically via the SDK's MongoTelemetry + * hook or via POST /agents/register. */ + origin?: "in-memory" | "registry"; + registeredBy?: string | null; + lastSeen?: string | null; +} + +export interface RegisterAgentInput { + name: string; + label?: string; + harness?: string; + source?: string; + model?: string; + registeredBy?: string; +} + +/** Result of `displaySource(agent.source)`. Drives `` rendering. */ +export interface SourceDisplay { + kind: "git" | "local" | "inline" | "unknown"; + primary: string; + secondary: string; + href?: string; +} + +/** Derive a render-ready breakdown of an agent's source. */ +export function displaySource(source: IdentitySource | string | null | undefined): SourceDisplay { + if (!source) return { kind: "unknown", primary: "(no source)", secondary: "" }; + if (typeof source === "object") { + if (source.type === "local") { + const path = source.path; + const tail = path.split("/").filter(Boolean).slice(-2).join("/"); + return { kind: "local", primary: tail || path, secondary: path }; + } + if (source.type === "inline") { + const name = + (typeof source.manifest?.name === "string" && (source.manifest.name as string)) || "inline"; + return { kind: "inline", primary: name, secondary: "inline manifest" }; + } + return parseGitUrl(source.url, source.ref); + } + return parseGitUrl(source); +} + +function parseGitUrl(raw: string, ref?: string): SourceDisplay { + const stripped = raw.replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/\.git$/, ""); + const parts = stripped.split(/[:/]/).filter(Boolean); + if (parts.length >= 3) { + const [host, owner, repo] = parts; + const href = `https://${host}/${owner}/${repo}${ref ? `/tree/${encodeURIComponent(ref)}` : ""}`; + return { kind: "git", primary: `${owner}/${repo}`, secondary: host, href }; + } + if (parts.length === 2) { + const [owner, repo] = parts; + return { kind: "git", primary: `${owner}/${repo}`, secondary: "github.com", href: `https://github.com/${owner}/${repo}` }; + } + return { kind: "unknown", primary: raw, secondary: "" }; } export interface LogEntry { @@ -151,6 +222,12 @@ async function reqJSON(method: string, path: string, body?: unknown): Promise export const api = { agents: () => getJSON<{ agents: Agent[] }>("/agents").then((d) => d.agents), + registerAgent: (input: RegisterAgentInput) => + postJSON<{ ok: boolean; name: string }>("/agents/register", input), + unregisterAgent: (name: string) => + reqJSON<{ ok: boolean }>("DELETE", `/agents/${encodeURIComponent(name)}`), + patchAgent: (name: string, fields: Partial>) => + reqJSON<{ ok: boolean }>("PATCH", `/agents/${encodeURIComponent(name)}`, fields), logs: (bot?: string, limit = 100) => getJSON<{ logs: LogEntry[] }>(`/logs?limit=${limit}${bot ? `&bot=${encodeURIComponent(bot)}` : ""}`).then((d) => d.logs), sessions: (bot?: string, limit = 100) => diff --git a/agentos/src/components/AgentCard.tsx b/agentos/src/components/AgentCard.tsx new file mode 100644 index 0000000..67d01d6 --- /dev/null +++ b/agentos/src/components/AgentCard.tsx @@ -0,0 +1,170 @@ +import { GitBranch, FolderTree, Code2, MessageSquare, Activity, Clock, ExternalLink } from "lucide-react"; +import { type Agent, displaySource } from "../api.ts"; +import { Badge } from "./ui/badge.tsx"; +import { cn } from "../lib/cn.ts"; + +function typeLogo(harness: string): string | null { + if (harness === "gitagent") return "/logos/gitagent.png"; + if (harness === "claude-agent-sdk") return "/logos/claude.svg"; + if (harness === "deepagents") return "/logos/langchain.svg"; + return null; +} + +const NAME_OVERRIDES: Record = { + "general-agent": "General Agent", + "agentos-builder": "AgentOS Builder", + "gap-promoter": "GAP Promoter", + "framework-translator-agent": "Framework Translator", +}; +function agentNameFromSource(source: string, fallback: string): string { + const slug = source.split("/").pop() ?? ""; + if (NAME_OVERRIDES[slug]) return NAME_OVERRIDES[slug]; + if (slug) return slug.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); + return fallback + .replace(/[-_]+/g, " ") + .replace(/\b\w/g, (c) => c.toUpperCase()); +} + +function timeAgo(iso: string | null): string { + if (!iso) return "never"; + const s = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); + if (s < 60) return `${s}s`; + if (s < 3600) return `${Math.floor(s / 60)}m`; + if (s < 86400) return `${Math.floor(s / 3600)}h`; + return `${Math.floor(s / 86400)}d`; +} + +/** + * Single agent card for the rail. + * + * Two zones with a hairline divider between them: + * Top: avatar (harness logo on white) + name + status pill + LIB/1-shot chip + * source line: owner/repo with kind glyph + clickable external icon + * Bottom: stat row — sessions / logs / lastActivity, each with icon + * + * Click anywhere on the card opens the agent's workspace (chat). The + * external-link icon is the only nested clickable; stopPropagation + * prevents card-click bubbling. + */ +export function AgentCard({ + agent: a, + selected, + onClick, +}: { + agent: Agent; + selected: boolean; + onClick: () => void; +}) { + const sourceUrl = a.sourceUrl ?? ""; + const displayName = agentNameFromSource(sourceUrl, a.name); + const harnessLogo = typeLogo(a.harness); + const initial = (displayName || a.name).charAt(0).toUpperCase(); + const isLive = a.activeSandboxes > 0; + const sd = displaySource(a.source); + + return ( + + ); +} + +function SourceGlyph({ kind }: { kind: "git" | "local" | "inline" | "unknown" }) { + if (kind === "git") return ; + if (kind === "local") return ; + if (kind === "inline") return ; + return ; +} diff --git a/agentos/src/components/AuthGate.tsx b/agentos/src/components/AuthGate.tsx new file mode 100644 index 0000000..a986dd3 --- /dev/null +++ b/agentos/src/components/AuthGate.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState, type ReactNode } from "react"; +import { LoginPage } from "./LoginPage.tsx"; +import { Loader2 } from "lucide-react"; + +type State = + | { kind: "checking" } + | { kind: "anon" } + | { kind: "auth"; user: string }; + +/** + * Wraps the app. On mount, calls /api/me to determine whether the user is + * already authenticated (cookie or backwards-compat Basic). If anonymous, + * shows the LoginPage. The cookie is httpOnly so we ALWAYS round-trip to + * the server for the truth — never trust localStorage for auth state. + */ +export function AuthGate({ children }: { children: ReactNode }) { + const [state, setState] = useState({ kind: "checking" }); + + useEffect(() => { + void check(); + }, []); + + async function check() { + setState({ kind: "checking" }); + try { + const res = await fetch("/api/me", { credentials: "include" }); + if (res.ok) { + const data = (await res.json()) as { user: string }; + setState({ kind: "auth", user: data.user }); + } else { + setState({ kind: "anon" }); + } + } catch { + // Backend unreachable — treat as anon so the login form gives a clear + // error rather than spinning forever. + setState({ kind: "anon" }); + } + } + + if (state.kind === "checking") { + return ( +
+ +
+ ); + } + if (state.kind === "anon") { + return void check()} />; + } + return <>{children}; +} diff --git a/agentos/src/components/ChatTab.tsx b/agentos/src/components/ChatTab.tsx index 52198f0..4125976 100644 --- a/agentos/src/components/ChatTab.tsx +++ b/agentos/src/components/ChatTab.tsx @@ -1,13 +1,30 @@ import { useEffect, useRef, useState } from "react"; +import { Paperclip, Play, Send, RefreshCw, AlertCircle } from "lucide-react"; import { api } from "../api.ts"; import { streamChat, stripAttachMarkers } from "../sse.ts"; +import { Button } from "./ui/button.tsx"; +import { Textarea } from "./ui/textarea.tsx"; +import { Badge } from "./ui/badge.tsx"; +import { StatusDot } from "./composite/StatusDot.tsx"; +import { cn } from "../lib/cn.ts"; -interface Msg { role: "user" | "assistant" | "status"; text: string; files?: string[]; canContinue?: boolean; } +interface Msg { + role: "user" | "assistant" | "status"; + text: string; + files?: string[]; + canContinue?: boolean; +} -const CONTINUE_PROMPT = "Continue from where you left off — keep building until the project is complete, then summarize what you built and give me the deploy URL."; +const CONTINUE_PROMPT = + "Continue from where you left off — keep building until the project is complete, then summarize what you built and give me the deploy URL."; export function ChatTab({ - agent, sandboxCapable, resumeSessionId, onConsumedResume, initialMessage, onConsumedInitial, + agent, + sandboxCapable, + resumeSessionId, + onConsumedResume, + initialMessage, + onConsumedInitial, }: { agent: string; sandboxCapable: boolean; @@ -29,11 +46,13 @@ export function ChatTab({ scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight }); }, [msgs]); - // Reset the console when the agent changes. - useEffect(() => { setSandboxId(null); setSessionId(null); setMsgs([]); setErr(null); }, [agent]); + useEffect(() => { + setSandboxId(null); + setSessionId(null); + setMsgs([]); + setErr(null); + }, [agent]); - // If asked to resume a session, load its transcript into the view and boot a - // sandbox pinned to it so the user can continue the conversation. useEffect(() => { if (!resumeSessionId) return; const sid = resumeSessionId; @@ -51,25 +70,28 @@ export function ChatTab({ } await boot(sid); })(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [resumeSessionId]); - // From Home: auto-send the prompt the user typed on the landing page. useEffect(() => { if (!initialMessage) return; const m = initialMessage; onConsumedInitial?.(); void send(m); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialMessage]); async function boot(resume?: string): Promise { - setBooting(true); setErr(null); + setBooting(true); + setErr(null); try { const r = await api.chatSandbox(agent, resume); setSandboxId(r.sandboxId); setSessionId(r.sessionId); return r.sandboxId; } catch (e) { - setErr(String(e)); return null; + setErr(String(e)); + return null; } finally { setBooting(false); } @@ -79,137 +101,201 @@ export function ChatTab({ const text = (textArg ?? input).trim(); if (!text || busy) return; - // Resolve where to stream: warm sandbox (multi-turn) or one-shot /run. let streamUrl: string; let curSandbox = sandboxId; if (sandboxCapable) { - if (!curSandbox) { curSandbox = await boot(); if (!curSandbox) return; } + if (!curSandbox) { + curSandbox = await boot(); + if (!curSandbox) return; + } streamUrl = api.chatStreamUrl(curSandbox); } else { - streamUrl = api.runStreamUrl(agent); // deepagents: fresh run each message + streamUrl = api.runStreamUrl(agent); } if (textArg === undefined) setInput(""); - setMsgs((m) => [...m, { role: "user", text }, { role: "status", text: "🤔 Working…" }]); + setMsgs((m) => [...m, { role: "user", text }, { role: "status", text: "Working…" }]); setBusy(true); let finalText = ""; let lastTools = 0; - const setStatus = (s: string) => setMsgs((m) => { - const c = [...m]; const last = c[c.length - 1]; - if (last?.role === "status") c[c.length - 1] = { role: "status", text: s }; - return c; - }); - - await streamChat( - streamUrl, - text, - { - onTool: (name, count) => { lastTools = count; setStatus(`🔧 ${name}… (${count} tool${count !== 1 ? "s" : ""})`); }, - onText: (t) => { finalText = t; }, - onError: (msg) => setMsgs((m) => replaceStatus(m, { role: "assistant", text: `❌ ${msg}` })), - onDone: (t) => { - const raw = t || finalText; - const { text: clean, files } = stripAttachMarkers(raw); - // The agent may finish via tool calls without a closing text summary - // (e.g. it hit its turn limit). Don't show a bare "(no reply)" — and - // offer a one-click Continue so the build can finish in this session. - const toolOnly = !clean && lastTools > 0; - const body = clean - || (toolOnly - ? `_(Ran ${lastTools} tool calls but stopped without a summary — likely its per-turn limit. Click Continue to keep building in this session.)_` - : "_(no reply)_"); - setMsgs((m) => replaceStatus(m, { - role: "assistant", text: body, - files: (sandboxCapable && files.length) ? files : undefined, + const setStatus = (s: string) => + setMsgs((m) => { + const c = [...m]; + const last = c[c.length - 1]; + if (last?.role === "status") c[c.length - 1] = { role: "status", text: s }; + return c; + }); + + await streamChat(streamUrl, text, { + onTool: (name, count) => { + lastTools = count; + setStatus(`${name}… (${count} tool${count !== 1 ? "s" : ""})`); + }, + onText: (t) => { + finalText = t; + }, + onError: (msg) => setMsgs((m) => replaceStatus(m, { role: "assistant", text: `❌ ${msg}` })), + onDone: (t) => { + const raw = t || finalText; + const { text: clean, files } = stripAttachMarkers(raw); + const toolOnly = !clean && lastTools > 0; + const body = + clean || + (toolOnly + ? `_(Ran ${lastTools} tool calls but stopped without a summary — likely its per-turn limit. Click Continue to keep building in this session.)_` + : "_(no reply)_"); + setMsgs((m) => + replaceStatus(m, { + role: "assistant", + text: body, + files: sandboxCapable && files.length ? files : undefined, canContinue: toolOnly, - })); - }, + }), + ); }, - ).catch((e) => setMsgs((m) => replaceStatus(m, { role: "assistant", text: `❌ ${e}` }))); + }).catch((e) => setMsgs((m) => replaceStatus(m, { role: "assistant", text: `❌ ${e}` }))); setBusy(false); - // Persist the web turn to the audit log. - api.logWebTurn({ bot: agent, sessionId: sessionId ?? `oneshot-${agent}`, query: text, reply: finalText, ok: true }).catch(() => {}); + api.logWebTurn({ + bot: agent, + sessionId: sessionId ?? `oneshot-${agent}`, + query: text, + reply: finalText, + ok: true, + }).catch(() => {}); } return (
-
+ {/* Session bar */} +
{!sandboxCapable ? ( - one-shot mode — each message is an independent run (no memory across turns) + ) : sandboxId ? ( <> - - {sessionId} - + + {sessionId} + ) : ( - {booting ? "Booting sandbox…" : "Send a message to start a session"} + {booting ? "Booting sandbox…" : "Send a message to start a session"} )}
+ {/* Messages */}
- {err &&
{err}
} + {err && ( +
+ + {err} +
+ )} {msgs.length === 0 && !err && ( -
+
Talk to {agent}. It runs with the same config as in Slack.
)} {msgs.map((m, i) => { - if (m.role === "status") return
{m.text}
; - const isUser = m.role === "user"; - return ( -
-
+ if (m.role === "status") { + return ( +
+ {m.text} - {m.files && m.files.length > 0 && ( -
- {m.files.map((f) => ( - 📎 {f.split("/").pop()} - ))} -
- )} - {m.canContinue && !busy && ( - - )}
-
- ); + ); + } + return send(CONTINUE_PROMPT)} />; })}
-
+ {/* Input */} +
-