diff --git a/hindsight-docs/docs-integrations/paperclip.md b/hindsight-docs/docs-integrations/paperclip.md index 79a954a3a8..15bd0a759a 100644 --- a/hindsight-docs/docs-integrations/paperclip.md +++ b/hindsight-docs/docs-integrations/paperclip.md @@ -1,163 +1,87 @@ --- sidebar_position: 11 title: "Paperclip Persistent Memory with Hindsight | Integration Guide" -description: "Add long-term memory to Paperclip agents with Hindsight. Retain, recall, and reflect memories across sessions using the Paperclip integration." +description: "Add long-term memory to all Paperclip agents with Hindsight. Install once as a plugin — every agent gets automatic recall before runs and retain after runs." --- # Paperclip Persistent memory for [Paperclip AI](https://github.com/paperclipai/paperclip) agents using [Hindsight](https://hindsight.vectorize.io). -Paperclip agents start every heartbeat cold — no memory of prior sessions, decisions, or patterns. The `@vectorize-io/hindsight-paperclip` package gives them long-term memory that persists across heartbeats and sessions. +Install the `@vectorize-io/hindsight-paperclip` plugin once. Every agent in your Paperclip instance automatically gets long-term memory that persists across runs, companies, and restarts — no code changes required. -## Quick Start +## Installation ```bash -npm install @vectorize-io/hindsight-paperclip +pnpm paperclipai plugin install @vectorize-io/hindsight-paperclip ``` -```typescript -import { recall, retain, loadConfig } from '@vectorize-io/hindsight-paperclip' - -const config = loadConfig() // reads HINDSIGHT_API_URL, HINDSIGHT_API_TOKEN - -// Before the heartbeat — inject context from prior sessions -const memories = await recall({ - companyId, - agentId, - query: `${task.title}\n${task.description}`, -}, config) - -if (memories) { - systemPrompt = `Past context:\n${memories}\n\n${systemPrompt}` -} - -// After the heartbeat — store what the agent did -await retain({ - companyId, - agentId, - content: agentOutput, - documentId: runId, -}, config) -``` +Then configure in **Settings → Plugins → Hindsight Memory**. -Get an API key at [Hindsight Cloud](https://ui.hindsight.vectorize.io/signup). +## Prerequisites -## How It Works +Either: +```bash +# Self-hosted +pip install hindsight-all +export HINDSIGHT_API_LLM_API_KEY=your-openai-key +hindsight-api ``` -Paperclip Heartbeat - │ - ▼ - recall() ← Query Hindsight for prior context - │ - ▼ - Agent executes ← Prompt enriched with memories - │ - ▼ - retain() ← Store output for future heartbeats -``` - -Memory is isolated per company and agent by default (`paperclip::{companyId}::{agentId}`), matching Paperclip's multi-tenant model. - -## HTTP Adapter Integration -For agents running as HTTP webhook servers, use the Express middleware: +Or [Hindsight Cloud](https://ui.hindsight.vectorize.io/signup) — no self-hosting required. -```typescript -import express from 'express' -import { createMemoryMiddleware, loadConfig } from '@vectorize-io/hindsight-paperclip' -import type { HindsightRequest } from '@vectorize-io/hindsight-paperclip' - -const app = express() -app.use(express.json()) -app.use(createMemoryMiddleware(loadConfig())) - -app.post('/heartbeat', async (req, res) => { - const { memories } = (req as HindsightRequest).hindsight - const { context } = req.body - - const prompt = memories - ? `Past context:\n${memories}\n\nCurrent task: ${context.taskDescription}` - : `Task: ${context.taskDescription}` +## How It Works - const output = await runYourAgent(prompt) - res.json({ output }) // output is auto-retained by middleware -}) ``` +agent.run.started + └─ recall(issueTitle + description) + └─ cached in plugin state for this run -The middleware reads `agentId`, `companyId`, `runId`, and `context.taskDescription` from Paperclip's HTTP adapter request body, then auto-retains the agent's `output` field after each response. - -## Process Adapter Integration +agent running… + ├─ hindsight_recall(query) → returns cached context or live recall + └─ hindsight_retain(content) → stores immediately -For agents running as scripts via Paperclip's Process adapter: - -```typescript -import { recall, retain, loadConfig } from '@vectorize-io/hindsight-paperclip' - -const config = loadConfig() -const { PAPERCLIP_AGENT_ID, PAPERCLIP_COMPANY_ID, PAPERCLIP_RUN_ID } = process.env - -const memories = await recall({ - agentId: PAPERCLIP_AGENT_ID!, - companyId: PAPERCLIP_COMPANY_ID!, - query: process.env.TASK_DESCRIPTION ?? '', -}, config) - -if (memories) { - console.log(`[Memory Context]\n${memories}`) -} - -// ... agent executes ... - -await retain({ - agentId: PAPERCLIP_AGENT_ID!, - companyId: PAPERCLIP_COMPANY_ID!, - content: agentOutput, - documentId: PAPERCLIP_RUN_ID!, -}, config) +agent.run.finished + └─ retain(output) → stored with runId as document_id ``` -## Bank ID Isolation +Memory is keyed to `companyId` + `agentId` — never to the run ID — so it accumulates across every run. -By default, each company+agent pair gets its own memory bank: - -| Setting | Bank ID format | -|---|---| -| Default | `paperclip::{companyId}::{agentId}` | -| Company-only | `paperclip::{companyId}` | -| Agent-only | `paperclip::{agentId}` | -| Custom prefix | `{prefix}::{companyId}::{agentId}` | +## Configuration -```typescript -// Shared memory across all agents in a company -loadConfig({ bankGranularity: ['company'] }) +| Field | Default | Description | +|-------|---------|-------------| +| `hindsightApiUrl` | `http://localhost:8888` | Hindsight server URL | +| `hindsightApiKeyRef` | — | Paperclip secret name holding Hindsight Cloud API key | +| `bankGranularity` | `["company", "agent"]` | Memory isolation: per company+agent, per company, or per agent | +| `recallBudget` | `mid` | `low` = fastest, `mid` = balanced, `high` = most thorough | +| `autoRetain` | `true` | Automatically retain run output after every run | -// Agent's global memory across all companies -loadConfig({ bankGranularity: ['agent'] }) +## Bank ID Format -// Custom prefix -loadConfig({ bankIdPrefix: 'myapp' }) +``` +paperclip::{companyId}::{agentId} ← default (company + agent granularity) +paperclip::{companyId} ← company granularity (shared across agents) +paperclip::{agentId} ← agent granularity (agent memory across companies) ``` -## Configuration +## Agent Tools + +Agents can call these tools directly during a run: -| Option | Env Variable | Default | Description | -|---|---|---|---| -| `hindsightApiUrl` | `HINDSIGHT_API_URL` | Required | Hindsight server URL | -| `hindsightApiToken` | `HINDSIGHT_API_TOKEN` | — | API token for Hindsight Cloud | -| `bankGranularity` | — | `['company', 'agent']` | Which IDs to include in the bank ID | -| `bankIdPrefix` | — | `'paperclip'` | Prefix for bank IDs | -| `recallBudget` | — | `'mid'` | Search depth: `low`, `mid`, or `high` | -| `recallMaxTokens` | — | `1024` | Max tokens in recalled memory block | -| `retainContext` | — | `'paperclip'` | Provenance label stored with memories | -| `timeoutMs` | — | `15000` | Request timeout in milliseconds | +**`hindsight_recall(query)`** — search memory for relevant context. Called automatically at run start; agents can also call it mid-run for targeted queries. -## Skill File +**`hindsight_retain(content)`** — store a fact or decision immediately, without waiting for run end. -A markdown skill file is included at `src/skills/hindsight.md`. Inject it into your agent's system prompt to give the agent direct access to Hindsight's REST API via `curl` for mid-task recall and retention. +## Adapter Compatibility -## Requirements +Works with all Paperclip adapter types via the event system: -- Node.js 20+ (uses native `fetch`, no external HTTP dependencies) -- Hindsight server (self-hosted or [Hindsight Cloud](https://hindsight.vectorize.io)) +| Adapter | Supported | +|---------|-----------| +| Claude | ✓ | +| Codex | ✓ | +| Cursor | ✓ | +| HTTP | ✓ | +| Process | ✓ | diff --git a/hindsight-docs/src/pages/changelog/integrations/paperclip.md b/hindsight-docs/src/pages/changelog/integrations/paperclip.md index 6d9ebd8cf3..ba5962daf5 100644 --- a/hindsight-docs/src/pages/changelog/integrations/paperclip.md +++ b/hindsight-docs/src/pages/changelog/integrations/paperclip.md @@ -10,6 +10,21 @@ For the source code, see [`hindsight-integrations/paperclip`](https://github.com ← [Back to main changelog](/changelog) +## [0.2.0](https://github.com/vectorize-io/hindsight/tree/integrations/paperclip/v0.2.0) + +**Breaking Changes** + +- Rewritten as a proper Paperclip plugin (installed via `pnpm paperclipai plugin install`). No code changes required — memory hooks run automatically via the event system. +- Works with all adapter types (Claude, Codex, Cursor, HTTP, Process). Previously required manual `recall()`/`retain()` calls and only supported HTTP adapter agents. + +**Features** + +- `agent.run.started` hook: auto-recalls context keyed to issue title + description +- `agent.run.finished` hook: auto-retains agent output with `runId` as document ID +- `hindsight_recall` and `hindsight_retain` agent tools for mid-run memory access +- `onValidateConfig`: live connectivity check when operator saves settings +- Configurable bank granularity (company+agent, company-only, agent-only) + ## [0.1.2](https://github.com/vectorize-io/hindsight/tree/integrations/paperclip/v0.1.2) **Improvements** diff --git a/hindsight-integrations/paperclip/README.md b/hindsight-integrations/paperclip/README.md index 8bf26bcd0a..6f3a632c54 100644 --- a/hindsight-integrations/paperclip/README.md +++ b/hindsight-integrations/paperclip/README.md @@ -1,159 +1,91 @@ # @vectorize-io/hindsight-paperclip -Persistent memory for [Paperclip AI](https://github.com/paperclipai/paperclip) agents using [Hindsight](https://hindsight.vectorize.io). +Persistent long-term memory for Paperclip agents via [Hindsight](https://github.com/vectorize-io/hindsight). -Paperclip agents start every heartbeat cold — no memory of prior sessions, decisions, or patterns. This package gives them long-term memory that persists across heartbeats and sessions. +Install once. Every agent in your Paperclip instance gets memory that persists across runs, companies, and restarts. -## How It Works - -1. **Before each heartbeat**: `recall()` queries Hindsight for context relevant to the current task and injects it into the agent's prompt -2. **After each heartbeat**: `retain()` stores the agent's output so future heartbeats can reference it +## What It Does -Memory is isolated per company and agent by default (`paperclip::{companyId}::{agentId}`), matching Paperclip's multi-tenant model. +- **Before each run** — recalls relevant memories from past runs and caches them for the agent +- **After each run** — retains the agent's output to Hindsight automatically +- **Agent tools** — `hindsight_recall` and `hindsight_retain` tools for agents to query and store memory mid-run ## Installation ```bash -npm install @vectorize-io/hindsight-paperclip +pnpm paperclipai plugin install @vectorize-io/hindsight-paperclip ``` -## Configuration +Then configure in **Settings → Plugins → Hindsight Memory**. -Set environment variables (or pass as options to `loadConfig()`): +## Prerequisites -| Variable | Description | Default | -| --------------------- | ----------------------------- | -------- | -| `HINDSIGHT_API_URL` | Hindsight server URL | Required | -| `HINDSIGHT_API_TOKEN` | API token for Hindsight Cloud | — | +Either: -## Usage - -### HTTP Adapter Agents (Express middleware) +```bash +# Self-hosted (runs locally) +pip install hindsight-all +export HINDSIGHT_API_LLM_API_KEY=your-openai-key +hindsight-api +``` -```typescript -import express from "express"; -import { createMemoryMiddleware, loadConfig } from "@vectorize-io/hindsight-paperclip"; -import type { HindsightRequest } from "@vectorize-io/hindsight-paperclip"; +Or [Hindsight Cloud](https://ui.hindsight.vectorize.io/signup) — no self-hosting required. -const app = express(); -app.use(express.json()); -app.use(createMemoryMiddleware(loadConfig())); +## Configuration -app.post("/heartbeat", async (req, res) => { - const { memories, runId } = (req as HindsightRequest).hindsight; - const { context } = req.body; +| Field | Default | Description | +| -------------------- | ----------------------- | -------------------------------------------------------------- | +| `hindsightApiUrl` | `http://localhost:8888` | Hindsight server URL | +| `hindsightApiKeyRef` | — | Paperclip secret name holding Hindsight Cloud API key | +| `bankGranularity` | `["company", "agent"]` | Memory isolation: per company+agent, per company, or per agent | +| `recallBudget` | `mid` | `low` = fastest, `mid` = balanced, `high` = most thorough | +| `autoRetain` | `true` | Automatically retain run output after every run | - const prompt = memories - ? `Past context:\n${memories}\n\nCurrent task: ${context.taskDescription}` - : `Task: ${context.taskDescription}`; +## Bank ID Format - const output = await runYourAgent(prompt); - res.json({ output }); // middleware auto-retains output -}); ``` - -The middleware reads `agentId`, `companyId`, `runId`, and `context.taskDescription` from the Paperclip HTTP adapter request body automatically. - -### Process Adapter Scripts - -```typescript -import { recall, retain, loadConfig } from "@vectorize-io/hindsight-paperclip"; - -const config = loadConfig(); -const { PAPERCLIP_AGENT_ID, PAPERCLIP_COMPANY_ID, PAPERCLIP_RUN_ID } = process.env; - -// Recall before executing -const memories = await recall( - { - agentId: PAPERCLIP_AGENT_ID!, - companyId: PAPERCLIP_COMPANY_ID!, - query: process.env.TASK_DESCRIPTION ?? "", - }, - config -); - -if (memories) { - console.log(`[Memory Context]\n${memories}`); -} - -// ... agent does its work ... - -// Retain after -await retain( - { - agentId: PAPERCLIP_AGENT_ID!, - companyId: PAPERCLIP_COMPANY_ID!, - content: agentOutput, - documentId: PAPERCLIP_RUN_ID!, - }, - config -); +paperclip::{companyId}::{agentId} ← default (company + agent granularity) +paperclip::{companyId} ← company granularity (shared across agents) +paperclip::{agentId} ← agent granularity (agent memory across companies) ``` -### Direct Function Usage - -```typescript -import { recall, retain, loadConfig } from "@vectorize-io/hindsight-paperclip"; - -const config = loadConfig({ - hindsightApiUrl: "https://api.hindsight.vectorize.io", - hindsightApiToken: process.env.HINDSIGHT_API_TOKEN, -}); +## Agent Tools -const memories = await recall( - { companyId, agentId, query: `${task.title}\n${task.description}` }, - config -); +Agents can call these tools directly during a run: -if (memories) { - systemPrompt = `Past context:\n${memories}\n\n${systemPrompt}`; -} -``` +**`hindsight_recall(query)`** — search memory for relevant context. Called automatically at run start; agents can also call it mid-run for targeted queries. -## Bank ID Isolation +**`hindsight_retain(content)`** — store a fact or decision immediately, without waiting for run end. -By default, each company+agent pair gets its own memory bank: +## How It Works ``` -paperclip::{companyId}::{agentId} -``` - -You can change the isolation granularity: - -```typescript -// Shared memory across all agents in a company -loadConfig({ bankGranularity: ["company"] }); -// → "paperclip::{companyId}" +agent.run.started + └─ recall(issueTitle + description) + └─ store in plugin state for this run (instant lookup by tools) -// Agent's global memory across all companies -loadConfig({ bankGranularity: ["agent"] }); -// → "paperclip::{agentId}" +agent running… + ├─ hindsight_recall(query) → returns cached context or live recall + └─ hindsight_retain(content) → stores immediately -// Custom prefix -loadConfig({ bankIdPrefix: "myapp" }); -// → "myapp::{companyId}::{agentId}" +agent.run.finished + └─ retain(output) → stored in Hindsight with runId as document_id ``` -## Configuration Reference - -```typescript -interface PaperclipMemoryConfig { - hindsightApiUrl: string; // HINDSIGHT_API_URL — required - hindsightApiToken?: string; // HINDSIGHT_API_TOKEN - bankGranularity?: ("company" | "agent")[]; // default: ['company', 'agent'] - bankIdPrefix?: string; // default: 'paperclip' - recallBudget?: "low" | "mid" | "high"; // default: 'mid' - recallMaxTokens?: number; // default: 1024 - retainContext?: string; // default: 'paperclip' - timeoutMs?: number; // default: 15000 -} -``` +Memory is keyed to `companyId` + `agentId`, never to the Paperclip session or run ID — so it survives across any number of runs. -## Skill File +## Development -An agent-readable skill file is included at `src/skills/hindsight.md`. Inject it into your agent's system prompt or as a Paperclip skill to give the agent direct access to Hindsight's REST API via `curl`. +```bash +npm install +npm run build +npm test +``` -## Requirements +Local install into a running Paperclip instance: -- Node.js 20+ (uses native `fetch`) -- Hindsight server (self-hosted or [Hindsight Cloud](https://hindsight.vectorize.io)) +```bash +curl -X POST http://127.0.0.1:3100/api/plugins/install \ + -H "Content-Type: application/json" \ + -d '{"packageName":"/absolute/path/to/hindsight-integrations/paperclip","isLocalPath":true}' +``` diff --git a/hindsight-integrations/paperclip/esbuild.config.mjs b/hindsight-integrations/paperclip/esbuild.config.mjs new file mode 100644 index 0000000000..f540165484 --- /dev/null +++ b/hindsight-integrations/paperclip/esbuild.config.mjs @@ -0,0 +1,25 @@ +import esbuild from "esbuild"; + +const watch = process.argv.includes("--watch"); + +const sharedConfig = { + bundle: true, + platform: "node", + target: "node20", + format: "esm", + external: ["@paperclipai/plugin-sdk"], +}; + +const builds = [ + { entryPoints: ["src/manifest.ts"], outfile: "dist/manifest.js" }, + { entryPoints: ["src/worker.ts"], outfile: "dist/worker.js" }, +]; + +if (watch) { + const contexts = await Promise.all(builds.map((b) => esbuild.context({ ...sharedConfig, ...b }))); + await Promise.all(contexts.map((ctx) => ctx.watch())); + console.log("Watching for changes…"); +} else { + await Promise.all(builds.map((b) => esbuild.build({ ...sharedConfig, ...b }))); + console.log("Build complete."); +} diff --git a/hindsight-integrations/paperclip/package-lock.json b/hindsight-integrations/paperclip/package-lock.json index 661dc2b1ae..97b1c9096b 100644 --- a/hindsight-integrations/paperclip/package-lock.json +++ b/hindsight-integrations/paperclip/package-lock.json @@ -1,30 +1,24 @@ { - "name": "@vectorize-io/hindsight-paperclip", - "version": "0.1.1", + "name": "paperclip-plugin-hindsight", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@vectorize-io/hindsight-paperclip", - "version": "0.1.1", + "name": "paperclip-plugin-hindsight", + "version": "0.1.0", "license": "MIT", + "dependencies": { + "@paperclipai/plugin-sdk": "^2026.403.0" + }, "devDependencies": { - "@types/express": "^5.0.0", "@types/node": "^20.0.0", - "express": "^5.0.0", + "esbuild": "^0.25.0", "typescript": "^5.3.0", "vitest": "^4.0.0" }, "engines": { "node": ">=20" - }, - "peerDependencies": { - "express": ">=4" - }, - "peerDependenciesMeta": { - "express": { - "optional": true - } } }, "node_modules/@emnapi/core": { @@ -61,48 +55,63 @@ "tslib": "^2.4.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@oxc-project/types": { - "version": "0.123.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", - "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", @@ -111,13 +120,13 @@ "android" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -128,13 +137,13 @@ "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -145,13 +154,30 @@ "darwin" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -162,13 +188,13 @@ "freebsd" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", - "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -179,13 +205,13 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -196,15 +222,15 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", @@ -213,15 +239,15 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ - "ppc64" + "loong64" ], "dev": true, "license": "MIT", @@ -230,15 +256,15 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ - "s390x" + "mips64el" ], "dev": true, "license": "MIT", @@ -247,15 +273,15 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", @@ -264,15 +290,15 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ - "x64" + "riscv64" ], "dev": true, "license": "MIT", @@ -281,49 +307,47 @@ "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ - "arm64" + "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "linux" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", - "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ - "wasm32" + "x64" ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "1.9.1", - "@emnapi/runtime": "1.9.1", - "@napi-rs/wasm-runtime": "^1.1.2" - }, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -331,16 +355,16 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "netbsd" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -348,512 +372,635 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "netbsd" ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", - "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "node_modules/@oxc-project/types": { + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", + "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", "dev": true, "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", - "dev": true, + "node_modules/@paperclipai/plugin-sdk": { + "version": "2026.403.0", + "resolved": "https://registry.npmjs.org/@paperclipai/plugin-sdk/-/plugin-sdk-2026.403.0.tgz", + "integrity": "sha512-8WmsX1rGk1ubG41l26pvEyuaLBQgdWQ/s3QL9XMU3rcwItkMKGhopy9b4uPbwJMMv3gbRiaXRa2JJz3e+P3OgA==", "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "@paperclipai/shared": "2026.403.0", + "zod": "^3.24.2" }, - "funding": { - "url": "https://opencollective.com/vitest" + "bin": { + "paperclip-plugin-dev-server": "dist/dev-cli.js" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "react": ">=18" }, "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { + "react": { "optional": true } } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", - "dev": true, + "node_modules/@paperclipai/shared": { + "version": "2026.403.0", + "resolved": "https://registry.npmjs.org/@paperclipai/shared/-/shared-2026.403.0.tgz", + "integrity": "sha512-wAwG5Bv0JEg0Itqf41niH5hAyV/aDA7n5ZRlHdMfmfMA4pcLKxZUyRhduFVJvgHtAW3UJwR/QA2rGQxxrzJrTw==", "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "zod": "^3.24.2" } }, - "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.2", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", + "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", + "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", + "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", + "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", + "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", + "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.1", + "@emnapi/runtime": "1.9.1", + "@napi-rs/wasm-runtime": "^1.1.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", + "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", + "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", + "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.6.0" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@vitest/expect": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.3.tgz", + "integrity": "sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.3.tgz", + "integrity": "sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/@vitest/runner": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.3.tgz", + "integrity": "sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.3", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/@vitest/snapshot": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.3.tgz", + "integrity": "sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "@vitest/pretty-format": "4.1.3", + "@vitest/utils": "4.1.3", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "node_modules/@vitest/spy": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.3.tgz", + "integrity": "sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/@vitest/utils": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@vitest/pretty-format": "4.1.3", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, "node_modules/es-module-lexer": { @@ -863,26 +1010,48 @@ "dev": true, "license": "MIT" }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -893,16 +1062,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -913,50 +1072,6 @@ "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -975,54 +1090,12 @@ } } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1032,156 +1105,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -1453,73 +1376,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1539,29 +1395,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -1573,50 +1406,6 @@ ], "license": "MIT" }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", - "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1673,62 +1462,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/rolldown": { "version": "1.0.0-rc.13", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz", @@ -1763,323 +1496,743 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 18" + "node": ">=0.10.0" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.3.tgz", + "integrity": "sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "@vitest/expect": "4.1.3", + "@vitest/mocker": "4.1.3", + "@vitest/pretty-format": "4.1.3", + "@vitest/runner": "4.1.3", + "@vitest/snapshot": "4.1.3", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">= 0.4" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/vitest" + }, + "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.3", + "@vitest/browser-preview": "4.1.3", + "@vitest/browser-webdriverio": "4.1.3", + "@vitest/coverage-istanbul": "4.1.3", + "@vitest/coverage-v8": "4.1.3", + "@vitest/ui": "4.1.3", + "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 + }, + "vite": { + "optional": false + } } }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, "engines": { "node": ">=18" } }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=18" } }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">=0.6" + "node": ">=18" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, "engines": { - "node": ">=14.17" + "node": ">=18" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.3.tgz", + "integrity": "sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@vitest/spy": "4.1.3", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vitest/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.6.tgz", - "integrity": "sha512-jeOXoY6N8rOfit/mZADMd0misLqjRdWBB3/S23ZQNuPcbVsfMBJutWD8b4ftdczMOsNyMBnKro0Z1Kt0HIqq5Q==", + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz", + "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", "dev": true, "license": "MIT", "dependencies": { @@ -2154,88 +2307,6 @@ } } }, - "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "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.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", - "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/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -2253,12 +2324,14 @@ "node": ">=8" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/hindsight-integrations/paperclip/package.json b/hindsight-integrations/paperclip/package.json index 93036ff941..c66be76c88 100644 --- a/hindsight-integrations/paperclip/package.json +++ b/hindsight-integrations/paperclip/package.json @@ -1,16 +1,19 @@ { "name": "@vectorize-io/hindsight-paperclip", - "version": "0.1.2", - "description": "Persistent memory for Paperclip AI agents using Hindsight", + "version": "0.2.0", + "description": "Persistent long-term memory for Paperclip agents via Hindsight — recall before every heartbeat, retain after every run", "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "paperclipPlugin": { + "manifest": "./dist/manifest.js", + "worker": "./dist/worker.js" + }, "keywords": [ "paperclip", "hindsight", "memory", "agents", - "ai" + "ai", + "plugin" ], "author": "Vectorize ", "license": "MIT", @@ -24,32 +27,24 @@ "README.md" ], "scripts": { - "build": "tsc", - "dev": "tsc --watch", + "build": "node esbuild.config.mjs", + "dev": "node esbuild.config.mjs --watch", "clean": "rm -rf dist", "test": "vitest run tests", "test:watch": "vitest tests", + "typecheck": "tsc --noEmit", "prepublishOnly": "npm run clean && npm run build" }, - "peerDependencies": { - "express": ">=4" - }, - "peerDependenciesMeta": { - "express": { - "optional": true - } + "dependencies": { + "@paperclipai/plugin-sdk": "^2026.403.0" }, "devDependencies": { - "@types/express": "^5.0.0", "@types/node": "^20.0.0", - "express": "^5.0.0", + "esbuild": "^0.25.0", "typescript": "^5.3.0", "vitest": "^4.0.0" }, "engines": { "node": ">=20" - }, - "overrides": { - "vite": ">=8.0.5" } } diff --git a/hindsight-integrations/paperclip/src/bank.ts b/hindsight-integrations/paperclip/src/bank.ts index 0e3067f305..99ebd40b98 100644 --- a/hindsight-integrations/paperclip/src/bank.ts +++ b/hindsight-integrations/paperclip/src/bank.ts @@ -1,40 +1,30 @@ /** - * Bank ID derivation for Paperclip agents. + * Bank ID derivation — maps Paperclip company/agent identity onto Hindsight bank IDs. * - * Aligns Hindsight's memory bank model with Paperclip's company/agent isolation. + * Default format: "paperclip::{companyId}::{agentId}" + * + * bankGranularity: ['company'] → "paperclip::{companyId}" + * bankGranularity: ['agent'] → "paperclip::{agentId}" + * bankGranularity: ['company','agent'] → "paperclip::{companyId}::{agentId}" */ -import type { PaperclipMemoryConfig } from "./config.js"; - export interface BankContext { companyId: string; agentId: string; } -/** - * Derive a Hindsight bank ID from Paperclip context. - * - * Default output: "paperclip::{companyId}::{agentId}" - * - * With bankGranularity: ['company'] → "paperclip::{companyId}" - * With bankGranularity: ['agent'] → "paperclip::{agentId}" - * With bankIdPrefix: '' → "{companyId}::{agentId}" - */ -export function deriveBankId(context: BankContext, config: PaperclipMemoryConfig): string { - const parts: string[] = []; +export interface BankConfig { + bankGranularity?: Array<"company" | "agent">; +} - if (config.bankIdPrefix) { - parts.push(config.bankIdPrefix); - } +export function deriveBankId(context: BankContext, config: BankConfig): string { + const granularity = config.bankGranularity ?? ["company", "agent"]; + const parts: string[] = ["paperclip"]; - for (const field of config.bankGranularity ?? ["company", "agent"]) { + for (const field of granularity) { if (field === "company") parts.push(context.companyId); if (field === "agent") parts.push(context.agentId); } - if (parts.length === 0) { - throw new Error("Bank ID cannot be empty — bankGranularity or bankIdPrefix must be set"); - } - return parts.join("::"); } diff --git a/hindsight-integrations/paperclip/src/client.ts b/hindsight-integrations/paperclip/src/client.ts index 6debf9237c..3d85fedbac 100644 --- a/hindsight-integrations/paperclip/src/client.ts +++ b/hindsight-integrations/paperclip/src/client.ts @@ -1,73 +1,38 @@ /** - * HTTP client for the Hindsight REST API. + * Minimal Hindsight HTTP client for use inside the plugin worker. * * Uses native fetch (Node 20+). No external dependencies. */ -import { readFileSync } from "fs"; -import { dirname, join } from "path"; -import { fileURLToPath } from "url"; -import type { PaperclipMemoryConfig } from "./config.js"; - -function loadPackageVersion(): string { - try { - const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"); - const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version?: string }; - return pkg.version ?? "0.0.0"; - } catch { - return "0.0.0"; - } -} - -// Sent on every request so self-hosted deployments behind Cloudflare (or any -// reverse proxy with UA-based bot filtering) accept the traffic. -const USER_AGENT = `hindsight-paperclip/${loadPackageVersion()}`; - export interface Memory { text: string; type?: string; - mentionedAt?: string; } export interface RecallResponse { results: Memory[]; } -export interface RetainResponse { - success: boolean; - bankId?: string; -} - export class HindsightClient { private readonly baseUrl: string; private readonly token: string | undefined; - private readonly timeoutMs: number; - constructor(config: PaperclipMemoryConfig) { - const url = config.hindsightApiUrl.trim(); + constructor(baseUrl: string, token?: string) { + const url = baseUrl.trim(); if (!url) throw new Error("hindsightApiUrl is required"); this.baseUrl = url.replace(/\/$/, ""); - this.token = config.hindsightApiToken; - this.timeoutMs = config.timeoutMs ?? 15_000; + this.token = token; } private headers(): Record { - const h: Record = { - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }; + const h: Record = { "Content-Type": "application/json" }; if (this.token) h["Authorization"] = `Bearer ${this.token}`; return h; } - private async request( - method: string, - path: string, - body?: unknown, - timeoutMs?: number - ): Promise { + private async request(method: string, path: string, body?: unknown): Promise { const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs ?? this.timeoutMs); + const timer = setTimeout(() => controller.abort(), 15_000); try { const resp = await fetch(`${this.baseUrl}${path}`, { @@ -88,43 +53,29 @@ export class HindsightClient { } } - async recall( - bankId: string, - query: string, - options?: { budget?: string; maxTokens?: number } - ): Promise { + async recall(bankId: string, query: string, budget = "mid"): Promise { const path = `/v1/default/banks/${encodeURIComponent(bankId)}/memories/recall`; return this.request("POST", path, { query, - budget: options?.budget ?? "mid", - max_tokens: options?.maxTokens ?? 1024, + budget, + max_tokens: 1024, }); } async retain( bankId: string, content: string, - options?: { - documentId?: string; - context?: string; - metadata?: Record; - tags?: string[]; - } - ): Promise { + documentId?: string, + metadata?: Record + ): Promise { const path = `/v1/default/banks/${encodeURIComponent(bankId)}/memories`; - const item: Record = { content }; - if (options?.documentId) item["document_id"] = options.documentId; - if (options?.context) item["context"] = options.context; - if (options?.metadata) item["metadata"] = options.metadata; - if (options?.tags) item["tags"] = options.tags; - return this.request("POST", path, { items: [item], async: true }); - } - - async setBankMission(bankId: string, mission: string, retainMission?: string): Promise { - const path = `/v1/default/banks/${encodeURIComponent(bankId)}/config`; - const updates: Record = { reflect_mission: mission }; - if (retainMission) updates["retain_mission"] = retainMission; - await this.request("PATCH", path, { updates }); + const item: Record = { + content, + context: "paperclip", + }; + if (documentId) item["document_id"] = documentId; + if (metadata) item["metadata"] = metadata; + await this.request("POST", path, { items: [item], async: true }); } async health(): Promise { @@ -139,3 +90,8 @@ export class HindsightClient { } } } + +export function formatMemories(memories: Memory[]): string { + if (memories.length === 0) return ""; + return memories.map((m) => `- ${m.text}`).join("\n"); +} diff --git a/hindsight-integrations/paperclip/src/config.ts b/hindsight-integrations/paperclip/src/config.ts deleted file mode 100644 index dc425a309d..0000000000 --- a/hindsight-integrations/paperclip/src/config.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Configuration for @vectorize-io/hindsight-paperclip. - * - * Loaded from explicit options first, then environment variables. - */ - -export type BankGranularity = "company" | "agent"; - -export interface PaperclipMemoryConfig { - /** Hindsight server URL. Required. env: HINDSIGHT_API_URL */ - hindsightApiUrl: string; - /** API token for Hindsight Cloud. env: HINDSIGHT_API_TOKEN */ - hindsightApiToken?: string; - /** - * Which dimensions to include in the bank ID. - * Default: ['company', 'agent'] → "paperclip::{companyId}::{agentId}" - */ - bankGranularity?: BankGranularity[]; - /** Prefix prepended to all bank IDs. Default: "paperclip" */ - bankIdPrefix?: string; - /** Recall search depth. Default: "mid" */ - recallBudget?: "low" | "mid" | "high"; - /** Max tokens in the recalled memory block. Default: 1024 */ - recallMaxTokens?: number; - /** Provenance label stored with each retained document. Default: "paperclip" */ - retainContext?: string; - /** Request timeout in milliseconds. Default: 15000 */ - timeoutMs?: number; -} - -export function loadConfig(overrides?: Partial): PaperclipMemoryConfig { - const config: PaperclipMemoryConfig = { - hindsightApiUrl: process.env["HINDSIGHT_API_URL"] ?? "", - hindsightApiToken: process.env["HINDSIGHT_API_TOKEN"], - bankGranularity: ["company", "agent"], - bankIdPrefix: "paperclip", - recallBudget: "mid", - recallMaxTokens: 1024, - retainContext: "paperclip", - timeoutMs: 15_000, - ...overrides, - }; - if (!config.hindsightApiUrl) { - throw new Error( - "hindsightApiUrl is required — set HINDSIGHT_API_URL or pass hindsightApiUrl to loadConfig()" - ); - } - return config; -} diff --git a/hindsight-integrations/paperclip/src/index.ts b/hindsight-integrations/paperclip/src/index.ts deleted file mode 100644 index f0df3a10fb..0000000000 --- a/hindsight-integrations/paperclip/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @vectorize-io/hindsight-paperclip - * - * Persistent memory for Paperclip AI agents using Hindsight. - * - * @example - * ```typescript - * import { recall, retain, loadConfig } from '@vectorize-io/hindsight-paperclip' - * - * const config = loadConfig() - * - * // Before heartbeat - * const memories = await recall({ companyId, agentId, query }, config) - * - * // After heartbeat - * await retain({ companyId, agentId, content: output, documentId: runId }, config) - * ``` - */ - -export { recall } from "./recall.js"; -export type { RecallInput } from "./recall.js"; - -export { retain } from "./retain.js"; -export type { RetainInput } from "./retain.js"; - -export { createMemoryMiddleware } from "./middleware.js"; -export type { HindsightRequest } from "./middleware.js"; - -export { deriveBankId } from "./bank.js"; -export type { BankContext } from "./bank.js"; - -export { loadConfig } from "./config.js"; -export type { PaperclipMemoryConfig, BankGranularity } from "./config.js"; - -export { HindsightClient } from "./client.js"; -export type { Memory, RecallResponse, RetainResponse } from "./client.js"; diff --git a/hindsight-integrations/paperclip/src/manifest.ts b/hindsight-integrations/paperclip/src/manifest.ts new file mode 100644 index 0000000000..21e99f49dd --- /dev/null +++ b/hindsight-integrations/paperclip/src/manifest.ts @@ -0,0 +1,100 @@ +import type { PaperclipPluginManifestV1 } from "@paperclipai/plugin-sdk"; + +const manifest: PaperclipPluginManifestV1 = { + id: "paperclip-plugin-hindsight", + apiVersion: 1, + version: "0.2.0", + displayName: "Hindsight Memory", + author: "Vectorize ", + description: + "Persistent long-term memory for Paperclip agents. Automatically recalls relevant context before each run and retains agent output after — so every agent gets smarter over time.", + categories: ["automation"], + capabilities: [ + "events.subscribe", + "agent.tools.register", + "plugin.state.read", + "plugin.state.write", + "http.outbound", + "secrets.read-ref", + "agents.read", + ], + entrypoints: { + worker: "./dist/worker.js", + }, + instanceConfigSchema: { + type: "object", + required: ["hindsightApiUrl"], + properties: { + hindsightApiUrl: { + type: "string", + title: "Hindsight API URL", + description: + "Base URL of your Hindsight instance. Use http://localhost:8888 for self-hosted.", + default: "http://localhost:8888", + }, + hindsightApiKeyRef: { + type: "string", + title: "Hindsight API Key (secret ref)", + description: + "Name of the Paperclip secret holding your Hindsight Cloud API key. Leave empty for self-hosted.", + }, + bankGranularity: { + type: "array", + title: "Bank Granularity", + description: + "Controls memory isolation. Default ['company', 'agent'] gives each agent its own bank per company.", + items: { type: "string", enum: ["company", "agent"] }, + default: ["company", "agent"], + }, + recallBudget: { + type: "string", + title: "Recall Budget", + description: "'low' is fastest, 'mid' balances speed and depth, 'high' is most thorough.", + enum: ["low", "mid", "high"], + default: "mid", + }, + autoRetain: { + type: "boolean", + title: "Auto-retain on Run Finished", + description: "Automatically retain agent run output to Hindsight when a run completes.", + default: true, + }, + }, + }, + tools: [ + { + name: "hindsight_recall", + displayName: "Recall from Memory", + description: + "Search Hindsight long-term memory for context relevant to a query. Use this before starting a task to surface relevant past decisions, preferences, and knowledge.", + parametersSchema: { + type: "object", + required: ["query"], + properties: { + query: { + type: "string", + description: "What to search for in memory", + }, + }, + }, + }, + { + name: "hindsight_retain", + displayName: "Save to Memory", + description: + "Store important facts, decisions, or outcomes in Hindsight long-term memory for future runs.", + parametersSchema: { + type: "object", + required: ["content"], + properties: { + content: { + type: "string", + description: "The content to store in memory", + }, + }, + }, + }, + ], +}; + +export default manifest; diff --git a/hindsight-integrations/paperclip/src/middleware.ts b/hindsight-integrations/paperclip/src/middleware.ts deleted file mode 100644 index b1abedc7fe..0000000000 --- a/hindsight-integrations/paperclip/src/middleware.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Express middleware for Paperclip HTTP adapter agents. - * - * Automatically injects recalled memories into each request and - * retains the agent's output after each response. - * - * Paperclip HTTP adapter request shape: - * { - * runId: string, - * agentId: string, - * companyId: string, - * context: { taskId: string, taskDescription?: string, ... } - * } - */ - -import type { Request, Response, NextFunction } from "express"; -import type { PaperclipMemoryConfig } from "./config.js"; -import { recall } from "./recall.js"; -import { retain } from "./retain.js"; - -/** Augmented request with Hindsight memory context. */ -export interface HindsightRequest extends Request { - hindsight: { - memories: string; - companyId: string; - agentId: string; - runId: string; - }; -} - -/** - * Create Express middleware that auto-recalls before each heartbeat - * and auto-retains after each response. - * - * @example - * ```typescript - * import express from 'express' - * import { createMemoryMiddleware, loadConfig } from '@vectorize-io/hindsight-paperclip' - * - * const app = express() - * app.use(express.json()) - * app.use(createMemoryMiddleware(loadConfig())) - * - * app.post('/heartbeat', (req, res) => { - * const { memories, runId } = (req as HindsightRequest).hindsight - * const { context } = req.body - * - * const prompt = memories - * ? `Past context:\n${memories}\n\nCurrent task: ${context.taskDescription}` - * : `Task: ${context.taskDescription}` - * - * // ... run agent ... - * res.json({ output: agentOutput }) // auto-retained by middleware - * }) - * ``` - */ -export function createMemoryMiddleware(config: PaperclipMemoryConfig) { - return async (req: Request, res: Response, next: NextFunction): Promise => { - const { runId, agentId, companyId, context } = req.body ?? {}; - - if (!agentId || !companyId) { - next(); - return; - } - - const query: string = context?.taskDescription ?? context?.taskTitle ?? ""; - - // Pre-recall: inject memories into request - const memories = await recall({ companyId, agentId, query }, config); - (req as HindsightRequest).hindsight = { - memories, - companyId, - agentId, - runId: runId ?? "", - }; - - // Post-retain: wrap res.json to capture agent output - const originalJson = res.json.bind(res) as (body: unknown) => Response; - (res as Response).json = function (body: unknown): Response { - // Fire-and-forget retain (don't block response) - if (body && typeof body === "object" && "output" in body && runId) { - const output = (body as { output: unknown }).output; - if (typeof output === "string" && output.trim()) { - retain({ companyId, agentId, content: output, documentId: runId }, config).catch( - (err) => { - console.warn("[hindsight-paperclip] retain failed:", (err as Error).message); - } - ); - } - } - return originalJson(body); - }; - - next(); - }; -} diff --git a/hindsight-integrations/paperclip/src/recall.ts b/hindsight-integrations/paperclip/src/recall.ts deleted file mode 100644 index 8a95181894..0000000000 --- a/hindsight-integrations/paperclip/src/recall.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Recall memories for a Paperclip agent heartbeat. - * - * Call this before the agent processes a task to inject relevant context - * from prior heartbeats and sessions. - */ - -import { HindsightClient } from "./client.js"; -import type { PaperclipMemoryConfig } from "./config.js"; -import { deriveBankId } from "./bank.js"; - -export interface RecallInput { - /** Paperclip company ID — used to derive the bank ID. */ - companyId: string; - /** Paperclip agent ID — used to derive the bank ID. */ - agentId: string; - /** - * Query string for memory retrieval. Typically the task title + description. - * e.g. `${issue.title}\n${issue.description}` - */ - query: string; -} - -/** - * Retrieve relevant memories for the current Paperclip task. - * - * Returns a formatted string of memories to inject into the agent's prompt, - * or an empty string if no relevant memories are found. - * - * @example - * ```typescript - * const memories = await recall( - * { companyId, agentId, query: `${task.title}\n${task.description}` }, - * loadConfig() - * ) - * if (memories) { - * systemPrompt = `Past context:\n${memories}\n\n${systemPrompt}` - * } - * ``` - */ -export async function recall(input: RecallInput, config: PaperclipMemoryConfig): Promise { - const { companyId, agentId, query } = input; - - if (!query.trim()) return ""; - - const bankId = deriveBankId({ companyId, agentId }, config); - const client = new HindsightClient(config); - - let results: Array<{ text: string; type?: string; mentionedAt?: string }>; - try { - const response = await client.recall(bankId, query, { - budget: config.recallBudget, - maxTokens: config.recallMaxTokens, - }); - results = response.results; - } catch (err) { - console.warn("[hindsight-paperclip] recall failed:", (err as Error).message); - return ""; - } - - if (!results.length) return ""; - - return formatMemories(results); -} - -function formatMemories( - results: Array<{ text: string; type?: string; mentionedAt?: string }> -): string { - return results - .map((r) => { - const typeStr = r.type ? ` [${r.type}]` : ""; - const dateStr = r.mentionedAt ? ` (${r.mentionedAt})` : ""; - return `- ${r.text}${typeStr}${dateStr}`; - }) - .join("\n\n"); -} diff --git a/hindsight-integrations/paperclip/src/retain.ts b/hindsight-integrations/paperclip/src/retain.ts deleted file mode 100644 index fece27f7e7..0000000000 --- a/hindsight-integrations/paperclip/src/retain.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Retain memories after a Paperclip agent heartbeat. - * - * Call this after the agent completes a task to store what it did - * so future heartbeats can recall the context. - */ - -import { HindsightClient } from "./client.js"; -import type { PaperclipMemoryConfig } from "./config.js"; -import { deriveBankId } from "./bank.js"; - -export interface RetainInput { - /** Paperclip company ID — used to derive the bank ID. */ - companyId: string; - /** Paperclip agent ID — used to derive the bank ID. */ - agentId: string; - /** The agent's output or summary of what it did during the heartbeat. */ - content: string; - /** Paperclip run ID — used as document ID to prevent duplicate storage. */ - documentId: string; - /** Additional metadata to store with the memory. */ - metadata?: Record; -} - -/** - * Store the agent's output as a memory after a Paperclip task heartbeat. - * - * Fails silently — memory retention is an enhancement, not a requirement. - * - * @example - * ```typescript - * await retain( - * { companyId, agentId, content: agentOutput, documentId: runId }, - * loadConfig() - * ) - * ``` - */ -export async function retain(input: RetainInput, config: PaperclipMemoryConfig): Promise { - const { companyId, agentId, content, documentId, metadata } = input; - - if (!content.trim()) return; - - const bankId = deriveBankId({ companyId, agentId }, config); - const client = new HindsightClient(config); - - try { - await client.retain(bankId, content, { - documentId, - context: config.retainContext, - metadata: { companyId, agentId, ...metadata }, - }); - } catch (err) { - console.warn("[hindsight-paperclip] retain failed:", (err as Error).message); - } -} diff --git a/hindsight-integrations/paperclip/src/worker.ts b/hindsight-integrations/paperclip/src/worker.ts new file mode 100644 index 0000000000..85e41e0cd0 --- /dev/null +++ b/hindsight-integrations/paperclip/src/worker.ts @@ -0,0 +1,252 @@ +/** + * paperclip-plugin-hindsight — worker entrypoint. + * + * Gives Paperclip agents persistent long-term memory via Hindsight. + * + * Lifecycle: + * agent.run.started → recall relevant memories, store in plugin state for the run + * agent.run.finished → retain agent output to Hindsight (if autoRetain is enabled) + * + * Agent tools (callable mid-run): + * hindsight_recall(query) → search memory, returns relevant context + * hindsight_retain(content) → store content in memory immediately + */ + +import { definePlugin, runWorker } from "@paperclipai/plugin-sdk"; +import type { ToolRunContext } from "@paperclipai/plugin-sdk"; +import { HindsightClient, formatMemories } from "./client.js"; +import { deriveBankId } from "./bank.js"; + +interface PluginConfig { + hindsightApiUrl: string; + hindsightApiKeyRef?: string; + bankGranularity?: Array<"company" | "agent">; + recallBudget?: "low" | "mid" | "high"; + autoRetain?: boolean; +} + +interface RunStartedPayload { + agentId: string; + runId: string; + issueTitle?: string; + issueDescription?: string; +} + +interface RunFinishedPayload { + agentId: string; + runId: string; + output?: string; + result?: string; +} + +async function getConfig(ctx: { + config: { get(): Promise> }; +}): Promise { + return (await ctx.config.get()) as unknown as PluginConfig; +} + +async function resolveApiKey( + ctx: { secrets: { resolve(ref: string): Promise } }, + config: PluginConfig +): Promise { + if (!config.hindsightApiKeyRef) return undefined; + const resolved = await ctx.secrets.resolve(config.hindsightApiKeyRef); + return resolved ?? undefined; +} + +const plugin = definePlugin({ + async setup(ctx) { + ctx.logger.info("Hindsight memory plugin starting"); + + // --------------------------------------------------------------------------- + // agent.run.started — recall memories and cache them for this run + // --------------------------------------------------------------------------- + ctx.events.on("agent.run.started", async (event) => { + const payload = event.payload as RunStartedPayload; + const config = await getConfig(ctx); + const { agentId, runId, issueTitle, issueDescription } = payload; + const companyId = event.companyId; + + const query = [issueTitle, issueDescription].filter(Boolean).join("\n"); + if (!query.trim()) return; + + try { + const apiKey = await resolveApiKey(ctx, config); + const client = new HindsightClient(config.hindsightApiUrl, apiKey); + const bankId = deriveBankId({ companyId, agentId }, config); + + const response = await client.recall(bankId, query, config.recallBudget ?? "mid"); + + const memories = formatMemories(response.results); + if (memories) { + await ctx.state.set( + { scopeKind: "run", scopeId: runId, stateKey: "recalled-memories" }, + memories + ); + ctx.logger.info("Recalled memories for run", { + runId, + bankId, + count: response.results.length, + }); + } + } catch (err) { + // Non-fatal: agent runs without memory context. + ctx.logger.warn("Failed to recall memories on run start", { + runId, + error: String(err), + }); + } + }); + + // --------------------------------------------------------------------------- + // agent.run.finished — retain run output to Hindsight + // --------------------------------------------------------------------------- + ctx.events.on("agent.run.finished", async (event) => { + const payload = event.payload as RunFinishedPayload; + const config = await getConfig(ctx); + + if (config.autoRetain === false) return; + + const { agentId, runId, output, result } = payload; + const companyId = event.companyId; + const content = output ?? result; + + if (!content?.trim()) return; + + try { + const apiKey = await resolveApiKey(ctx, config); + const client = new HindsightClient(config.hindsightApiUrl, apiKey); + const bankId = deriveBankId({ companyId, agentId }, config); + + await client.retain(bankId, content, runId, { agentId, companyId, runId }); + ctx.logger.info("Retained run output to memory", { runId, bankId }); + } catch (err) { + ctx.logger.warn("Failed to retain run output", { + runId, + error: String(err), + }); + } + }); + + // --------------------------------------------------------------------------- + // Tool: hindsight_recall + // --------------------------------------------------------------------------- + ctx.tools.register( + "hindsight_recall", + { + displayName: "Recall from Memory", + description: "Search Hindsight long-term memory for context relevant to a query.", + parametersSchema: { + type: "object", + required: ["query"], + properties: { + query: { type: "string", description: "What to search for" }, + }, + }, + }, + async (params: unknown, runCtx: ToolRunContext) => { + const { query } = params as { query: string }; + const config = await getConfig(ctx); + const bankId = deriveBankId( + { companyId: runCtx.companyId, agentId: runCtx.agentId }, + config + ); + + // Return cached memories from run start if available + const cached = await ctx.state.get({ + scopeKind: "run", + scopeId: runCtx.runId, + stateKey: "recalled-memories", + }); + if (cached && typeof cached === "string") { + return { content: cached }; + } + + // Live recall fallback + try { + const apiKey = await resolveApiKey(ctx, config); + const client = new HindsightClient(config.hindsightApiUrl, apiKey); + const response = await client.recall(bankId, query, config.recallBudget ?? "mid"); + const memories = formatMemories(response.results); + return { content: memories || "No relevant memories found." }; + } catch (err) { + return { content: `Memory recall failed: ${String(err)}` }; + } + } + ); + + // --------------------------------------------------------------------------- + // Tool: hindsight_retain + // --------------------------------------------------------------------------- + ctx.tools.register( + "hindsight_retain", + { + displayName: "Save to Memory", + description: + "Store important facts, decisions, or outcomes in Hindsight long-term memory for future runs.", + parametersSchema: { + type: "object", + required: ["content"], + properties: { + content: { + type: "string", + description: "The content to store in memory", + }, + }, + }, + }, + async (params: unknown, runCtx: ToolRunContext) => { + const { content } = params as { content: string }; + const config = await getConfig(ctx); + const bankId = deriveBankId( + { companyId: runCtx.companyId, agentId: runCtx.agentId }, + config + ); + + try { + const apiKey = await resolveApiKey(ctx, config); + const client = new HindsightClient(config.hindsightApiUrl, apiKey); + await client.retain(bankId, content, undefined, { + agentId: runCtx.agentId, + companyId: runCtx.companyId, + runId: runCtx.runId, + }); + return { content: "Memory saved." }; + } catch (err) { + return { content: `Failed to save memory: ${String(err)}` }; + } + } + ); + + ctx.logger.info("Hindsight memory plugin ready"); + }, + + async onHealth() { + return { status: "ok" }; + }, + + async onValidateConfig(config) { + const c = config as Partial; + if (!c.hindsightApiUrl?.trim()) { + return { ok: false, errors: ["hindsightApiUrl is required"] }; + } + + try { + const client = new HindsightClient(c.hindsightApiUrl); + const healthy = await client.health(); + if (!healthy) { + return { + ok: false, + errors: [`Cannot reach Hindsight at ${c.hindsightApiUrl}`], + }; + } + } catch (err) { + return { ok: false, errors: [`Connection failed: ${String(err)}`] }; + } + + return { ok: true }; + }, +}); + +export default plugin; +runWorker(plugin, import.meta.url); diff --git a/hindsight-integrations/paperclip/tests/bank.test.ts b/hindsight-integrations/paperclip/tests/bank.test.ts deleted file mode 100644 index 756f4285d4..0000000000 --- a/hindsight-integrations/paperclip/tests/bank.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { deriveBankId } from "../src/bank.js"; -import { loadConfig } from "../src/config.js"; - -describe("deriveBankId", () => { - const ctx = { companyId: "co-123", agentId: "ag-456" }; - - const baseUrl = "http://fake:9077"; - - it("default: paperclip::companyId::agentId", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl }); - expect(deriveBankId(ctx, config)).toBe("paperclip::co-123::ag-456"); - }); - - it("company-only granularity", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl, bankGranularity: ["company"] }); - expect(deriveBankId(ctx, config)).toBe("paperclip::co-123"); - }); - - it("agent-only granularity", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl, bankGranularity: ["agent"] }); - expect(deriveBankId(ctx, config)).toBe("paperclip::ag-456"); - }); - - it("custom prefix", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl, bankIdPrefix: "myapp" }); - expect(deriveBankId(ctx, config)).toBe("myapp::co-123::ag-456"); - }); - - it("empty prefix with default granularity", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl, bankIdPrefix: "" }); - expect(deriveBankId(ctx, config)).toBe("co-123::ag-456"); - }); - - it("throws when bank ID would be empty", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl, bankIdPrefix: "", bankGranularity: [] }); - expect(() => deriveBankId(ctx, config)).toThrow("Bank ID cannot be empty"); - }); - - it("reversed granularity order", () => { - const config = loadConfig({ hindsightApiUrl: baseUrl, bankGranularity: ["agent", "company"] }); - expect(deriveBankId(ctx, config)).toBe("paperclip::ag-456::co-123"); - }); -}); diff --git a/hindsight-integrations/paperclip/tests/plugin.spec.ts b/hindsight-integrations/paperclip/tests/plugin.spec.ts new file mode 100644 index 0000000000..6d224363da --- /dev/null +++ b/hindsight-integrations/paperclip/tests/plugin.spec.ts @@ -0,0 +1,349 @@ +/** + * Tests for paperclip-plugin-hindsight. + * + * Uses @paperclipai/plugin-sdk's createTestHarness to simulate the Paperclip + * host environment without requiring a running Paperclip instance. + * + * Hindsight API calls are intercepted via global fetch mocking. + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createTestHarness } from "@paperclipai/plugin-sdk"; +import manifest from "../src/manifest.js"; +import plugin from "../src/worker.js"; + +// --------------------------------------------------------------------------- +// Fetch mock helpers +// --------------------------------------------------------------------------- + +function mockFetch(responses: Array<{ url: string | RegExp; body: unknown; status?: number }>) { + return vi.fn(async (url: string) => { + const match = responses.find((r) => + typeof r.url === "string" ? url.includes(r.url) : r.url.test(url) + ); + if (!match) { + return new Response(JSON.stringify({ error: "unmatched url" }), { status: 404 }); + } + return new Response(JSON.stringify(match.body), { + status: match.status ?? 200, + }); + }); +} + +// --------------------------------------------------------------------------- +// Harness setup +// --------------------------------------------------------------------------- + +const DEFAULT_CONFIG = { + hindsightApiUrl: "http://localhost:8888", + bankGranularity: ["company", "agent"], + recallBudget: "mid", + autoRetain: true, +}; + +function buildHarness(config: Record = DEFAULT_CONFIG) { + return createTestHarness({ manifest, config, capabilities: manifest.capabilities }); +} + +async function setupPlugin(harness: ReturnType) { + await plugin.definition.setup(harness.ctx); +} + +// --------------------------------------------------------------------------- +// Bank ID derivation +// --------------------------------------------------------------------------- + +describe("bank ID derivation", () => { + it("default: company + agent", async () => { + const { deriveBankId } = await import("../src/bank.js"); + expect( + deriveBankId( + { companyId: "co-1", agentId: "ag-1" }, + { bankGranularity: ["company", "agent"] } + ) + ).toBe("paperclip::co-1::ag-1"); + }); + + it("company only", async () => { + const { deriveBankId } = await import("../src/bank.js"); + expect( + deriveBankId({ companyId: "co-1", agentId: "ag-1" }, { bankGranularity: ["company"] }) + ).toBe("paperclip::co-1"); + }); + + it("agent only", async () => { + const { deriveBankId } = await import("../src/bank.js"); + expect( + deriveBankId({ companyId: "co-1", agentId: "ag-1" }, { bankGranularity: ["agent"] }) + ).toBe("paperclip::ag-1"); + }); +}); + +// --------------------------------------------------------------------------- +// agent.run.started — recall +// --------------------------------------------------------------------------- + +describe("agent.run.started", () => { + let fetchMock: ReturnType; + + beforeEach(() => { + fetchMock = mockFetch([ + { url: /recall/, body: { results: [{ text: "User prefers TypeScript" }] } }, + ]); + vi.stubGlobal("fetch", fetchMock); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("calls recall and caches memories in plugin state", async () => { + const harness = buildHarness(); + await setupPlugin(harness); + + await harness.emit( + "agent.run.started", + { + agentId: "ag-1", + runId: "run-1", + issueTitle: "Refactor auth module", + issueDescription: "Migrate to JWT", + }, + { companyId: "co-1" } + ); + + const recallCall = fetchMock.mock.calls.find(([url]: [string]) => url.includes("recall")); + expect(recallCall).toBeDefined(); + expect(recallCall?.[0]).toContain("paperclip%3A%3Aco-1%3A%3Aag-1"); + + const state = harness.getState({ + scopeKind: "run", + scopeId: "run-1", + stateKey: "recalled-memories", + }); + expect(state).toContain("TypeScript"); + }); + + it("skips recall when no issue context provided", async () => { + const harness = buildHarness(); + await setupPlugin(harness); + + await harness.emit( + "agent.run.started", + { agentId: "ag-1", runId: "run-2" }, + { companyId: "co-1" } + ); + + expect(fetchMock).not.toHaveBeenCalled(); + }); + + it("does not throw when Hindsight is unreachable", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response("", { status: 503 })) + ); + const harness = buildHarness(); + await setupPlugin(harness); + + await expect( + harness.emit( + "agent.run.started", + { agentId: "ag-1", runId: "run-3", issueTitle: "Fix bug" }, + { companyId: "co-1" } + ) + ).resolves.not.toThrow(); + }); +}); + +// --------------------------------------------------------------------------- +// agent.run.finished — auto-retain +// --------------------------------------------------------------------------- + +describe("agent.run.finished", () => { + let fetchMock: ReturnType; + + beforeEach(() => { + fetchMock = mockFetch([{ url: /memories$/, body: { success: true } }]); + vi.stubGlobal("fetch", fetchMock); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("retains run output with runId as document ID", async () => { + const harness = buildHarness(); + await setupPlugin(harness); + + await harness.emit( + "agent.run.finished", + { + agentId: "ag-1", + runId: "run-1", + output: "Refactored auth. Migrated to JWT with 24h expiry.", + }, + { companyId: "co-1" } + ); + + const retainCall = fetchMock.mock.calls.find(([url]: [string]) => /memories$/.test(url)); + expect(retainCall).toBeDefined(); + + const body = JSON.parse(retainCall?.[1]?.body as string) as { + items: Array<{ content: string; document_id?: string }>; + }; + expect(body.items[0]?.content).toContain("JWT"); + expect(body.items[0]?.document_id).toBe("run-1"); + }); + + it("skips retain when output is empty", async () => { + const harness = buildHarness(); + await setupPlugin(harness); + + await harness.emit( + "agent.run.finished", + { agentId: "ag-1", runId: "run-2", output: "" }, + { companyId: "co-1" } + ); + + expect(fetchMock).not.toHaveBeenCalled(); + }); + + it("skips retain when autoRetain is false", async () => { + const harness = buildHarness({ ...DEFAULT_CONFIG, autoRetain: false }); + await setupPlugin(harness); + + await harness.emit( + "agent.run.finished", + { agentId: "ag-1", runId: "run-3", output: "Some output" }, + { companyId: "co-1" } + ); + + expect(fetchMock).not.toHaveBeenCalled(); + }); +}); + +// --------------------------------------------------------------------------- +// hindsight_recall tool +// --------------------------------------------------------------------------- + +describe("hindsight_recall tool", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("returns cached memories from run start without additional API call", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response("{}", { status: 200 })) + ); + const harness = buildHarness(); + await setupPlugin(harness); + + // Emit agent.run.started so recall fires and caches state + vi.stubGlobal( + "fetch", + mockFetch([{ url: /recall/, body: { results: [{ text: "User prefers dark mode" }] } }]) + ); + await harness.emit( + "agent.run.started", + { agentId: "ag-1", runId: "run-1", issueTitle: "Update UI" }, + { companyId: "co-1" } + ); + + // Now recall tool should return cached state, not hit the API again + const callsBefore = (vi.mocked(fetch) as ReturnType).mock.calls.length; + const result = await harness.executeTool( + "hindsight_recall", + { query: "preferences" }, + { agentId: "ag-1", runId: "run-1", companyId: "co-1", projectId: "proj-1" } + ); + + expect((result as { content: string }).content).toContain("dark mode"); + const callsAfter = (vi.mocked(fetch) as ReturnType).mock.calls.length; + // No new recall call — returned from cache + expect(callsAfter).toBe(callsBefore); + }); + + it("falls back to live recall when no cached state", async () => { + vi.stubGlobal( + "fetch", + mockFetch([{ url: /recall/, body: { results: [{ text: "Agent is a Python specialist" }] } }]) + ); + const harness = buildHarness(); + await setupPlugin(harness); + + const result = await harness.executeTool( + "hindsight_recall", + { query: "specialization" }, + { agentId: "ag-1", runId: "run-2", companyId: "co-1", projectId: "proj-1" } + ); + + expect((result as { content: string }).content).toContain("Python specialist"); + }); +}); + +// --------------------------------------------------------------------------- +// hindsight_retain tool +// --------------------------------------------------------------------------- + +describe("hindsight_retain tool", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("stores content via Hindsight retain endpoint", async () => { + const fetchMock = mockFetch([{ url: /memories$/, body: { success: true } }]); + vi.stubGlobal("fetch", fetchMock); + const harness = buildHarness(); + await setupPlugin(harness); + + const result = await harness.executeTool( + "hindsight_retain", + { content: "Decision: use Postgres not MySQL" }, + { agentId: "ag-1", runId: "run-1", companyId: "co-1", projectId: "proj-1" } + ); + + expect((result as { content: string }).content).toBe("Memory saved."); + const call = fetchMock.mock.calls.find(([url]: [string]) => /memories$/.test(url)); + const body = JSON.parse(call?.[1]?.body as string) as { + items: Array<{ content: string }>; + }; + expect(body.items[0]?.content).toContain("Postgres"); + }); +}); + +// --------------------------------------------------------------------------- +// onValidateConfig +// --------------------------------------------------------------------------- + +describe("onValidateConfig", () => { + it("fails when hindsightApiUrl is missing", async () => { + const result = await plugin.definition.onValidateConfig!({ hindsightApiUrl: "" }); + expect(result.ok).toBe(false); + expect(result.errors?.some((e) => e.includes("hindsightApiUrl"))).toBe(true); + }); + + it("fails when Hindsight is unreachable", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response("", { status: 503 })) + ); + const result = await plugin.definition.onValidateConfig!({ + hindsightApiUrl: "http://localhost:8888", + }); + expect(result.ok).toBe(false); + vi.unstubAllGlobals(); + }); + + it("passes with a reachable Hindsight instance", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response("{}", { status: 200 })) + ); + const result = await plugin.definition.onValidateConfig!({ + hindsightApiUrl: "http://localhost:8888", + }); + expect(result.ok).toBe(true); + vi.unstubAllGlobals(); + }); +}); diff --git a/hindsight-integrations/paperclip/tests/recall.test.ts b/hindsight-integrations/paperclip/tests/recall.test.ts deleted file mode 100644 index 00369ad5b3..0000000000 --- a/hindsight-integrations/paperclip/tests/recall.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { recall } from "../src/recall.js"; -import { loadConfig } from "../src/config.js"; - -// Mock fetch globally -const mockFetch = vi.fn(); -vi.stubGlobal("fetch", mockFetch); - -function makeRecallResponse(results: Array<{ text: string; type?: string; mentionedAt?: string }>) { - return { - ok: true, - status: 200, - json: async () => ({ results }), - text: async () => "", - } as unknown as Response; -} - -function makeErrorResponse(status: number, body = "") { - return { - ok: false, - status, - json: async () => { - throw new Error("not json"); - }, - text: async () => body, - } as unknown as Response; -} - -beforeEach(() => { - mockFetch.mockReset(); -}); - -const config = loadConfig({ hindsightApiUrl: "http://fake:9077" }); -const input = { companyId: "co-1", agentId: "ag-1", query: "what did I work on?" }; - -describe("recall()", () => { - it("returns empty string for blank query", async () => { - const result = await recall({ ...input, query: " " }, config); - expect(result).toBe(""); - expect(mockFetch).not.toHaveBeenCalled(); - }); - - it("formats memories as bullet list", async () => { - mockFetch.mockResolvedValue( - makeRecallResponse([ - { text: "Fixed the login bug", type: "experience" }, - { text: "Prefers TypeScript", type: "preference" }, - ]) - ); - const result = await recall(input, config); - expect(result).toContain("- Fixed the login bug [experience]"); - expect(result).toContain("- Prefers TypeScript [preference]"); - }); - - it("includes mentionedAt date when present", async () => { - mockFetch.mockResolvedValue( - makeRecallResponse([{ text: "Deployed to prod", mentionedAt: "2024-01-15" }]) - ); - const result = await recall(input, config); - expect(result).toContain("(2024-01-15)"); - }); - - it("returns empty string when no results", async () => { - mockFetch.mockResolvedValue(makeRecallResponse([])); - const result = await recall(input, config); - expect(result).toBe(""); - }); - - it("gracefully degrades on HTTP error", async () => { - mockFetch.mockResolvedValue(makeErrorResponse(500, "Internal Server Error")); - const result = await recall(input, config); - expect(result).toBe(""); - }); - - it("gracefully degrades on network error", async () => { - mockFetch.mockRejectedValue(new Error("ECONNREFUSED")); - const result = await recall(input, config); - expect(result).toBe(""); - }); - - it("calls the correct API path with bank ID", async () => { - mockFetch.mockResolvedValue(makeRecallResponse([])); - await recall(input, config); - const [url] = mockFetch.mock.calls[0] as [string, RequestInit]; - expect(url).toContain("/v1/default/banks/paperclip%3A%3Aco-1%3A%3Aag-1/memories/recall"); - }); - - it("sends query and budget in request body", async () => { - mockFetch.mockResolvedValue(makeRecallResponse([])); - await recall(input, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.query).toBe("what did I work on?"); - expect(body.budget).toBe("mid"); - expect(body.max_tokens).toBe(1024); - }); - - it("uses custom budget and max_tokens from config", async () => { - const customConfig = loadConfig({ - hindsightApiUrl: "http://fake:9077", - recallBudget: "high", - recallMaxTokens: 2048, - }); - mockFetch.mockResolvedValue(makeRecallResponse([])); - await recall(input, customConfig); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.budget).toBe("high"); - expect(body.max_tokens).toBe(2048); - }); - - it("sends Authorization header when token is set", async () => { - const authConfig = loadConfig({ - hindsightApiUrl: "http://fake:9077", - hindsightApiToken: "hsk_test123", - }); - mockFetch.mockResolvedValue(makeRecallResponse([])); - await recall(input, authConfig); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - expect((init.headers as Record)["Authorization"]).toBe("Bearer hsk_test123"); - }); -}); diff --git a/hindsight-integrations/paperclip/tests/retain.test.ts b/hindsight-integrations/paperclip/tests/retain.test.ts deleted file mode 100644 index 176da6cae7..0000000000 --- a/hindsight-integrations/paperclip/tests/retain.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { retain } from "../src/retain.js"; -import { loadConfig } from "../src/config.js"; - -const mockFetch = vi.fn(); -vi.stubGlobal("fetch", mockFetch); - -function makeRetainResponse() { - return { - ok: true, - status: 200, - json: async () => ({ success: true }), - text: async () => "", - } as unknown as Response; -} - -function makeErrorResponse(status: number) { - return { - ok: false, - status, - json: async () => { - throw new Error("not json"); - }, - text: async () => "error", - } as unknown as Response; -} - -beforeEach(() => { - mockFetch.mockReset(); -}); - -const config = loadConfig({ hindsightApiUrl: "http://fake:9077" }); -const input = { - companyId: "co-1", - agentId: "ag-1", - content: "Fixed the authentication bug in login.ts", - documentId: "run-abc123", -}; - -describe("retain()", () => { - it("does nothing for blank content", async () => { - await retain({ ...input, content: " " }, config); - expect(mockFetch).not.toHaveBeenCalled(); - }); - - it("calls the correct API path", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain(input, config); - const [url] = mockFetch.mock.calls[0] as [string, RequestInit]; - expect(url).toContain("/v1/default/banks/paperclip%3A%3Aco-1%3A%3Aag-1/memories"); - }); - - it("sends content in request body items array", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain(input, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.items).toHaveLength(1); - expect(body.items[0].content).toBe("Fixed the authentication bug in login.ts"); - }); - - it("sends document_id to prevent duplicates", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain(input, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.items[0].document_id).toBe("run-abc123"); - }); - - it("includes companyId and agentId in metadata", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain(input, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.items[0].metadata.companyId).toBe("co-1"); - expect(body.items[0].metadata.agentId).toBe("ag-1"); - }); - - it("merges custom metadata with default metadata", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain({ ...input, metadata: { taskId: "task-99" } }, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.items[0].metadata.taskId).toBe("task-99"); - expect(body.items[0].metadata.companyId).toBe("co-1"); - }); - - it("sets context to retainContext from config", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain(input, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.items[0].context).toBe("paperclip"); - }); - - it("gracefully degrades on HTTP error", async () => { - mockFetch.mockResolvedValue(makeErrorResponse(503)); - // Should not throw - await expect(retain(input, config)).resolves.toBeUndefined(); - }); - - it("gracefully degrades on network error", async () => { - mockFetch.mockRejectedValue(new Error("Network failure")); - await expect(retain(input, config)).resolves.toBeUndefined(); - }); - - it("sends async flag in request body", async () => { - mockFetch.mockResolvedValue(makeRetainResponse()); - await retain(input, config); - const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; - const body = JSON.parse(init.body as string); - expect(body.async).toBe(true); - }); -}); diff --git a/skills/hindsight-docs/references/changelog/integrations/paperclip.md b/skills/hindsight-docs/references/changelog/integrations/paperclip.md index 1dbab3d9f5..f077771bd5 100644 --- a/skills/hindsight-docs/references/changelog/integrations/paperclip.md +++ b/skills/hindsight-docs/references/changelog/integrations/paperclip.md @@ -10,6 +10,21 @@ For the source code, see [`hindsight-integrations/paperclip`](https://github.com ← [Back to main changelog](../index.md) +## [0.2.0](https://github.com/vectorize-io/hindsight/tree/integrations/paperclip/v0.2.0) + +**Breaking Changes** + +- Rewritten as a proper Paperclip plugin (installed via `pnpm paperclipai plugin install`). No code changes required — memory hooks run automatically via the event system. +- Works with all adapter types (Claude, Codex, Cursor, HTTP, Process). Previously required manual `recall()`/`retain()` calls and only supported HTTP adapter agents. + +**Features** + +- `agent.run.started` hook: auto-recalls context keyed to issue title + description +- `agent.run.finished` hook: auto-retains agent output with `runId` as document ID +- `hindsight_recall` and `hindsight_retain` agent tools for mid-run memory access +- `onValidateConfig`: live connectivity check when operator saves settings +- Configurable bank granularity (company+agent, company-only, agent-only) + ## [0.1.2](https://github.com/vectorize-io/hindsight/tree/integrations/paperclip/v0.1.2) **Improvements**