diff --git a/apps/playground/README.md b/apps/playground/README.md new file mode 100644 index 0000000..cbb734a --- /dev/null +++ b/apps/playground/README.md @@ -0,0 +1,24 @@ +# @any-harness/playground + +Minimal reference harness on `@any-harness/metaharness` — e2e playground and +SDK-outreach demo. Runs one agent turn (or a REPL) through the AI SDK +`ToolLoopAgent` loop with capability injection: + +- **storage** — `unstorage` fs driver at `.playground-store/` +- **secrets** — BYOK `SecretPort` over the injected env +- **bridge** — in-process demo host (`src/host.ts`) with canned extensions; + the `list_extensions` / `get_extension` / `call_extension_tool` / + `resolve_command` / `materialize_skill` tools read through it +- **exec** — computer-only slot, stubbed here (reports unavailability) + +## Run + +```bash +pnpm install +AI_BASE_URL=http://localhost:1234/v1 AI_MODEL_ID= pnpm start +AI_BASE_URL=... AI_API_KEY=... AI_MODEL_ID=... pnpm start -- --prompt "list your extensions" +``` + +Any OpenAI-compatible endpoint works (OpenAI, Anthropic-compat gateways, +LM Studio, mocks). Try: "what extensions do you have?" — the model calls +`list_extensions` via bridge op `extensions.list`. diff --git a/apps/playground/package.json b/apps/playground/package.json new file mode 100644 index 0000000..9e72d01 --- /dev/null +++ b/apps/playground/package.json @@ -0,0 +1,26 @@ +{ + "name": "@any-harness/playground", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "tsx src/cli.ts", + "build": "echo 'build: not yet implemented'", + "typecheck": "tsc --noEmit", + "lint": "echo 'lint: not yet implemented'", + "test": "vitest run", + "clean": "rm -rf dist .playground-store" + }, + "dependencies": { + "@any-harness/metaharness": "workspace:*", + "@ai-sdk/openai-compatible": "^3.0.0", + "ai": "catalog:", + "unstorage": "catalog:" + }, + "devDependencies": { + "@types/node": "catalog:", + "tsx": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/apps/playground/src/capabilities.ts b/apps/playground/src/capabilities.ts new file mode 100644 index 0000000..e43408f --- /dev/null +++ b/apps/playground/src/capabilities.ts @@ -0,0 +1,50 @@ +import type { Capabilities, SecretPort, StoragePort } from "@any-harness/metaharness"; +import { createStorage, type StorageValue } from "unstorage"; +import fsDriver from "unstorage/drivers/fs"; + +import { demoBridge } from "./host.js"; + +const storagePort = (base: string): StoragePort => { + const storage = createStorage({ driver: fsDriver({ base }) }); + return { + get: (key) => storage.getItem(key), + set: async (key, value) => { + await storage.setItem(key, value as StorageValue); + }, + delete: async (key) => { + await storage.removeItem(key); + }, + }; +}; + +/** Env map → SecretPort. The host injects the map; nothing reads globals. */ +const secretPort = (env: Record): SecretPort => ({ + resolve: (name) => env[name], +}); + +export interface PlaygroundCapabilitiesOptions { + /** Injected environment (e.g. `process.env`) for the BYOK secret slot. */ + env: Record; + /** Directory for the unstorage fs driver. */ + storeDir: string; +} + +export const createPlaygroundCapabilities = ( + options: PlaygroundCapabilitiesOptions, +): Capabilities => ({ + storage: storagePort(options.storeDir), + secrets: secretPort(options.env), + bridge: demoBridge(), + // Computer-only slot: the playground is a demo harness, so exec is a stub + // that reports unavailability rather than a real process runner. A computer + // build injects a real ExecPort; a browser build injects nothing and the + // same tools degrade cleanly. + exec: { + run: (command) => + Promise.resolve({ + code: 127, + stdout: "", + stderr: `exec stub: playground does not run commands (computer-only slot): ${command}`, + }), + }, +}); diff --git a/apps/playground/src/cli.ts b/apps/playground/src/cli.ts new file mode 100644 index 0000000..966d3d5 --- /dev/null +++ b/apps/playground/src/cli.ts @@ -0,0 +1,96 @@ +// Node edge of the playground — the only file allowed `node:*` imports. +// Everything upstream (capabilities, host, run loop) stays isomorphic. +import process from "node:process"; +import readline from "node:readline/promises"; + +import type { ModelMessage } from "ai"; + +import { createPlaygroundCapabilities } from "./capabilities.js"; +import { modelFromEnv } from "./model.js"; +import { streamTurn } from "./run.js"; + +const USAGE = `anyharness playground — metaharness demo CLI + +usage: + tsx src/cli.ts # REPL + tsx src/cli.ts --prompt "..." # one-shot turn + tsx src/cli.ts --help + +env (BYOK): AI_BASE_URL, AI_API_KEY, AI_MODEL_ID — any OpenAI-compatible +endpoint (OpenAI, Anthropic-compat gateway, LM Studio, mock server). +`; + +const promptArg = (argv: string[]): string | undefined => { + const i = argv.findIndex((a) => a === "--prompt" || a === "-p"); + return i === -1 ? undefined : argv[i + 1]; +}; + +const render = (event: Awaited> extends AsyncIterable ? E : never): void => { + if (event.kind === "text") { + process.stdout.write(event.text); + } else if (event.kind === "tool-call") { + process.stdout.write(`\n[tool] ${event.tool} ${JSON.stringify(event.input)}\n`); + } else if (event.kind === "tool-result") { + const out = JSON.stringify(event.output); + process.stdout.write(`[result] ${out.length > 400 ? `${out.slice(0, 400)}…` : out}\n`); + } else if (event.kind === "finish") { + process.stdout.write("\n"); + } +}; + +const main = async () => { + const argv = process.argv.slice(2); + if (argv.includes("--help") || argv.includes("-h")) { + process.stdout.write(USAGE); + return; + } + + const resolved = modelFromEnv(process.env); + if ("error" in resolved) { + process.stderr.write(`${resolved.error}\n`); + process.exitCode = 2; + return; + } + + const capabilities = createPlaygroundCapabilities({ + env: process.env, + storeDir: ".playground-store", + }); + const history: ModelMessage[] = []; + const once = promptArg(argv); + + const runTurn = async (prompt: string) => { + history.push({ role: "user", content: prompt }); + process.stdout.write("agent> "); + for await (const event of streamTurn({ + model: resolved.model, + messages: history, + capabilities, + })) { + render(event); + } + }; + + if (once !== undefined) { + await runTurn(once); + return; + } + + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + process.stdout.write("anyharness playground — type a message, 'exit' to quit\n"); + for (;;) { + let line: string; + try { + line = await rl.question("you> "); + } catch { + break; // stdin closed (EOF / piped input ended) + } + const prompt = line.trim(); + if (prompt === "exit" || prompt === "quit") break; + if (!prompt) continue; + await runTurn(prompt); + } + rl.close(); +}; + +await main(); diff --git a/apps/playground/src/host.ts b/apps/playground/src/host.ts new file mode 100644 index 0000000..ba016aa --- /dev/null +++ b/apps/playground/src/host.ts @@ -0,0 +1,81 @@ +import { + inProcessBridge, + type BridgeClient, + type BridgeHandlers, + type Extension, +} from "@any-harness/metaharness"; + +/** + * Demo bridge host: an in-process binding over a canned extension set. + * Stands in for a real store host (`harness serve` over stdio) until W2/W3 + * land the spec and the sdk implementation. + */ +const DEMO_EXTENSIONS: Extension[] = [ + { + name: "commit-smith", + kind: "skill", + ref: { manifest: "~/.agents/skills/commit-smith/SKILL.md" }, + description: "Draft conventional-commit messages from a staged diff.", + }, + { + name: "echo-mcp", + kind: "mcp", + ref: { manifest: "~/.agents/harness/echo-mcp/plugin.json", version: "0.1.0" }, + description: "Demo MCP server exposing a single `echo` tool.", + }, + { + name: "review", + kind: "command", + ref: { manifest: "~/.agents/harness/review/plugin.json", version: "0.1.0" }, + description: "Demo command: review the current diff.", + }, +]; + +const COMMIT_SMITH_SKILL = `--- +name: commit-smith +description: Draft conventional-commit messages from a staged diff. +--- +# commit-smith +Read the staged diff, then propose a single conventional commit message +(type(scope): summary) plus an optional body listing breaking changes. +`; + +const handlers: BridgeHandlers = { + "capabilities.negotiate": (params) => { + const requested = params?.capabilities ?? []; + // The demo host grants every declared slot — a real host denies the ones + // it can't serve (e.g. exec on browser targets). + return { + protocolVersion: params?.protocolVersion ?? "0.1", + granted: requested, + denied: [], + }; + }, + "extensions.list": (params) => ({ + extensions: params?.kind + ? DEMO_EXTENSIONS.filter((e) => e.kind === params.kind) + : DEMO_EXTENSIONS, + }), + "extensions.get": (params) => ({ + extension: DEMO_EXTENSIONS.find((e) => e.name === params?.name) ?? null, + }), + "skills.materialize": (params) => + params?.name === "commit-smith" + ? { found: true, content: COMMIT_SMITH_SKILL } + : { found: false }, + "commands.resolve": (params) => + params?.name === "review" + ? { + resolved: true, + instructions: "Review the current diff for correctness, then summarize findings.", + } + : { resolved: false }, + "tools.call": (params) => + params?.extension === "echo-mcp" && params?.tool === "echo" + ? { output: params.input } + : { error: `unknown tool ${params?.extension}/${params?.tool}` }, + "events.notify": () => ({ ok: true }), + // hooks.invoke intentionally omitted — the demo host has no hooks. +}; + +export const demoBridge = (): BridgeClient => inProcessBridge(handlers); diff --git a/apps/playground/src/model.ts b/apps/playground/src/model.ts new file mode 100644 index 0000000..cf09843 --- /dev/null +++ b/apps/playground/src/model.ts @@ -0,0 +1,30 @@ +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; +import type { LanguageModel } from "ai"; + +// Justified dep beyond the `ai` catalog: the playground needs one concrete +// provider adapter for BYOK demo runs; `ai` ships none. openai-compatible +// covers OpenAI, Anthropic-compat gateways and local servers (LM Studio, +// mocks) through a single env-driven config. +export interface ModelEnv { + AI_BASE_URL?: string; + AI_API_KEY?: string; + AI_MODEL_ID?: string; +} + +export const modelFromEnv = ( + env: ModelEnv, +): { model: LanguageModel } | { error: string } => { + if (!env.AI_BASE_URL || !env.AI_MODEL_ID) { + return { + error: + "set AI_BASE_URL + AI_MODEL_ID (and AI_API_KEY if the provider requires it) " + + "to run a real model", + }; + } + const provider = createOpenAICompatible({ + name: "playground-byok", + baseURL: env.AI_BASE_URL, + apiKey: env.AI_API_KEY, + }); + return { model: provider.chatModel(env.AI_MODEL_ID) }; +}; diff --git a/apps/playground/src/run.ts b/apps/playground/src/run.ts new file mode 100644 index 0000000..faa96ab --- /dev/null +++ b/apps/playground/src/run.ts @@ -0,0 +1,49 @@ +import { + aiSdkHarness, + extensionTools, + type Capabilities, +} from "@any-harness/metaharness"; +import type { LanguageModel, ModelMessage } from "ai"; + +export type TurnEvent = + | { kind: "text"; text: string } + | { kind: "tool-call"; tool: string; input: unknown } + | { kind: "tool-result"; tool: string; output: unknown } + | { kind: "finish" }; + +export interface TurnInput { + model: LanguageModel; + messages: ModelMessage[]; + capabilities: Capabilities; + signal?: AbortSignal; +} + +/** One agent turn through the metaharness, normalized for CLI display. */ +export async function* streamTurn(input: TurnInput): AsyncIterable { + const harness = aiSdkHarness(); + for await (const part of harness.stream({ + agent: { + name: "playground", + instructions: + "You are the AnyHarness playground agent. Your extension surfaces " + + "(list/get extensions, call extension tools, resolve commands, " + + "materialize skills, exec) come through injected capabilities — use " + + "them when asked; report plainly when a slot is unavailable.", + tools: extensionTools(), + }, + model: input.model, + messages: input.messages, + capabilities: input.capabilities, + signal: input.signal, + })) { + if (part.type === "text-delta") { + yield { kind: "text", text: part.text }; + } else if (part.type === "tool-call") { + yield { kind: "tool-call", tool: part.toolName, input: part.input }; + } else if (part.type === "tool-result") { + yield { kind: "tool-result", tool: part.toolName, output: part.output }; + } else if (part.type === "finish") { + yield { kind: "finish" }; + } + } +} diff --git a/apps/playground/tsconfig.json b/apps/playground/tsconfig.json new file mode 100644 index 0000000..7c9df92 --- /dev/null +++ b/apps/playground/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "types": ["node"] + }, + "include": ["src/**/*"] +} diff --git a/packages/metaharness/AGENTS.md b/packages/metaharness/AGENTS.md new file mode 100644 index 0000000..e08be15 --- /dev/null +++ b/packages/metaharness/AGENTS.md @@ -0,0 +1,29 @@ +# @any-harness/metaharness — agent notes + +Contract seam between a host and whichever agent loop backs it, plus the +in-process bridge client the loop consumes extension surfaces through. +AI SDK (`ToolLoopAgent`) is the only implementation today. + +## Hard rules + +- **Isomorphic**: zero `node:*` imports — `unenv`/`unstorage`/`ofetch` when an + env shim is ever needed. This package must compile for browser and scriptc. +- **Interface-first**: object shapes are `interface`; unions stay `type`. +- **No harness logic here**: loop, approvals, retries live in the impl. If + you're writing a loop, stop — it belongs in the harness impl. +- All tool I/O via `Capabilities` → the tool `context` option; never globals. +- Env differences arrive via injected `Capabilities` — absent slots + (computer-only: `exec`, fs-backed `storage`/`bridge`) degrade in the tool + `execute`, never via `#ifdef`-style branches on the environment. +- Bridge op names are pinned in `.agents/plans/2026-10-07-v2-foundation.md`; + don't invent new ones — they belong in `spec/bridge/` first. + +## Layout + +| File | Purpose | +| ----------------- | -------------------------------------------------------------------- | +| `src/harness.ts` | Contract: `AgentHarness`, `AgentSpec`, `Capabilities`, `HarnessRunInput` | +| `src/bridge.ts` | Bridge ops table, `BridgeClient`, `inProcessBridge` binding | +| `src/ai-sdk.ts` | `aiSdkHarness` — `ToolLoopAgent` adapter | +| `src/tools.ts` | `extensionTools()` — bridge surfaces + computer-only exec as tools | +| `src/harness.test.ts` | Mock-model smoke test via `ai/test` `MockLanguageModelV4` | diff --git a/packages/metaharness/package.json b/packages/metaharness/package.json index 923ad47..1fbee4c 100644 --- a/packages/metaharness/package.json +++ b/packages/metaharness/package.json @@ -11,7 +11,11 @@ "test": "vitest run", "clean": "rm -rf dist" }, + "dependencies": { + "ai": "catalog:" + }, "devDependencies": { + "@ai-sdk/provider": "^4.0.0", "typescript": "catalog:", "vitest": "catalog:" } diff --git a/packages/metaharness/src/ai-sdk.ts b/packages/metaharness/src/ai-sdk.ts new file mode 100644 index 0000000..c9f86ea --- /dev/null +++ b/packages/metaharness/src/ai-sdk.ts @@ -0,0 +1,25 @@ +import { stepCountIs, ToolLoopAgent } from "ai"; + +import type { AgentHarness, HarnessRunInput } from "./harness.js"; + +const DEFAULT_MAX_STEPS = 16; + +export const aiSdkHarness = (): AgentHarness => ({ + async *stream({ agent, model, messages, capabilities, signal }: HarnessRunInput) { + // Per-run capabilities reach every tool's `execute` via `options.context`. + const toolsContext = Object.fromEntries( + Object.keys(agent.tools ?? {}).map((name) => [name, capabilities]), + ); + const sdk = new ToolLoopAgent({ + model, + instructions: agent.instructions, + tools: agent.tools, + stopWhen: stepCountIs(agent.maxSteps ?? DEFAULT_MAX_STEPS), + // `toolsContext` is typed `never` for a schema-less ToolSet; the runtime + // still forwards `toolsContext[toolName]` into `options.context`. + toolsContext: toolsContext as never, + }); + const result = await sdk.stream({ messages, abortSignal: signal }); + yield* result.fullStream; + }, +}); diff --git a/packages/metaharness/src/bridge.ts b/packages/metaharness/src/bridge.ts new file mode 100644 index 0000000..f285a0b --- /dev/null +++ b/packages/metaharness/src/bridge.ts @@ -0,0 +1,127 @@ +/** + * Bridge protocol v0.1 — client-side surface. + * + * The bridge is the versioned JSON-RPC slot between a harness and an + * AnyHarness store host. Three transports are planned (stdio primary, HTTP + * loopback, in-process); this package ships the in-process binding only — + * the same `BridgeClient` shape a stdio/HTTP client will satisfy. + * + * NOTE: `Extension` / `ExtensionKind` / `ManifestRef` are interface pins from + * `.agents/plans/2026-10-07-v2-foundation.md`. `@any-harness/spec` will own + * the canonical schemas once W2/W3 land; keep these structurally aligned. + */ + +export type ExtensionKind = + | "skill" + | "mcp" + | "plugin" + | "hook" + | "command" + | "agent" + | "rule"; + +/** Pointer to an installed unit's `plugin.json` + resolved version. */ +export interface ManifestRef { + /** Path or URI to the manifest (e.g. `~/.agents/harness//plugin.json`). */ + manifest: string; + /** Pinned version when resolved through the lockfile. */ + version?: string; +} + +/** Installed unit. */ +export interface Extension { + name: string; + kind: ExtensionKind; + ref: ManifestRef; + enabled?: boolean; + description?: string; +} + +/** Operation table: wire name → params/result pair. */ +export interface BridgeOps { + "capabilities.negotiate": { + params: { protocolVersion: string; capabilities: string[] }; + result: { protocolVersion: string; granted: string[]; denied: string[] }; + }; + "extensions.list": { + params: { kind?: ExtensionKind } | undefined; + result: { extensions: Extension[] }; + }; + "extensions.get": { + params: { name: string }; + result: { extension: Extension | null }; + }; + "hooks.invoke": { + params: { hook: string; event: string; payload?: unknown }; + result: { ok: boolean; output?: unknown }; + }; + "commands.resolve": { + params: { name: string; args?: string[] }; + result: { resolved: boolean; instructions?: string }; + }; + "skills.materialize": { + params: { name: string }; + result: { found: boolean; content?: string }; + }; + /** MCP passthrough to managed servers. */ + "tools.call": { + params: { extension: string; tool: string; input?: unknown }; + result: { output?: unknown; error?: string }; + }; + "events.notify": { + params: { event: string; payload?: unknown }; + result: { ok: boolean }; + }; +} + +export type BridgeOp = keyof BridgeOps; + +export const BRIDGE_OPS: readonly BridgeOp[] = [ + "capabilities.negotiate", + "extensions.list", + "extensions.get", + "hooks.invoke", + "commands.resolve", + "skills.materialize", + "tools.call", + "events.notify", +]; + +/** One callable op, whatever the transport underneath. */ +export interface BridgeClient { + call( + op: Op, + params: BridgeOps[Op]["params"], + ): Promise; +} + +export class BridgeError extends Error { + readonly op: BridgeOp; + constructor(op: BridgeOp, message: string) { + super(`bridge ${op}: ${message}`); + this.name = "BridgeError"; + this.op = op; + } +} + +export type BridgeHandlers = { + [Op in BridgeOp]?: ( + params: BridgeOps[Op]["params"], + ) => Promise | BridgeOps[Op]["result"]; +}; + +/** + * In-process transport: a `BridgeClient` bound directly to a host's handler + * table — no serialization. Used by TS embeds, the browser build, and tests. + * Missing ops raise `BridgeError`; host surfaces stay opt-in per op. + */ +export const inProcessBridge = (handlers: BridgeHandlers): BridgeClient => ({ + call: async (op, params) => { + const handler = handlers[op]; + if (!handler) { + throw new BridgeError(op, "op not implemented by this host"); + } + // Params/result are per-op typed; dispatch is unchecked by construction. + return (handler as (p: unknown) => unknown)(params) as never; + }, +}); diff --git a/packages/metaharness/src/harness.test.ts b/packages/metaharness/src/harness.test.ts new file mode 100644 index 0000000..408dba1 --- /dev/null +++ b/packages/metaharness/src/harness.test.ts @@ -0,0 +1,165 @@ +import type { LanguageModelV4StreamPart } from "@ai-sdk/provider"; +import { jsonSchema, tool } from "ai"; +import { convertArrayToReadableStream, MockLanguageModelV4 } from "ai/test"; +import { describe, expect, it } from "vitest"; + +import { aiSdkHarness } from "./ai-sdk.js"; +import { inProcessBridge, type Extension } from "./bridge.js"; +import type { Capabilities } from "./harness.js"; +import { extensionTools } from "./tools.js"; + +const usage = { + inputTokens: { total: 3, noCache: 3, cacheRead: 0, cacheWrite: 0 }, + outputTokens: { total: 5, text: 5, reasoning: 0 }, +}; + +const textParts = (delta: string): LanguageModelV4StreamPart[] => [ + { type: "stream-start" as const, warnings: [] }, + { type: "text-start" as const, id: "1" }, + { type: "text-delta" as const, id: "1", delta }, + { type: "text-end" as const, id: "1" }, + { type: "finish" as const, finishReason: { unified: "stop" as const, raw: undefined }, usage }, +]; + +const toolCallThenText = ( + toolName: string, + input: string, + calls: { n: number }, +): LanguageModelV4StreamPart[] => + calls.n++ === 0 + ? [ + { type: "stream-start" as const, warnings: [] }, + { type: "tool-call" as const, toolCallId: "c1", toolName, input }, + { + type: "finish" as const, + finishReason: { unified: "tool-calls" as const, raw: undefined }, + usage, + }, + ] + : textParts("done"); + +const collect = async (input: Parameters["stream"]>[0]) => { + const parts = []; + for await (const part of aiSdkHarness().stream(input)) parts.push(part); + return parts; +}; + +const demoExtensions: Extension[] = [ + { name: "commit-smith", kind: "skill", ref: { manifest: "~/.agents/skills/commit-smith/SKILL.md" } }, + { name: "echo-mcp", kind: "mcp", ref: { manifest: "~/.agents/harness/echo-mcp/plugin.json", version: "0.1.0" } }, +]; + +describe("aiSdkHarness", () => { + it("streams model text through to the caller", async () => { + const model = new MockLanguageModelV4({ + doStream: async () => ({ stream: convertArrayToReadableStream(textParts("hello")) }), + }); + + const parts = await collect({ + agent: { name: "test" }, + model, + messages: [{ role: "user", content: "hi" }], + }); + + const deltas = parts.filter((p) => p.type === "text-delta"); + expect(deltas.map((p) => (p.type === "text-delta" ? p.text : ""))).toEqual(["hello"]); + expect(parts.at(-1)?.type).toBe("finish"); + }); + + it("injects capabilities into tool execute via the tool context", async () => { + const capabilities: Capabilities = { storage: { get: async () => null, set: async () => {}, delete: async () => {} } }; + let seenContext: unknown; + const calls = { n: 0 }; + + const echo = tool({ + description: "echo the value", + inputSchema: jsonSchema<{ v: string }>({ + type: "object", + properties: { v: { type: "string" } }, + required: ["v"], + additionalProperties: false, + }), + execute: (input, options) => { + seenContext = options.context; + return Promise.resolve(input.v); + }, + }); + + const model = new MockLanguageModelV4({ + doStream: () => + Promise.resolve({ + stream: convertArrayToReadableStream(toolCallThenText("echo", JSON.stringify({ v: "x" }), calls)), + }), + }); + + const parts = await collect({ + agent: { name: "test", tools: { echo } }, + model, + messages: [{ role: "user", content: "use echo" }], + capabilities, + }); + + expect(calls.n).toBe(2); + expect(parts.map((p) => p.type)).toEqual( + expect.arrayContaining(["tool-call", "tool-result", "finish"]), + ); + expect(seenContext).toBe(capabilities); + }); + + it("wires bridge extension surfaces into tools", async () => { + const calls = { n: 0 }; + const bridge = inProcessBridge({ + "extensions.list": (params) => + Promise.resolve({ + extensions: params?.kind + ? demoExtensions.filter((e) => e.kind === params.kind) + : demoExtensions, + }), + }); + + const model = new MockLanguageModelV4({ + doStream: () => + Promise.resolve({ + stream: convertArrayToReadableStream( + toolCallThenText("list_extensions", JSON.stringify({ kind: "skill" }), calls), + ), + }), + }); + + const parts = await collect({ + agent: { name: "test", tools: extensionTools() }, + model, + messages: [{ role: "user", content: "list skills" }], + capabilities: { bridge }, + }); + + const result = parts.find((p) => p.type === "tool-result"); + expect(result && result.type === "tool-result" ? result.output : undefined).toEqual({ + extensions: [demoExtensions[0]], + }); + }); + + it("degrades computer-only slots instead of failing when capabilities are absent", async () => { + const calls = { n: 0 }; + const model = new MockLanguageModelV4({ + doStream: () => + Promise.resolve({ + stream: convertArrayToReadableStream( + toolCallThenText("computer_exec", JSON.stringify({ command: "ls" }), calls), + ), + }), + }); + + const parts = await collect({ + agent: { name: "test", tools: extensionTools() }, + model, + messages: [{ role: "user", content: "run ls" }], + // No exec, no bridge: browser-shaped capability set. + }); + + const result = parts.find((p) => p.type === "tool-result"); + expect(result && result.type === "tool-result" ? result.output : undefined).toMatchObject({ + error: expect.stringContaining("computer-only"), + }); + }); +}); diff --git a/packages/metaharness/src/harness.ts b/packages/metaharness/src/harness.ts new file mode 100644 index 0000000..9f82439 --- /dev/null +++ b/packages/metaharness/src/harness.ts @@ -0,0 +1,73 @@ +import type { LanguageModel, ModelMessage, TextStreamPart, ToolSet } from "ai"; + +import type { BridgeClient } from "./bridge.js"; + +/** + * Static agent definition. + * Maps to LangChain `createAgent` args / TanStack `defineAgent`. + */ +export interface AgentSpec { + name: string; + instructions?: string; + tools?: ToolSet; + /** Cap on model steps per run (default 16). */ + maxSteps?: number; +} + +export interface StoragePort { + get(key: string): Promise; + set(key: string, value: unknown): Promise; + delete(key: string): Promise; +} + +/** BYOK: resolves user-held keys client-side; values must never reach a server. */ +export interface SecretPort { + resolve(name: string): string | undefined; +} + +export interface ExecResult { + code: number; + stdout: string; + stderr: string; +} + +/** + * Computer-only slot: process execution. Browser targets never inject it — + * its absence is declared at `capabilities.negotiate`, not branched on inside + * the core. + */ +export interface ExecPort { + run(command: string): Promise; +} + +/** + * Host-provided capabilities, injected per run and visible to every tool's + * `execute` via the tool `context` option. Concrete shapes are owned by the + * layers: `storage`/`secrets` by the host app, `bridge` by the AnyHarness + * store host; `exec` is a computer-only slot. + */ +export interface Capabilities { + storage?: StoragePort; + secrets?: SecretPort; + exec?: ExecPort; + bridge?: BridgeClient; +} + +export interface HarnessRunInput { + agent: AgentSpec; + /** Maps to LangChain `BaseChatModel` / a TanStack AI adapter. */ + model: LanguageModel; + messages: ModelMessage[]; + capabilities?: Capabilities; + signal?: AbortSignal; +} + +/** + * The seam host code binds to. `aiSdkHarness` is the only implementation + * today; an alternative loop only has to emit the same AI-SDK-shaped parts — + * the union covers text deltas, tool calls/results/errors, approval requests + * and denials, step and run boundaries, abort and error. + */ +export interface AgentHarness { + stream(input: HarnessRunInput): AsyncIterable>; +} diff --git a/packages/metaharness/src/index.ts b/packages/metaharness/src/index.ts index cb0ff5c..95e99e1 100644 --- a/packages/metaharness/src/index.ts +++ b/packages/metaharness/src/index.ts @@ -1 +1,26 @@ -export {}; +export { aiSdkHarness } from "./ai-sdk.js"; +export { + BRIDGE_OPS, + BridgeError, + inProcessBridge, +} from "./bridge.js"; +export type { + BridgeClient, + BridgeHandlers, + BridgeOp, + BridgeOps, + Extension, + ExtensionKind, + ManifestRef, +} from "./bridge.js"; +export type { + AgentHarness, + AgentSpec, + Capabilities, + ExecPort, + ExecResult, + HarnessRunInput, + SecretPort, + StoragePort, +} from "./harness.js"; +export { extensionTools } from "./tools.js"; diff --git a/packages/metaharness/src/tools.ts b/packages/metaharness/src/tools.ts new file mode 100644 index 0000000..86d3fea --- /dev/null +++ b/packages/metaharness/src/tools.ts @@ -0,0 +1,137 @@ +import { jsonSchema, tool, type ToolSet } from "ai"; + +import type { ExtensionKind } from "./bridge.js"; +import type { Capabilities } from "./harness.js"; + +const capabilitiesOf = (context: unknown): Capabilities | undefined => + typeof context === "object" && context !== null + ? (context as Capabilities) + : undefined; + +const NO_BRIDGE = + "bridge capability not injected — extension surfaces are unavailable on this target"; +const NO_EXEC = + "exec capability not injected — command execution is a computer-only slot"; + +const kindSchema = { + type: "string", + enum: ["skill", "mcp", "plugin", "hook", "command", "agent", "rule"], +} as const; + +/** + * Extension surfaces exposed to the model as AI SDK tools. Every `execute` + * reads the injected `Capabilities` through `options.context` — nothing here + * touches globals or the environment directly, so a browser target gets the + * same tool surface with capability-degraded results. + */ +export const extensionTools = (): ToolSet => ({ + list_extensions: tool({ + description: + "List extensions installed in the AnyHarness store " + + "(skills, MCP servers, plugins, hooks, commands, agents, rules).", + inputSchema: jsonSchema<{ kind?: ExtensionKind }>({ + type: "object", + properties: { kind: kindSchema }, + additionalProperties: false, + }), + execute: async (input, options) => { + const bridge = capabilitiesOf(options.context)?.bridge; + if (!bridge) return { error: NO_BRIDGE }; + return bridge.call("extensions.list", input.kind ? { kind: input.kind } : undefined); + }, + }), + + get_extension: tool({ + description: "Get one installed extension's manifest ref and metadata by name.", + inputSchema: jsonSchema<{ name: string }>({ + type: "object", + properties: { name: { type: "string" } }, + required: ["name"], + additionalProperties: false, + }), + execute: async (input, options) => { + const bridge = capabilitiesOf(options.context)?.bridge; + if (!bridge) return { error: NO_BRIDGE }; + return bridge.call("extensions.get", { name: input.name }); + }, + }), + + call_extension_tool: tool({ + description: + "Call a tool exposed by an installed extension " + + "(bridge `tools.call` — MCP passthrough to managed servers).", + inputSchema: jsonSchema<{ extension: string; tool: string; input?: unknown }>({ + type: "object", + properties: { + extension: { type: "string" }, + tool: { type: "string" }, + input: {}, + }, + required: ["extension", "tool"], + additionalProperties: false, + }), + execute: async (input, options) => { + const bridge = capabilitiesOf(options.context)?.bridge; + if (!bridge) return { error: NO_BRIDGE }; + return bridge.call("tools.call", { + extension: input.extension, + tool: input.tool, + input: input.input, + }); + }, + }), + + resolve_command: tool({ + description: + "Resolve a named harness command through the bridge " + + "(bridge `commands.resolve`) and return its instructions.", + inputSchema: jsonSchema<{ name: string; args?: string[] }>({ + type: "object", + properties: { + name: { type: "string" }, + args: { type: "array", items: { type: "string" } }, + }, + required: ["name"], + additionalProperties: false, + }), + execute: async (input, options) => { + const bridge = capabilitiesOf(options.context)?.bridge; + if (!bridge) return { error: NO_BRIDGE }; + return bridge.call("commands.resolve", { name: input.name, args: input.args }); + }, + }), + + materialize_skill: tool({ + description: + "Materialize an installed skill's instructions " + + "(bridge `skills.materialize` — returns the SKILL.md body).", + inputSchema: jsonSchema<{ name: string }>({ + type: "object", + properties: { name: { type: "string" } }, + required: ["name"], + additionalProperties: false, + }), + execute: async (input, options) => { + const bridge = capabilitiesOf(options.context)?.bridge; + if (!bridge) return { error: NO_BRIDGE }; + return bridge.call("skills.materialize", { name: input.name }); + }, + }), + + computer_exec: tool({ + description: + "Run a shell command. Computer-only capability — returns an error " + + "on targets that do not inject an exec slot (e.g. browser builds).", + inputSchema: jsonSchema<{ command: string }>({ + type: "object", + properties: { command: { type: "string" } }, + required: ["command"], + additionalProperties: false, + }), + execute: async (input, options) => { + const exec = capabilitiesOf(options.context)?.exec; + if (!exec) return { error: NO_EXEC }; + return exec.run(input.command); + }, + }), +}); diff --git a/packages/metaharness/tsconfig.json b/packages/metaharness/tsconfig.json new file mode 100644 index 0000000..fac6008 --- /dev/null +++ b/packages/metaharness/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "lib": ["ES2022", "DOM", "DOM.Iterable"] + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..51ca6fe --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1374 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +catalogs: + default: + '@types/node': + specifier: ^22.20.0 + version: 22.20.5 + ai: + specifier: ^7.0.0 + version: 7.0.130 + tsx: + specifier: ^4.22.4 + version: 4.23.15 + typescript: + specifier: ^6.0.0 + version: 6.0.3 + unstorage: + specifier: ^1.0.0 + version: 1.17.5 + vitest: + specifier: ^4.0.0 + version: 4.1.11 + +importers: + + .: {} + + apps/playground: + dependencies: + '@ai-sdk/openai-compatible': + specifier: ^3.0.0 + version: 3.0.65(zod@4.6.5) + '@any-harness/metaharness': + specifier: workspace:* + version: link:../../packages/metaharness + ai: + specifier: 'catalog:' + version: 7.0.130(zod@4.6.5) + unstorage: + specifier: 'catalog:' + version: 1.17.5 + devDependencies: + '@types/node': + specifier: 'catalog:' + version: 22.20.5 + tsx: + specifier: 'catalog:' + version: 4.23.15 + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.11(@types/node@22.20.5)(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + + packages/cli: + devDependencies: + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.11(@types/node@22.20.5)(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + + packages/metaharness: + dependencies: + ai: + specifier: 'catalog:' + version: 7.0.130(zod@4.6.5) + devDependencies: + '@ai-sdk/provider': + specifier: ^4.0.0 + version: 4.0.24 + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.11(@types/node@22.20.5)(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + + packages/sdk: + devDependencies: + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.11(@types/node@22.20.5)(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + + packages/spec: + devDependencies: + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.11(@types/node@22.20.5)(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + +packages: + + '@ai-sdk/gateway@4.0.106': + resolution: {integrity: sha512-NprSdFzyVnbEp2SDWtIgsCFIUHFCehOD9S6FQkF/dLrCmnke8w4SOK3KcmUUiEbwtVzlUF8prkaELp+ohcQaQw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai-compatible@3.0.65': + resolution: {integrity: sha512-TBfQk1hf9hOioOwdeDvuXdbmFDp+uLvYf5lfu/gCKVFy6rzjpEqTekEln2ebYonrnq2lIglsuj9S9IovaGhDZA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@5.0.56': + resolution: {integrity: sha512-F7/U1YpVXEfSxRHbxScm0PVdr9lN6GaUWHnt8ws2UjuGeuxWHqCkV25dGpHAkpDHkuIXoC+aak/OqSylKSJmHw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@4.0.24': + resolution: {integrity: sha512-iztFqJTdxNN0ZzqZAAnVPMhRMaL1bfQFa3GIPAffWQ0+dP2FXIkyMD8kHeEI7EpB/C6TR45cqOkrS7YZZ1sATA==} + engines: {node: '>=22'} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + + '@rolldown/binding-android-arm-eabi@1.2.12': + resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.12': + resolution: {integrity: sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.12': + resolution: {integrity: sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.12': + resolution: {integrity: sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.12': + resolution: {integrity: sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + resolution: {integrity: sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + resolution: {integrity: sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.12': + resolution: {integrity: sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + resolution: {integrity: sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + resolution: {integrity: sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.12': + resolution: {integrity: sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.12': + resolution: {integrity: sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.12': + resolution: {integrity: sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + resolution: {integrity: sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.12': + resolution: {integrity: sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@22.20.5': + resolution: {integrity: sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==} + + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + '@workflow/serde@4.1.0': + resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + + ai@7.0.130: + resolution: {integrity: sha512-QkBkCogl46jXUg0SrSsdqQlOI2ZVMCjfnbBQS8t3gE21U/SO17oNMVFyjmcC9z20wOoYUA+2O2vsso0maoS7dw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.5: + resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} + engines: {node: ^10 || ^12 || >=14} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@7.30.0: + resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} + engines: {node: '>=20.18.1'} + + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + vite@8.3.3: + resolution: {integrity: sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + +snapshots: + + '@ai-sdk/gateway@4.0.106(zod@4.6.5)': + dependencies: + '@ai-sdk/provider': 4.0.24 + '@ai-sdk/provider-utils': 5.0.56(zod@4.6.5) + '@vercel/oidc': 3.2.0 + zod: 4.6.5 + + '@ai-sdk/openai-compatible@3.0.65(zod@4.6.5)': + dependencies: + '@ai-sdk/provider': 4.0.24 + '@ai-sdk/provider-utils': 5.0.56(zod@4.6.5) + zod: 4.6.5 + + '@ai-sdk/provider-utils@5.0.56(zod@4.6.5)': + dependencies: + '@ai-sdk/provider': 4.0.24 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.1.1 + undici: 7.30.0 + zod: 4.6.5 + + '@ai-sdk/provider@4.0.24': + dependencies: + json-schema: 0.4.0 + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@oxc-project/types@0.152.0': {} + + '@rolldown/binding-android-arm-eabi@1.2.12': + optional: true + + '@rolldown/binding-android-arm64@1.2.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.12': + optional: true + + '@rolldown/binding-darwin-x64@1.2.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.12': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.12': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@22.20.5': + dependencies: + undici-types: 6.21.0 + + '@vercel/oidc@3.2.0': {} + + '@vitest/expect@4.1.11': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.3.0 + tinyrainbow: 3.2.0 + + '@vitest/mocker@4.1.11(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15) + + '@vitest/pretty-format@4.1.11': + dependencies: + tinyrainbow: 3.2.0 + + '@vitest/runner@4.1.11': + dependencies: + '@vitest/utils': 4.1.11 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.11': {} + + '@vitest/utils@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + + '@workflow/serde@4.1.0': {} + + ai@7.0.130(zod@4.6.5): + dependencies: + '@ai-sdk/gateway': 4.0.106(zod@4.6.5) + '@ai-sdk/provider': 4.0.24 + '@ai-sdk/provider-utils': 5.0.56(zod@4.6.5) + zod: 4.6.5 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + assertion-error@2.0.1: {} + + chai@6.3.0: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + convert-source-map@2.0.0: {} + + cookie-es@1.2.3: {} + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + defu@6.1.7: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + es-module-lexer@2.3.2: {} + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + eventsource-parser@3.1.1: {} + + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + h3@1.15.11: + dependencies: + cookie-es: 1.2.3 + crossws: 0.3.5 + defu: 6.1.7 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.5 + radix3: 1.1.2 + ufo: 1.6.4 + uncrypto: 0.1.3 + + iron-webcrypto@1.2.1: {} + + json-schema@0.4.0: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@11.5.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + nanoid@3.3.20: {} + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.5: {} + + normalize-path@3.0.0: {} + + obug@2.2.1: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.4 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + postcss@8.5.29: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + radix3@1.1.2: {} + + readdirp@5.1.1: {} + + rolldown@1.2.12: + dependencies: + '@oxc-project/types': 0.152.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.12 + '@rolldown/binding-android-arm64': 1.2.12 + '@rolldown/binding-darwin-arm64': 1.2.12 + '@rolldown/binding-darwin-x64': 1.2.12 + '@rolldown/binding-freebsd-x64': 1.2.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.12 + '@rolldown/binding-linux-arm64-gnu': 1.2.12 + '@rolldown/binding-linux-arm64-musl': 1.2.12 + '@rolldown/binding-linux-ppc64-gnu': 1.2.12 + '@rolldown/binding-linux-s390x-gnu': 1.2.12 + '@rolldown/binding-linux-x64-gnu': 1.2.12 + '@rolldown/binding-linux-x64-musl': 1.2.12 + '@rolldown/binding-openharmony-arm64': 1.2.12 + '@rolldown/binding-win32-arm64-msvc': 1.2.12 + '@rolldown/binding-win32-x64-msvc': 1.2.12 + + siginfo@2.0.0: {} + + source-map-js@1.2.2: {} + + stackback@0.0.2: {} + + std-env@4.3.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.2.0: {} + + tsx@4.23.15: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + uncrypto@0.1.3: {} + + undici-types@6.21.0: {} + + undici@7.30.0: {} + + unstorage@1.17.5: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.11 + lru-cache: 11.5.3 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.4 + + vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.12 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.20.5 + esbuild: 0.28.2 + fsevents: 2.3.3 + tsx: 4.23.15 + + vitest@4.1.11(@types/node@22.20.5)(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.2.1 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 2.9.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tinyrainbow: 3.2.0 + vite: 8.3.3(@types/node@22.20.5)(esbuild@0.28.2)(tsx@4.23.15) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.5 + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + zod@4.6.5: {}