diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3a2f0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.tmp-*.md diff --git a/LICENSE b/LICENSE index 9c22820..94664d7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2026 Tobi Lutke, David Cortés +Copyright (c) 2026 Gianfranco Piana Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b54cff3..fc94d73 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,103 @@ -# pi-autoresearch — autonomous experiment loop for pi +# openclaw-autoresearch -**[Install](#install)** · **[Usage](#usage)** · **[How it works](#how-it-works)** +Autonomous experiment loop for any optimization target. -*Try an idea, measure it, keep what works, discard what doesn't, repeat forever.* +Faithful OpenClaw port of [`davebcn87/pi-autoresearch`](https://github.com/davebcn87/pi-autoresearch). -Inspired by [karpathy/autoresearch](https://github.com/karpathy/autoresearch). Works for any optimization target: test speed, bundle size, LLM training, build times, Lighthouse scores. +## How it works ---- +The agent runs a loop: edit code, run a benchmark, measure the result, keep or discard. Each iteration is logged. The loop runs autonomously until interrupted. -![pi-autoresearch dashboard](pi-autoresearch.png) +Three tools drive the loop: ---- - -## What's included - -| | | +| Tool | What it does | |---|---| -| **Extension** | Tools + live widget + `/autoresearch` dashboard | -| **Skill** | Gathers what to optimize, writes session files, starts the loop | +| `init_experiment` | Configures the session: name, primary metric, unit, direction (lower/higher). Re-calling starts a new segment. | +| `run_experiment` | Executes a shell command, times it, captures stdout/stderr, returns pass/fail via exit code. | +| `log_experiment` | Records the result. `keep` auto-commits to git. `discard`/`crash` log without committing. Tracks secondary metrics alongside the primary. | -### Extension tools +Each tool also accepts an optional `cwd` so callers can target a nested repo explicitly instead of relying on the current session working directory. -| Tool | Description | -|------|-------------| -| `init_experiment` | One-time session config — name, metric, unit, direction | -| `run_experiment` | Runs any command, times wall-clock duration, captures output | -| `log_experiment` | Records result, auto-commits, updates widget and dashboard | - -### UI - -- **Status widget** — always visible above the editor: `🔬 autoresearch 12 runs 8 kept │ best: 42.3s` -- **`/autoresearch`** — full results dashboard (`Ctrl+X` to toggle, `Escape` to close) - -### Skill - -`autoresearch-create` asks a few questions (or infers from context) about your goal, command, metric, and files in scope — then writes two files and starts the loop immediately: +All state lives in four repo-root files: | File | Purpose | -|------|---------| -| `autoresearch.md` | Session document — objective, metrics, files in scope, what's been tried. A fresh agent can resume from this alone. | -| `autoresearch.sh` | Benchmark script — pre-checks, runs the workload, outputs `METRIC name=number` lines. | +|---|---| +| `autoresearch.md` | Session doc: objective, metrics, files in scope, constraints, what's been tried. A fresh agent reads this to resume. | +| `autoresearch.sh` | Benchmark script. Outputs `METRIC name=number` lines. | +| `autoresearch.jsonl` | Structured log: config headers + experiment entries (metric, status, timestamp, segment, commit hash). | +| `autoresearch.ideas.md` | Backlog of promising ideas not yet tried. Optional. | ---- +The design is file-first: any agent can pick up the repo-root files and continue the loop without prior context. ## Install ```bash -pi install https://github.com/davebcn87/pi-autoresearch +npm install ``` -
-Manual install +Then load this repo path in OpenClaw plugin discovery and restart the gateway: + +```yaml +plugins: + load: + paths: + - /absolute/path/to/openclaw-autoresearch + entries: + openclaw-autoresearch: + enabled: true +``` + +OpenClaw discovers `openclaw.plugin.json`, loads `extensions/openclaw-autoresearch/index.ts`, and exposes `autoresearch-create`. + +Manual install is also possible: copy the plugin root, `extensions/openclaw-autoresearch/`, and `skills/autoresearch-create/` into your managed OpenClaw locations, then restart. + +Verify: + +- skill: `autoresearch-create` +- tools: `init_experiment`, `run_experiment`, `log_experiment` +- command: `/autoresearch` (recommended) +- direct skill fallback: `/skill autoresearch-create` + +Prefer the explicit `/autoresearch` command surface in OpenClaw. The auto-generated native skill alias `/autoresearch_create` may not trigger reliably on some hosts, so use `/skill autoresearch-create` if you need to invoke the skill directly. + +## Use + +In the repo you want to optimize: + +1. Load the plugin. +2. Run `/autoresearch` or `/autoresearch setup `. +3. Send a normal message with the goal, command, metric (+ direction), files in scope, and constraints. +4. If you need the raw skill invocation, use `/skill autoresearch-create`. +5. The agent writes `autoresearch.md` and `autoresearch.sh`, runs a baseline, then starts looping. +6. Use `/autoresearch` or `/autoresearch status` to re-prime context on a later turn. + +To resume an existing session, a new agent reads the repo-root files and continues from where the last one stopped. + +### User steers + +Messages sent while an experiment is running are queued and surfaced after the next `log_experiment`. The agent finishes the current experiment before incorporating the steer. + +### Ideas backlog + +When the agent discovers promising but complex ideas mid-loop, it appends them to `autoresearch.ideas.md`. On resume, the agent reads the backlog, prunes stale entries, and uses the remaining ideas as experiment paths. + +## Upstream reference + +This port preserves upstream semantics, names, and file contracts while adapting presentation to OpenClaw. There is no Pi-style widget, dashboard, or editor shortcut layer. Remaining differences are tracked in [`docs/non-parity.md`](docs/non-parity.md). + +- upstream repo: `https://github.com/davebcn87/pi-autoresearch` +- pinned upstream commit: `2227029fa5712944a36938b5fe59f709cb30ed22` (`2227029f`) + +## Validation ```bash -cp -r extensions/pi-autoresearch ~/.pi/agent/extensions/ -cp -r skills/autoresearch-create ~/.pi/agent/skills/ +npm install --include=dev +npm run typecheck +npm test +npm run validate ``` -Then `/reload` in pi. - -
- ---- - -## Usage - -### 1. Start autoresearch - -``` -/skill:autoresearch-create -``` - -The agent asks about your goal, command, metric, and files in scope — or infers them from context. It then creates a branch, writes `autoresearch.md` and `autoresearch.sh`, runs the baseline, and starts looping immediately. - -### 2. The loop - -The agent runs autonomously: edit → commit → `run_experiment` → `log_experiment` → keep or revert → repeat. It never stops unless interrupted. - -Every result is appended to `autoresearch.jsonl` in your project — one line per run. This means: - -- **Survives restarts** — the agent can resume a session by reading the file -- **Survives context resets** — `autoresearch.md` captures what's been tried so a fresh agent has full context -- **Human readable** — open it anytime to see the full history -- **Branch-aware** — each branch has its own session - -### 3. Monitor progress - -- **Widget** — always visible above the editor -- **`/autoresearch`** — full dashboard with results table and best run -- **`Escape`** — interrupt anytime and ask for a summary - ---- - -## Example domains - -| Domain | Metric | Command | -|--------|--------|---------| -| Test speed | seconds ↓ | `pnpm test` | -| Bundle size | KB ↓ | `pnpm build && du -sb dist` | -| LLM training | val_bpb ↓ | `uv run train.py` | -| Build speed | seconds ↓ | `pnpm build` | -| Lighthouse | perf score ↑ | `lighthouse http://localhost:3000 --output=json` | - ---- - -## How it works - -The **extension** is domain-agnostic infrastructure. The **skill** encodes domain knowledge. This separation means one extension serves unlimited domains. - -``` -┌──────────────────────┐ ┌──────────────────────────┐ -│ Extension (global) │ │ Skill (per-domain) │ -│ │ │ │ -│ run_experiment │◄────│ command: pnpm test │ -│ log_experiment │ │ metric: seconds (lower) │ -│ widget + dashboard │ │ scope: vitest configs │ -│ │ │ ideas: pool, parallel… │ -└──────────────────────┘ └──────────────────────────┘ -``` - -Two files keep the session alive across restarts and context resets: - -``` -autoresearch.jsonl — append-only log of every run (metric, status, commit, description) -autoresearch.md — living document: objective, what's been tried, dead ends, key wins -``` - -A fresh agent with no memory can read these two files and continue exactly where the previous session left off. - ---- +The local test shim supports typechecking and tests without a full OpenClaw host checkout. Runtime behavior depends on a real OpenClaw host. ## License diff --git a/docs/non-parity.md b/docs/non-parity.md new file mode 100644 index 0000000..988c91d --- /dev/null +++ b/docs/non-parity.md @@ -0,0 +1,95 @@ +# Non-Parity Notes for the OpenClaw Port + +This repo aims to be a **faithful port** of `davebcn87/pi-autoresearch`, but it will not be a literal 1:1 port of Pi's UI/runtime. + +Pinned upstream reference: + +- Repo: `https://github.com/davebcn87/pi-autoresearch` +- Commit: `2227029fa5712944a36938b5fe59f709cb30ed22` (`2227029f`) + +## Principle + +Non-parity is acceptable only when it is forced by the host/runtime difference. + +That means: +- preserve semantics first +- preserve names and file layout second +- adapt presentation/runtime integration last + +## Expected non-parity in v1 + +### 1. No Pi widget parity +The Pi extension renders an always-visible status widget above the editor. + +OpenClaw may provide a thinner status surface instead, such as: +- tool output +- command output +- lightweight summaries + +### 2. No fullscreen dashboard / TUI parity +Pi provides an inline dashboard with keyboard interaction. + +OpenClaw v1 should not try to fake this with a new UI system. If a status view exists, it should be thin and optional. + +### 3. No keyboard shortcut parity +Pi has `Ctrl+X` and `Escape` affordances tied to its editor runtime. + +These are considered host-specific and are not part of the core port contract. + +### 4. Lifecycle hook names will differ +Pi uses hooks such as: +- `session_start` +- `session_switch` +- `session_fork` +- `session_tree` +- `before_agent_start` +- `agent_end` +- `input` + +OpenClaw has a different hook model. We should preserve intent, not literal event names. + +In practice, this port now uses documented OpenClaw lifecycle hooks such as: +- `before_prompt_build` +- `message_received` +- `agent_end` +- `session_end` + +The result should preserve queued-steer handling and ideas-backlog continuation intent without pretending that Pi's hook names or UI affordances exist in OpenClaw. + +### 5. `/autoresearch` command is thinner than Pi +The upstream repo includes a dedicated `/autoresearch` dashboard/entry surface. + +OpenClaw v1 keeps the main UX command-first and implements `/autoresearch` as a mode and status helper that: +- provides a stable explicit entrypoint when host-native skill aliases vary +- detects canonical repo-root files +- enables or disables autoresearch mode for later agent turns +- offers terse status text +- points the agent back to `autoresearch.md` + +It is intentionally not a dashboard replacement or a fullscreen UI. + +## Non-parity that is **not** acceptable + +The following would be design drift, not justified non-parity: + +- moving canonical runtime files under `.autoresearch/` in v1 +- renaming `init_experiment`, `run_experiment`, or `log_experiment` +- making a provider/runtime the product identity +- replacing the explicit command and direct-skill setup surface with a provider-specific worker-first UX +- changing keep/discard/crash behavior for convenience +- relying on hidden runtime state instead of file-first resumability + +## Honest product statement for v1 + +The correct way to describe v1 is: + +> A faithful OpenClaw port of `pi-autoresearch` that preserves upstream semantics, names, and file contracts, while explicitly not matching Pi's editor widget/dashboard UX. + +## Future parity work + +Possible later work, if OpenClaw surfaces support it cleanly: +- richer status presentation +- optional command polish +- optional provider adapters behind the plugin boundary + +These should remain secondary to semantic fidelity. diff --git a/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.ideas.md b/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.ideas.md new file mode 100644 index 0000000..3bf6f0c --- /dev/null +++ b/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.ideas.md @@ -0,0 +1,2 @@ +- try a lighter parser +- remove redundant setup diff --git a/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.jsonl b/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.jsonl new file mode 100644 index 0000000..d9a0252 --- /dev/null +++ b/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.jsonl @@ -0,0 +1,6 @@ +{"type":"config","name":"Speed up tests","metricName":"seconds","metricUnit":"s","bestDirection":"lower"} +{"run":1,"commit":"aaaaaaa","metric":12.5,"metrics":{"compile_ms":40},"status":"keep","description":"baseline","timestamp":1,"segment":0} +{"run":2,"commit":"bbbbbbb","metric":11.8,"metrics":{"compile_ms":38},"status":"keep","description":"cache tweak","timestamp":2,"segment":0} +{"type":"config","name":"Speed up tests","metricName":"seconds","metricUnit":"s","bestDirection":"lower"} +{"run":1,"commit":"ccccccc","metric":10.9,"metrics":{"compile_ms":34,"bundle_kb":120},"status":"keep","description":"new baseline","timestamp":3,"segment":1} +{"run":2,"commit":"ddddddd","metric":11.4,"metrics":{"compile_ms":33,"bundle_kb":121},"status":"discard","description":"bad tradeoff","timestamp":4,"segment":1} diff --git a/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.md b/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.md new file mode 100644 index 0000000..83d23a5 --- /dev/null +++ b/extensions/openclaw-autoresearch/fixtures/resume-session/autoresearch.md @@ -0,0 +1,8 @@ +# Autoresearch: Speed up tests + +## Objective +Make the benchmark faster. + +## What's Been Tried +- cache tweak +- baseline reset after a strategy shift diff --git a/extensions/openclaw-autoresearch/index.ts b/extensions/openclaw-autoresearch/index.ts new file mode 100644 index 0000000..12b236e --- /dev/null +++ b/extensions/openclaw-autoresearch/index.ts @@ -0,0 +1,30 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { + AUTORESEARCH_PLUGIN_DESCRIPTION, + AUTORESEARCH_PLUGIN_ID, + AUTORESEARCH_PLUGIN_NAME, + autoresearchPluginConfigSchema, +} from "./src/config.js"; +import { createInitExperimentTool } from "./src/tools/init-experiment.js"; +import { createRunExperimentTool } from "./src/tools/run-experiment.js"; +import { createLogExperimentTool } from "./src/tools/log-experiment.js"; +import { createAutoresearchStatusTool } from "./src/tools/autoresearch-status.js"; +import { registerAutoresearchHooks } from "./src/hooks.js"; +import { registerAutoresearchCommand } from "./src/commands/autoresearch.js"; + +const plugin = { + id: AUTORESEARCH_PLUGIN_ID, + name: AUTORESEARCH_PLUGIN_NAME, + description: AUTORESEARCH_PLUGIN_DESCRIPTION, + configSchema: autoresearchPluginConfigSchema, + register(api: OpenClawPluginApi) { + registerAutoresearchHooks(api); + registerAutoresearchCommand(api); + api.registerTool(createInitExperimentTool(api)); + api.registerTool(createRunExperimentTool(api)); + api.registerTool(createLogExperimentTool(api)); + api.registerTool(createAutoresearchStatusTool(api)); + }, +}; + +export default plugin; diff --git a/extensions/openclaw-autoresearch/src/commands/autoresearch.ts b/extensions/openclaw-autoresearch/src/commands/autoresearch.ts new file mode 100644 index 0000000..4485e14 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/commands/autoresearch.ts @@ -0,0 +1,182 @@ +import * as fs from "node:fs"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { + AUTORESEARCH_ROOT_FILES, + getAutoresearchRootFilePath, + type AutoresearchRootFileKey, +} from "../files.js"; +import { reconstructStateFromJsonl } from "../state.js"; +import { formatAutoresearchStatusText } from "../tools/autoresearch-status.js"; +import { + clearAutoresearchSteers, + getAutoresearchRuntimeState, + setAutoresearchPendingCommand, + setAutoresearchRunInFlight, + setAutoresearchRuntimeMode, +} from "../runtime-state.js"; + +type CommandContext = { + args?: string; + channel?: string; + senderId?: string; + cwd?: string; +}; + +const COMMAND_USAGE = [ + "Enable or inspect repo-root autoresearch mode.", + "", + "Usage:", + "/autoresearch", + "/autoresearch on", + "/autoresearch off", + "/autoresearch setup", + "/autoresearch status", + "/autoresearch help", +].join("\n"); + +export function registerAutoresearchCommand(api: OpenClawPluginApi): void { + api.registerCommand({ + name: "autoresearch", + description: "Enable, disable, or inspect repo-root autoresearch mode.", + acceptsArgs: true, + handler: (ctx: CommandContext) => { + const cwd = resolveCommandCwd(api, ctx); + const rawArgs = (ctx.args ?? "").trim(); + const [verb, ...rest] = rawArgs.split(/\s+/).filter(Boolean); + const action = (verb ?? "").toLowerCase(); + const remainder = rest.join(" ").trim() || null; + + if (!rawArgs || action === "resume" || action === "on") { + return { + text: enableAutoresearchMode(cwd, rawArgs && action !== "resume" && action !== "on" ? rawArgs : remainder), + }; + } + if (action === "setup") { + return { text: primeAutoresearchSetup(cwd, remainder) }; + } + if (action === "off") { + setAutoresearchRuntimeMode(cwd, "off"); + setAutoresearchPendingCommand(cwd, null); + clearAutoresearchSteers(cwd); + setAutoresearchRunInFlight(cwd, false); + return { + text: [ + "Autoresearch mode OFF.", + `Canonical files remain at repo root: ${Object.values(AUTORESEARCH_ROOT_FILES).join(", ")}`, + ].join("\n"), + }; + } + if (action === "status") { + return { text: buildAutoresearchCommandText(cwd, "status") }; + } + if (action === "help") { + return { text: `${COMMAND_USAGE}\n\n${buildAutoresearchCommandText(cwd, "default")}` }; + } + + return { + text: enableAutoresearchMode(cwd, rawArgs), + }; + }, + }); +} + +export function buildAutoresearchCommandText( + cwd: string, + mode: "default" | "status", +): string { + const runtimeState = getAutoresearchRuntimeState(cwd); + const presentFiles = getPresentCanonicalFiles(cwd); + const hasSession = presentFiles.length > 0; + + if (!hasSession) { + return [ + "No repo-root autoresearch session detected.", + "", + `Expected canonical files: ${Object.values(AUTORESEARCH_ROOT_FILES).join(", ")}`, + "Recommended OpenClaw entrypoint: `/autoresearch` or `/autoresearch setup `.", + "Direct skill fallback: `/skill autoresearch-create`.", + ].join("\n"); + } + + const state = reconstructStateFromJsonl(cwd); + const lines = [ + `Autoresearch session detected at repo root: ${presentFiles.join(", ")}`, + `Read \`${AUTORESEARCH_ROOT_FILES.sessionDoc}\` before resuming or changing the loop.`, + ]; + + if (mode === "status") { + lines.push("", formatAutoresearchStatusText(state, runtimeState)); + } else if (state.mode === "active" || state.hasSessionDoc) { + lines.push( + "Use `/autoresearch` or `/autoresearch on` to enable mode for the next agent turn, then continue the upstream loop with `init_experiment`, `run_experiment`, and `log_experiment` as needed.", + ); + } else { + lines.push( + `The canonical files exist, but the session brief looks incomplete. Open \`${AUTORESEARCH_ROOT_FILES.sessionDoc}\` and finish setup, or restart via \`/skill autoresearch-create\` if needed.`, + ); + } + + return lines.join("\n"); +} + +function enableAutoresearchMode(cwd: string, args: string | null): string { + setAutoresearchRuntimeMode(cwd, "on"); + const presentFiles = getPresentCanonicalFiles(cwd); + const hasSession = presentFiles.length > 0; + + if (!hasSession) { + setAutoresearchPendingCommand(cwd, { + kind: "setup", + args, + }); + return [ + "Autoresearch mode ON.", + "No repo-root session was detected, so the next agent turn will be primed for setup.", + "Next step: send a normal message so the next agent turn can gather setup details.", + "Direct skill fallback: `/skill autoresearch-create`.", + args ? `Captured setup instruction: ${args}` : "Run `/autoresearch setup ` to attach a setup hint.", + ].join("\n"); + } + + setAutoresearchPendingCommand(cwd, { + kind: "resume", + args, + }); + return [ + "Autoresearch mode ON.", + `Next agent turn will be primed from \`${AUTORESEARCH_ROOT_FILES.sessionDoc}\` and the canonical repo-root files.`, + args ? `Captured resume instruction: ${args}` : "Send a normal message to continue the loop, or use `/autoresearch status` for a snapshot first.", + ].join("\n"); +} + +function primeAutoresearchSetup(cwd: string, args: string | null): string { + setAutoresearchRuntimeMode(cwd, "on"); + setAutoresearchPendingCommand(cwd, { + kind: "setup", + args, + }); + + return [ + "Autoresearch setup primed.", + "The next agent turn will be told to create the canonical repo-root files and start the loop.", + "Continue with a normal message on the next turn, or invoke the skill directly with `/skill autoresearch-create`.", + args ? `Captured setup instruction: ${args}` : "Add an argument to `/autoresearch setup` if you want a specific goal or constraint carried forward.", + ].join("\n"); +} + +function resolveCommandCwd(api: OpenClawPluginApi, ctx: CommandContext): string { + if (typeof ctx.cwd === "string" && ctx.cwd.trim().length > 0) { + return ctx.cwd; + } + return api.resolvePath("."); +} + +function getPresentCanonicalFiles(cwd: string): string[] { + const present: string[] = []; + for (const key of Object.keys(AUTORESEARCH_ROOT_FILES) as AutoresearchRootFileKey[]) { + if (fs.existsSync(getAutoresearchRootFilePath(cwd, key))) { + present.push(AUTORESEARCH_ROOT_FILES[key]); + } + } + return present; +} diff --git a/extensions/openclaw-autoresearch/src/config.ts b/extensions/openclaw-autoresearch/src/config.ts new file mode 100644 index 0000000..ece7891 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/config.ts @@ -0,0 +1,7 @@ +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; + +export const AUTORESEARCH_PLUGIN_ID = "openclaw-autoresearch"; +export const AUTORESEARCH_PLUGIN_NAME = "Autoresearch"; +export const AUTORESEARCH_PLUGIN_DESCRIPTION = "Faithful OpenClaw port of pi-autoresearch."; + +export const autoresearchPluginConfigSchema = emptyPluginConfigSchema(); diff --git a/extensions/openclaw-autoresearch/src/execute.ts b/extensions/openclaw-autoresearch/src/execute.ts new file mode 100644 index 0000000..59758db --- /dev/null +++ b/extensions/openclaw-autoresearch/src/execute.ts @@ -0,0 +1,122 @@ +import { spawn } from "node:child_process"; + +const OUTPUT_TAIL_LINES = 80; +const DEFAULT_TIMEOUT_SECONDS = 600; +const FORCE_KILL_GRACE_MS = 1_000; + +export type ExperimentExecutionResult = { + readonly command: string; + readonly exitCode: number | null; + readonly durationSeconds: number; + readonly passed: boolean; + readonly crashed: boolean; + readonly timedOut: boolean; + readonly tailOutput: string; + readonly stdout: string; + readonly stderr: string; +}; + +export async function executeExperimentCommand(options: { + command: string; + cwd: string; + timeoutSeconds?: number; + signal?: AbortSignal; +}): Promise { + const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS; + const timeoutMs = Math.max(0, timeoutSeconds) * 1_000; + const startedAt = Date.now(); + + return await new Promise((resolve) => { + const child = spawn("bash", ["-c", options.command], { + cwd: options.cwd, + stdio: ["ignore", "pipe", "pipe"], + }); + + let stdout = ""; + let stderr = ""; + let timedOut = false; + let forceKillTimer: NodeJS.Timeout | undefined; + + const timeoutTimer = + timeoutMs > 0 + ? setTimeout(() => { + timedOut = true; + child.kill("SIGTERM"); + forceKillTimer = setTimeout(() => { + if (!child.killed) { + child.kill("SIGKILL"); + } + }, FORCE_KILL_GRACE_MS); + }, timeoutMs) + : undefined; + + const abortHandler = () => { + child.kill("SIGTERM"); + forceKillTimer = setTimeout(() => { + if (!child.killed) { + child.kill("SIGKILL"); + } + }, FORCE_KILL_GRACE_MS); + }; + + if (options.signal) { + if (options.signal.aborted) { + abortHandler(); + } else { + options.signal.addEventListener("abort", abortHandler, { once: true }); + } + } + + child.stdout.on("data", (chunk: string | Buffer) => { + stdout += chunk.toString(); + }); + + child.stderr.on("data", (chunk: string | Buffer) => { + stderr += chunk.toString(); + }); + + child.on("error", (error) => { + stderr += `${stderr ? "\n" : ""}${String(error.message || error)}`; + }); + + child.on("close", (code) => { + if (timeoutTimer) { + clearTimeout(timeoutTimer); + } + if (forceKillTimer) { + clearTimeout(forceKillTimer); + } + if (options.signal) { + options.signal.removeEventListener("abort", abortHandler); + } + + const durationSeconds = (Date.now() - startedAt) / 1_000; + const passed = code === 0 && !timedOut; + + resolve({ + command: options.command, + exitCode: code, + durationSeconds, + passed, + crashed: !passed, + timedOut, + tailOutput: createOutputTail(stdout, stderr), + stdout, + stderr, + }); + }); + }); +} + +function createOutputTail(stdout: string, stderr: string): string { + const combined = [stdout, stderr] + .filter((value) => value.trim().length > 0) + .join("\n") + .trim(); + + if (!combined) { + return ""; + } + + return combined.split(/\r?\n/).slice(-OUTPUT_TAIL_LINES).join("\n"); +} diff --git a/extensions/openclaw-autoresearch/src/files.ts b/extensions/openclaw-autoresearch/src/files.ts new file mode 100644 index 0000000..1b9409f --- /dev/null +++ b/extensions/openclaw-autoresearch/src/files.ts @@ -0,0 +1,37 @@ +import * as fs from "node:fs"; + +export const AUTORESEARCH_ROOT_FILES = { + sessionDoc: "autoresearch.md", + runnerScript: "autoresearch.sh", + resultsLog: "autoresearch.jsonl", + ideasBacklog: "autoresearch.ideas.md", +} as const; + +export type AutoresearchRootFileKey = keyof typeof AUTORESEARCH_ROOT_FILES; + +export function getAutoresearchRootFilePath( + cwd: string, + file: AutoresearchRootFileKey, +): string { + return `${cwd}/${AUTORESEARCH_ROOT_FILES[file]}`; +} + +export function readAutoresearchRootFile( + cwd: string, + file: AutoresearchRootFileKey, +): string | null { + const filePath = getAutoresearchRootFilePath(cwd, file); + if (!fs.existsSync(filePath)) { + return null; + } + + return fs.readFileSync(filePath, "utf8"); +} + +/** + * PR 2 skeleton only. + * This module will own canonical root-level file IO helpers in later PRs. + */ +export function describeCanonicalFiles(): typeof AUTORESEARCH_ROOT_FILES { + return AUTORESEARCH_ROOT_FILES; +} diff --git a/extensions/openclaw-autoresearch/src/git.ts b/extensions/openclaw-autoresearch/src/git.ts new file mode 100644 index 0000000..e270596 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/git.ts @@ -0,0 +1,132 @@ +import { spawnSync } from "node:child_process"; + +export type GitCommandResult = { + readonly code: number | null; + readonly stdout: string; + readonly stderr: string; + readonly combinedOutput: string; +}; + +export type GitKeepResult = { + readonly attempted: true; + readonly committed: boolean; + readonly commit: string; + readonly summary: string; + readonly command: GitCommandResult; +}; + +function runGitCommand(cwd: string, args: readonly string[]): GitCommandResult { + const result = spawnSync("git", [...args], { + cwd, + encoding: "utf8", + }); + + const stdout = result.stdout ?? ""; + const stderr = result.stderr ?? ""; + + return { + code: result.status, + stdout, + stderr, + combinedOutput: `${stdout}${stderr}`.trim(), + }; +} + +export function commitKeptExperiment(options: { + cwd: string; + description: string; + metricName: string; + metric: number; + metrics: Record; + commit: string; + status: "keep"; +}): GitKeepResult { + const resultData: Record = { + status: options.status, + [options.metricName || "metric"]: options.metric, + ...options.metrics, + }; + const commitMessage = `${options.description}\n\nResult: ${JSON.stringify(resultData)}`; + + const repoRootResult = runGitCommand(options.cwd, ["rev-parse", "--show-toplevel"]); + if (repoRootResult.code !== 0 || repoRootResult.stdout.trim().length === 0) { + return { + attempted: true, + committed: false, + commit: options.commit, + summary: `Git repo check failed${formatExit(repoRootResult.code)}: ${truncateOutput(repoRootResult.combinedOutput)}`, + command: repoRootResult, + }; + } + + const repoRoot = repoRootResult.stdout.trim(); + + const addResult = runGitCommand(repoRoot, ["add", "-A"]); + if (addResult.code !== 0) { + return { + attempted: true, + committed: false, + commit: options.commit, + summary: `Git add failed${formatExit(addResult.code)}: ${truncateOutput(addResult.combinedOutput)}`, + command: addResult, + }; + } + + const diffResult = runGitCommand(repoRoot, ["diff", "--cached", "--quiet"]); + if (diffResult.code === 0) { + return { + attempted: true, + committed: false, + commit: options.commit, + summary: "Git: nothing to commit (working tree clean)", + command: diffResult, + }; + } + if (diffResult.code !== 1) { + return { + attempted: true, + committed: false, + commit: options.commit, + summary: `Git diff check failed${formatExit(diffResult.code)}: ${truncateOutput(diffResult.combinedOutput)}`, + command: diffResult, + }; + } + + const commitResult = runGitCommand(repoRoot, ["commit", "-m", commitMessage]); + if (commitResult.code !== 0) { + return { + attempted: true, + committed: false, + commit: options.commit, + summary: `Git commit failed${formatExit(commitResult.code)}: ${truncateOutput(commitResult.combinedOutput)}`, + command: commitResult, + }; + } + + const revParseResult = runGitCommand(repoRoot, ["rev-parse", "--short=7", "HEAD"]); + const actualCommit = + revParseResult.code === 0 && revParseResult.stdout.trim().length >= 7 + ? revParseResult.stdout.trim().slice(0, 7) + : options.commit; + const firstLine = commitResult.combinedOutput.split("\n")[0]?.trim() || "commit created"; + + return { + attempted: true, + committed: true, + commit: actualCommit, + summary: `Git: committed - ${firstLine}`, + command: commitResult, + }; +} + +function truncateOutput(output: string): string { + const normalized = output.trim(); + if (!normalized) { + return "no output"; + } + return normalized.length > 200 ? `${normalized.slice(0, 200)}...` : normalized; +} + +function formatExit(code: number | null): string { + return code === null ? "" : ` (exit ${code})`; +} diff --git a/extensions/openclaw-autoresearch/src/hooks.ts b/extensions/openclaw-autoresearch/src/hooks.ts new file mode 100644 index 0000000..ed4256a --- /dev/null +++ b/extensions/openclaw-autoresearch/src/hooks.ts @@ -0,0 +1,234 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { AUTORESEARCH_ROOT_FILES } from "./files.js"; +import { reconstructStateFromJsonl } from "./state.js"; +import { + clearAutoresearchRuntimeState, + consumeAutoresearchContinuationReminder, + consumeAutoresearchPendingCommand, + getAutoresearchRuntimeState, + queueAutoresearchSteer, + setAutoresearchContinuationReminder, +} from "./runtime-state.js"; + +type BeforeAgentStartEvent = { + systemPrompt?: string; +}; + +type HookContext = { + cwd?: string; +}; + +type HookCapablePluginApi = OpenClawPluginApi & { + on?: (hookName: string, handler: (event: unknown, ctx: HookContext) => unknown) => void; + registerHook?: ( + hookName: string, + handler: (event: BeforeAgentStartEvent, ctx: HookContext) => BeforeAgentStartEvent | void, + ) => void; +}; + +export function registerAutoresearchHooks(api: OpenClawPluginApi): void { + const hookApi = api as HookCapablePluginApi; + if (typeof hookApi.on === "function") { + hookApi.on("before_prompt_build", (_event, ctx) => { + const cwd = resolveHookCwd(api, ctx); + if (cwd === null) { + return; + } + + const addition = buildBeforePromptBuildContext(cwd); + if (addition === null) { + return; + } + + return { + appendSystemContext: addition, + }; + }); + + hookApi.on("message_received", (event, ctx) => { + const cwd = resolveHookCwd(api, ctx); + if (cwd === null) { + return; + } + + const state = getAutoresearchRuntimeState(cwd); + if (!state.runInFlight) { + return; + } + + const messageText = extractMessageText(event); + if (messageText === null || isCommandLikeMessage(messageText)) { + return; + } + + queueAutoresearchSteer(cwd, messageText); + }); + + hookApi.on("agent_end", (_event, ctx) => { + const cwd = resolveHookCwd(api, ctx); + if (cwd === null) { + return; + } + + const state = reconstructStateFromJsonl(cwd); + if (state.mode === "active" && state.ideas.hasBacklog) { + setAutoresearchContinuationReminder(cwd, true); + } + }); + + hookApi.on("session_end", (_event, ctx) => { + const cwd = resolveHookCwd(api, ctx); + if (cwd === null) { + return; + } + + clearAutoresearchRuntimeState(cwd); + }); + return; + } + + if (typeof hookApi.registerHook !== "function") { + return; + } + + hookApi.registerHook("before_agent_start", (event, ctx) => { + const cwd = resolveHookCwd(api, ctx); + if (cwd === null) { + return; + } + + const addition = buildBeforePromptBuildContext(cwd); + if (addition === null) { + return; + } + + return { + ...event, + systemPrompt: `${event.systemPrompt ?? ""}${addition}`, + }; + }); +} + +export function buildBeforePromptBuildContext(cwd: string): string | null { + const state = reconstructStateFromJsonl(cwd); + const runtimeState = getAutoresearchRuntimeState(cwd); + const modeEnabled = + runtimeState.mode === "on" || + (runtimeState.mode !== "off" && (state.mode === "active" || state.hasSessionDoc)); + + if (!modeEnabled) { + return null; + } + + const canonicalFiles = [ + AUTORESEARCH_ROOT_FILES.sessionDoc, + AUTORESEARCH_ROOT_FILES.runnerScript, + AUTORESEARCH_ROOT_FILES.resultsLog, + ]; + const pendingCommand = consumeAutoresearchPendingCommand(cwd); + const needsContinuationReminder = consumeAutoresearchContinuationReminder(cwd); + + const lines = ["", "", "## Autoresearch Mode (ACTIVE)"]; + + if (pendingCommand?.kind === "setup" || !state.hasSessionDoc) { + lines.push( + `No ${AUTORESEARCH_ROOT_FILES.sessionDoc} was detected. Gather context and set up the experiment now with the canonical repo-root files.`, + `Create ${AUTORESEARCH_ROOT_FILES.sessionDoc} and ${AUTORESEARCH_ROOT_FILES.runnerScript}, then initialize the loop with init_experiment, run_experiment, and log_experiment.`, + ); + if (pendingCommand?.args) { + lines.push(`Additional setup instruction from /autoresearch: ${pendingCommand.args}`); + } + } else { + lines.push( + `Autoresearch files live at repo root: ${canonicalFiles.join(", ")}.`, + `Read ${AUTORESEARCH_ROOT_FILES.sessionDoc} before resuming or changing the experiment loop, and re-read it after compaction.`, + "Resume the autonomous upstream loop: edit, run_experiment, log_experiment, keep/discard/crash, repeat.", + "Use init_experiment, run_experiment, and log_experiment for experiment state changes. Never stop unless the user explicitly interrupts the loop.", + ); + if (pendingCommand?.args) { + lines.push(`Additional resume instruction from /autoresearch: ${pendingCommand.args}`); + } + } + + lines.push( + `For discard or crash results, log_experiment records the outcome but does not revert your tree for you. Run \`git checkout -- .\` yourself after logging when you want to discard tracked changes.`, + ); + + if (state.ideas.hasBacklog) { + lines.push( + `${AUTORESEARCH_ROOT_FILES.ideasBacklog} exists with ${state.ideas.pendingCount} pending idea${state.ideas.pendingCount === 1 ? "" : "s"}; use it as continuation fuel for promising paths you have not exhausted.`, + ); + } + + if (needsContinuationReminder && state.ideas.hasBacklog) { + lines.push( + `The previous autoresearch run ended with pending ideas. Read ${AUTORESEARCH_ROOT_FILES.ideasBacklog}, prune stale items, and spin those ideas into the next experiments before declaring the work done.`, + ); + } + + if (runtimeState.queuedSteers.length > 0) { + lines.push( + `${runtimeState.queuedSteers.length} user steer${runtimeState.queuedSteers.length === 1 ? "" : "s"} arrived during the current experiment window. If the next followup turn repeats a steer already surfaced in log_experiment output, treat it as the same request rather than a new branch of work.`, + ); + } + + return lines.join("\n"); +} + +function resolveHookCwd(api: OpenClawPluginApi, ctx: HookContext | undefined): string | null { + if (ctx && typeof ctx.cwd === "string" && ctx.cwd.trim().length > 0) { + return ctx.cwd; + } + + try { + const resolved = api.resolvePath("."); + return resolved.trim().length > 0 ? resolved : null; + } catch { + return null; + } +} + +function extractMessageText(event: unknown): string | null { + if (!event || typeof event !== "object") { + return null; + } + + const record = event as Record; + const direct = firstString(record.text, record.content, record.body, record.prompt); + if (direct) { + return direct; + } + + const message = record.message; + if (message && typeof message === "object") { + const nested = message as Record; + const messageText = firstString(nested.text, nested.content, nested.body); + if (messageText) { + return messageText; + } + } + + const context = record.context; + if (context && typeof context === "object") { + const nested = context as Record; + const contextText = firstString(nested.content, nested.commandBody, nested.text); + if (contextText) { + return contextText; + } + } + + return null; +} + +function firstString(...values: unknown[]): string | null { + for (const value of values) { + if (typeof value === "string" && value.trim().length > 0) { + return value.trim(); + } + } + return null; +} + +function isCommandLikeMessage(text: string): boolean { + return /^[\/!]/.test(text.trim()); +} diff --git a/extensions/openclaw-autoresearch/src/logging.ts b/extensions/openclaw-autoresearch/src/logging.ts new file mode 100644 index 0000000..fe49ca1 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/logging.ts @@ -0,0 +1,55 @@ +import * as fs from "node:fs"; +import { getAutoresearchRootFilePath } from "./files.js"; + +export type AutoresearchConfigHeader = { + type: "config"; + name: string; + metricName: string; + metricUnit: string; + bestDirection: "lower" | "higher"; +}; + +export function createConfigHeader(config: { + name: string; + metricName: string; + metricUnit: string; + bestDirection: "lower" | "higher"; +}): AutoresearchConfigHeader { + return { + type: "config", + name: config.name, + metricName: config.metricName, + metricUnit: config.metricUnit, + bestDirection: config.bestDirection, + }; +} + +export function writeConfigHeader( + cwd: string, + header: AutoresearchConfigHeader, + mode: "create" | "append", +): void { + const jsonlPath = getAutoresearchRootFilePath(cwd, "resultsLog"); + const line = `${JSON.stringify(header)}\n`; + if (mode === "append") { + fs.appendFileSync(jsonlPath, line); + return; + } + fs.writeFileSync(jsonlPath, line); +} + +export type AutoresearchResultEntry = { + readonly run: number; + readonly commit: string; + readonly metric: number; + readonly metrics: Record; + readonly status: "keep" | "discard" | "crash"; + readonly description: string; + readonly timestamp: number; + readonly segment: number; +}; + +export function appendResultEntry(cwd: string, entry: AutoresearchResultEntry): void { + const jsonlPath = getAutoresearchRootFilePath(cwd, "resultsLog"); + fs.appendFileSync(jsonlPath, `${JSON.stringify(entry)}\n`); +} diff --git a/extensions/openclaw-autoresearch/src/runtime-state.ts b/extensions/openclaw-autoresearch/src/runtime-state.ts new file mode 100644 index 0000000..98e9e41 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/runtime-state.ts @@ -0,0 +1,143 @@ +export type AutoresearchRuntimeMode = "auto" | "on" | "off"; + +export type PendingAutoresearchCommand = + | { + readonly kind: "resume" | "setup"; + readonly args: string | null; + } + | null; + +export type AutoresearchRuntimeSnapshot = { + readonly mode: AutoresearchRuntimeMode; + readonly runInFlight: boolean; + readonly queuedSteers: readonly string[]; + readonly needsContinuationReminder: boolean; + readonly pendingCommand: PendingAutoresearchCommand; +}; + +type MutableAutoresearchRuntimeState = { + mode: AutoresearchRuntimeMode; + runInFlight: boolean; + queuedSteers: string[]; + needsContinuationReminder: boolean; + pendingCommand: PendingAutoresearchCommand; +}; + +const MAX_QUEUED_STEERS = 20; +const runtimeStates = new Map(); + +function createDefaultRuntimeState(): MutableAutoresearchRuntimeState { + return { + mode: "auto", + runInFlight: false, + queuedSteers: [], + needsContinuationReminder: false, + pendingCommand: null, + }; +} + +function getMutableRuntimeState(cwd: string): MutableAutoresearchRuntimeState { + let state = runtimeStates.get(cwd); + if (!state) { + state = createDefaultRuntimeState(); + runtimeStates.set(cwd, state); + } + return state; +} + +export function getAutoresearchRuntimeState(cwd: string): AutoresearchRuntimeSnapshot { + const state = getMutableRuntimeState(cwd); + return { + mode: state.mode, + runInFlight: state.runInFlight, + queuedSteers: [...state.queuedSteers], + needsContinuationReminder: state.needsContinuationReminder, + pendingCommand: state.pendingCommand, + }; +} + +export function setAutoresearchRuntimeMode( + cwd: string, + mode: AutoresearchRuntimeMode, +): AutoresearchRuntimeSnapshot { + const state = getMutableRuntimeState(cwd); + state.mode = mode; + return getAutoresearchRuntimeState(cwd); +} + +export function setAutoresearchRunInFlight( + cwd: string, + runInFlight: boolean, +): AutoresearchRuntimeSnapshot { + const state = getMutableRuntimeState(cwd); + state.runInFlight = runInFlight; + return getAutoresearchRuntimeState(cwd); +} + +export function queueAutoresearchSteer( + cwd: string, + steer: string, +): AutoresearchRuntimeSnapshot { + const normalized = steer.trim(); + if (!normalized) { + return getAutoresearchRuntimeState(cwd); + } + + const state = getMutableRuntimeState(cwd); + state.queuedSteers.push(normalized); + if (state.queuedSteers.length > MAX_QUEUED_STEERS) { + state.queuedSteers = state.queuedSteers.slice(-MAX_QUEUED_STEERS); + } + return getAutoresearchRuntimeState(cwd); +} + +export function consumeAutoresearchSteers(cwd: string): readonly string[] { + const state = getMutableRuntimeState(cwd); + const queued = [...state.queuedSteers]; + state.queuedSteers = []; + return queued; +} + +export function clearAutoresearchSteers(cwd: string): AutoresearchRuntimeSnapshot { + const state = getMutableRuntimeState(cwd); + state.queuedSteers = []; + return getAutoresearchRuntimeState(cwd); +} + +export function setAutoresearchPendingCommand( + cwd: string, + pendingCommand: PendingAutoresearchCommand, +): AutoresearchRuntimeSnapshot { + const state = getMutableRuntimeState(cwd); + state.pendingCommand = pendingCommand; + return getAutoresearchRuntimeState(cwd); +} + +export function consumeAutoresearchPendingCommand( + cwd: string, +): PendingAutoresearchCommand { + const state = getMutableRuntimeState(cwd); + const pending = state.pendingCommand; + state.pendingCommand = null; + return pending; +} + +export function setAutoresearchContinuationReminder( + cwd: string, + needsReminder: boolean, +): AutoresearchRuntimeSnapshot { + const state = getMutableRuntimeState(cwd); + state.needsContinuationReminder = needsReminder; + return getAutoresearchRuntimeState(cwd); +} + +export function consumeAutoresearchContinuationReminder(cwd: string): boolean { + const state = getMutableRuntimeState(cwd); + const needsReminder = state.needsContinuationReminder; + state.needsContinuationReminder = false; + return needsReminder; +} + +export function clearAutoresearchRuntimeState(cwd: string): void { + runtimeStates.delete(cwd); +} diff --git a/extensions/openclaw-autoresearch/src/state.ts b/extensions/openclaw-autoresearch/src/state.ts new file mode 100644 index 0000000..c661307 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/state.ts @@ -0,0 +1,290 @@ +import { readAutoresearchRootFile } from "./files.js"; + +export type SecondaryMetricDef = { + readonly name: string; + readonly unit: string; +}; + +export type AutoresearchMode = "inactive" | "active"; + +export type AutoresearchIdeasSnapshot = { + readonly hasBacklog: boolean; + readonly pendingCount: number; + readonly preview: readonly string[]; +}; + +export type AutoresearchRunSnapshot = { + readonly run: number; + readonly commit: string; + readonly metric: number; + readonly metrics: Record; + readonly status: "keep" | "discard" | "crash"; + readonly description: string; + readonly timestamp: number; + readonly segment: number; +}; + +export type AutoresearchStateSnapshot = { + readonly name: string | null; + readonly metricName: string; + readonly metricUnit: string; + readonly bestDirection: "lower" | "higher"; + readonly secondaryMetrics: readonly SecondaryMetricDef[]; + readonly currentSegment: number; + readonly currentRunCount: number; + readonly totalRunCount: number; + readonly currentBaselineMetric: number | null; + readonly currentBestMetric: number | null; + readonly lastRun: AutoresearchRunSnapshot | null; + readonly mode: AutoresearchMode; + readonly hasSessionDoc: boolean; + readonly ideas: AutoresearchIdeasSnapshot; +}; + +type MutableStateSnapshot = { + name: string | null; + metricName: string; + metricUnit: string; + bestDirection: "lower" | "higher"; + currentSegment: number; + currentRunCount: number; + totalRunCount: number; + currentBaselineMetric: number | null; + currentBestMetric: number | null; + lastRun: AutoresearchRunSnapshot | null; + mode: AutoresearchMode; + hasSessionDoc: boolean; + ideas: AutoresearchIdeasSnapshot; +}; + +type JsonlEntry = { + readonly type?: string; + readonly name?: string; + readonly metricName?: string; + readonly metricUnit?: string; + readonly bestDirection?: "lower" | "higher"; + readonly run?: number; + readonly commit?: string; + readonly metric?: number; + readonly metrics?: Record; + readonly status?: "keep" | "discard" | "crash"; + readonly description?: string; + readonly timestamp?: number; + readonly segment?: number; +}; + +export function createEmptyStateSnapshot(): AutoresearchStateSnapshot { + return { + name: null, + metricName: "metric", + metricUnit: "", + bestDirection: "lower", + secondaryMetrics: [], + currentSegment: 0, + currentRunCount: 0, + totalRunCount: 0, + currentBaselineMetric: null, + currentBestMetric: null, + lastRun: null, + mode: "inactive", + hasSessionDoc: false, + ideas: { + hasBacklog: false, + pendingCount: 0, + preview: [], + }, + }; +} + +function createMutableEmptyStateSnapshot(): MutableStateSnapshot { + return { + ...createEmptyStateSnapshot(), + }; +} + +export function reconstructStateFromJsonl(cwd: string): AutoresearchStateSnapshot { + const sessionDoc = readAutoresearchRootFile(cwd, "sessionDoc"); + const ideasBacklog = readAutoresearchRootFile(cwd, "ideasBacklog"); + const jsonl = readAutoresearchRootFile(cwd, "resultsLog"); + + const state = createMutableEmptyStateSnapshot(); + state.hasSessionDoc = sessionDoc !== null; + state.mode = detectAutoresearchMode(sessionDoc); + state.ideas = summarizeIdeasBacklog(ideasBacklog); + + if (jsonl === null) { + return { + ...state, + secondaryMetrics: [], + }; + } + + const currentSecondaryMetrics = new Map(); + let currentRunIndex = 0; + let hasSeenAnyRun = false; + + const lines = jsonl + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + + for (const line of lines) { + let entry: JsonlEntry; + try { + entry = JSON.parse(line) as JsonlEntry; + } catch { + continue; + } + + if (entry.type === "config") { + if (entry.name) { + state.name = entry.name; + } + if (entry.metricName) { + state.metricName = entry.metricName; + } + if (entry.metricUnit !== undefined) { + state.metricUnit = entry.metricUnit; + } + if (entry.bestDirection === "lower" || entry.bestDirection === "higher") { + state.bestDirection = entry.bestDirection; + } + if (hasSeenAnyRun) { + state.currentSegment += 1; + } + state.currentRunCount = 0; + state.currentBaselineMetric = null; + state.currentBestMetric = null; + currentRunIndex = 0; + currentSecondaryMetrics.clear(); + continue; + } + + if (typeof entry.metric !== "number") { + continue; + } + + hasSeenAnyRun = true; + currentRunIndex += 1; + state.currentRunCount = currentRunIndex; + state.totalRunCount += 1; + + const run: AutoresearchRunSnapshot = { + run: typeof entry.run === "number" ? entry.run : currentRunIndex, + commit: entry.commit ?? "", + metric: entry.metric, + metrics: normalizeMetrics(entry.metrics), + status: entry.status ?? "keep", + description: entry.description ?? "", + timestamp: typeof entry.timestamp === "number" ? entry.timestamp : 0, + segment: typeof entry.segment === "number" ? entry.segment : state.currentSegment, + }; + + if (state.currentBaselineMetric === null) { + state.currentBaselineMetric = run.metric; + } + + if (run.status === "keep" && run.metric > 0) { + if ( + state.currentBestMetric === null || + isBetter(run.metric, state.currentBestMetric, state.bestDirection) + ) { + state.currentBestMetric = run.metric; + } + } + + for (const metricName of Object.keys(run.metrics)) { + if (!currentSecondaryMetrics.has(metricName)) { + currentSecondaryMetrics.set(metricName, { + name: metricName, + unit: inferMetricUnit(metricName), + }); + } + } + + state.lastRun = run; + } + + return { + ...state, + secondaryMetrics: [...currentSecondaryMetrics.values()], + }; +} + +function normalizeMetrics(metrics: Record | undefined): Record { + if (!metrics || typeof metrics !== "object") { + return {}; + } + + return Object.fromEntries( + Object.entries(metrics).filter(([, value]) => typeof value === "number"), + ); +} + +function detectAutoresearchMode(sessionDoc: string | null): AutoresearchMode { + if (sessionDoc === null) { + return "inactive"; + } + + const normalized = sessionDoc.toLowerCase(); + if ( + normalized.includes("# autoresearch") || + normalized.includes("## objective") || + normalized.includes("## what's been tried") || + normalized.includes("## how to run") + ) { + return "active"; + } + + return sessionDoc.trim().length > 0 ? "active" : "inactive"; +} + +function summarizeIdeasBacklog(ideasBacklog: string | null): AutoresearchIdeasSnapshot { + if (ideasBacklog === null) { + return { + hasBacklog: false, + pendingCount: 0, + preview: [], + }; + } + + const ideas = ideasBacklog + .split("\n") + .map((line) => line.trim()) + .filter((line) => /^([-*+]|\d+\.)\s+/.test(line)) + .map((line) => line.replace(/^([-*+]|\d+\.)\s+/, "").trim()) + .filter(Boolean); + + return { + hasBacklog: ideas.length > 0, + pendingCount: ideas.length, + preview: ideas.slice(0, 3), + }; +} + +function isBetter( + current: number, + best: number, + direction: "lower" | "higher", +): boolean { + return direction === "lower" ? current < best : current > best; +} + +function inferMetricUnit(name: string): string { + if (name.endsWith("_µs") || name.includes("µs")) { + return "µs"; + } + if (name.endsWith("_ms") || name.includes("ms")) { + return "ms"; + } + if (name.endsWith("_s") || name.includes("sec")) { + return "s"; + } + if (name.endsWith("_kb") || name.includes("kb")) { + return "kb"; + } + if (name.endsWith("_mb") || name.includes("mb")) { + return "mb"; + } + return ""; +} diff --git a/extensions/openclaw-autoresearch/src/tools/autoresearch-status.ts b/extensions/openclaw-autoresearch/src/tools/autoresearch-status.ts new file mode 100644 index 0000000..3a265e5 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/tools/autoresearch-status.ts @@ -0,0 +1,99 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { Type } from "@sinclair/typebox"; +import { reconstructStateFromJsonl, type AutoresearchStateSnapshot } from "../state.js"; +import { getAutoresearchRuntimeState, type AutoresearchRuntimeSnapshot } from "../runtime-state.js"; +import { resolveToolCwd } from "./tool-cwd.js"; + +const AutoresearchStatusParams = Type.Object( + { + cwd: Type.Optional( + Type.String({ + description: + "Optional working directory for repo-local autoresearch state. Use this when the tool call originates outside the target repo session cwd.", + }), + ), + }, + { additionalProperties: false }, +); + +export function createAutoresearchStatusTool(api: OpenClawPluginApi) { + return { + name: "autoresearch_status", + label: "Autoresearch Status", + description: + "Read-only summary of autoresearch state reconstructed from root-level files.", + parameters: AutoresearchStatusParams, + async execute( + _toolCallId: string, + params: { + cwd?: string; + }, + _signal: AbortSignal, + _onUpdate: unknown, + ) { + const cwd = resolveToolCwd(api, params.cwd); + const state = reconstructStateFromJsonl(cwd); + const runtimeState = getAutoresearchRuntimeState(cwd); + + return { + content: [{ type: "text" as const, text: formatAutoresearchStatusText(state, runtimeState) }], + details: { + status: "ok", + state, + runtime: runtimeState, + }, + }; + }, + }; +} + +export function formatAutoresearchStatusText( + state: AutoresearchStateSnapshot, + runtimeState?: AutoresearchRuntimeSnapshot, +): string { + const lines = [ + `Mode: ${state.mode}`, + `Session doc: ${state.hasSessionDoc ? "present" : "missing"}`, + `Ideas backlog: ${state.ideas.hasBacklog ? `${state.ideas.pendingCount} pending` : "empty"}`, + `Metric: ${state.metricName} (${state.metricUnit || "unitless"}, ${state.bestDirection} is better)`, + `Current segment: ${state.currentSegment}`, + `Runs: ${state.currentRunCount} current / ${state.totalRunCount} total`, + `Baseline: ${formatMetric(state.currentBaselineMetric, state.metricUnit)}`, + `Best kept: ${formatMetric(state.currentBestMetric, state.metricUnit)}`, + ]; + + if (state.name) { + lines.splice(1, 0, `Session: ${state.name}`); + } + + if (runtimeState) { + lines.splice( + state.name ? 2 : 1, + 0, + `Runtime mode: ${runtimeState.mode}`, + `Experiment window: ${runtimeState.runInFlight ? "running" : "idle"}`, + `Queued steers: ${runtimeState.queuedSteers.length}`, + ); + } + + if (state.lastRun) { + lines.push( + `Last run: #${state.lastRun.run} ${state.lastRun.status} ${formatMetric(state.lastRun.metric, state.metricUnit)} ${state.lastRun.commit} ${state.lastRun.description}`.trim(), + ); + } + + if (state.ideas.preview.length > 0) { + lines.push(`Ideas preview: ${state.ideas.preview.join(" | ")}`); + } + + return lines.join("\n"); +} + +function formatMetric(value: number | null, unit: string): string { + if (value === null) { + return "n/a"; + } + + const rendered = value === Math.round(value) ? `${Math.round(value)}` : value.toFixed(2); + return `${rendered}${unit}`; +} diff --git a/extensions/openclaw-autoresearch/src/tools/init-experiment.ts b/extensions/openclaw-autoresearch/src/tools/init-experiment.ts new file mode 100644 index 0000000..3a932de --- /dev/null +++ b/extensions/openclaw-autoresearch/src/tools/init-experiment.ts @@ -0,0 +1,90 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { InitExperimentParams } from "./schemas.js"; +import { createConfigHeader, writeConfigHeader } from "../logging.js"; +import { + createEmptyStateSnapshot, + reconstructStateFromJsonl, + type AutoresearchStateSnapshot, +} from "../state.js"; +import { resolveToolCwd } from "./tool-cwd.js"; + +export function createInitExperimentTool(api: OpenClawPluginApi) { + return { + name: "init_experiment", + label: "Init Experiment", + description: + "Initialize the experiment session. Call once before the first run_experiment to set the name, primary metric, unit, and direction. Writes the config header to autoresearch.jsonl.", + parameters: InitExperimentParams, + async execute( + _toolCallId: string, + params: { + cwd?: string; + name: string; + metric_name: string; + metric_unit?: string; + direction?: "lower" | "higher"; + }, + _signal: AbortSignal, + _onUpdate: unknown, + ) { + const cwd = resolveToolCwd(api, params.cwd); + const previousState = reconstructStateFromJsonl(cwd); + const isReinit = previousState.currentRunCount > 0; + const nextState: AutoresearchStateSnapshot = { + ...createEmptyStateSnapshot(), + name: params.name, + metricName: params.metric_name, + metricUnit: params.metric_unit ?? "", + bestDirection: params.direction ?? "lower", + currentSegment: isReinit ? previousState.currentSegment + 1 : previousState.currentSegment, + }; + + try { + writeConfigHeader( + cwd, + createConfigHeader({ + name: nextState.name ?? params.name, + metricName: nextState.metricName, + metricUnit: nextState.metricUnit, + bestDirection: nextState.bestDirection, + }), + isReinit ? "append" : "create", + ); + } catch (error) { + return { + content: [ + { + type: "text" as const, + text: `Failed to write autoresearch.jsonl: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + details: { + status: "error", + }, + }; + } + + const reinitNote = isReinit + ? " (re-initialized - previous results archived, new baseline needed)" + : ""; + + return { + content: [ + { + type: "text" as const, + text: + `Experiment initialized: "${nextState.name}"${reinitNote}\n` + + `Metric: ${nextState.metricName} (${nextState.metricUnit || "unitless"}, ${nextState.bestDirection} is better)\n` + + "Config written to autoresearch.jsonl. Now run the baseline with run_experiment.", + }, + ], + details: { + status: "ok", + state: nextState, + }, + }; + }, + }; +} diff --git a/extensions/openclaw-autoresearch/src/tools/log-experiment.ts b/extensions/openclaw-autoresearch/src/tools/log-experiment.ts new file mode 100644 index 0000000..0300677 --- /dev/null +++ b/extensions/openclaw-autoresearch/src/tools/log-experiment.ts @@ -0,0 +1,378 @@ +import * as fs from "node:fs"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { LogExperimentParams } from "./schemas.js"; +import { commitKeptExperiment } from "../git.js"; +import { appendResultEntry, type AutoresearchResultEntry } from "../logging.js"; +import { getAutoresearchRootFilePath } from "../files.js"; +import { + reconstructStateFromJsonl, + type AutoresearchStateSnapshot, + type SecondaryMetricDef, +} from "../state.js"; +import { + consumeAutoresearchSteers, + setAutoresearchRunInFlight, +} from "../runtime-state.js"; +import { resolveToolCwd } from "./tool-cwd.js"; + +export function createLogExperimentTool(api: OpenClawPluginApi) { + return { + name: "log_experiment", + label: "Log Experiment", + description: + "Record an experiment result. Tracks metrics and preserves keep/discard/crash semantics. Call after every run_experiment.", + parameters: LogExperimentParams, + async execute( + _toolCallId: string, + params: { + cwd?: string; + commit: string; + metric: number; + status: "keep" | "discard" | "crash"; + description: string; + metrics?: Record; + force?: boolean; + }, + _signal: AbortSignal, + _onUpdate: unknown, + ) { + const cwd = resolveToolCwd(api, params.cwd); + const state = reconstructStateFromJsonl(cwd); + const secondaryMetrics = params.metrics ?? {}; + + if (state.secondaryMetrics.length > 0) { + const validationError = validateSecondaryMetrics( + state.secondaryMetrics, + secondaryMetrics, + params.force ?? false, + ); + if (validationError) { + return { + content: [{ type: "text" as const, text: validationError }], + details: { + status: "error", + phase: "validate", + }, + }; + } + } + + const knownSecondaryMetrics = mergeSecondaryMetrics(state.secondaryMetrics, secondaryMetrics); + const currentResults = readCurrentSegmentResults(cwd, state.currentSegment); + const experiment: AutoresearchResultEntry = { + run: state.currentRunCount + 1, + commit: params.commit.slice(0, 7), + metric: params.metric, + metrics: secondaryMetrics, + status: params.status, + description: params.description, + timestamp: Date.now(), + segment: state.currentSegment, + }; + let finalExperiment = experiment; + + let gitSummary = ""; + let gitAction: Record = { + action: params.status === "keep" ? "commit" : "skip", + attempted: params.status === "keep", + }; + + if (params.status === "keep") { + const gitResult = commitKeptExperiment({ + cwd: cwd, + description: params.description, + metricName: state.metricName, + metric: params.metric, + metrics: secondaryMetrics, + commit: experiment.commit, + status: "keep", + }); + gitSummary = gitResult.summary; + gitAction = { + action: "commit", + attempted: true, + committed: gitResult.committed, + commit: gitResult.commit, + }; + if (gitResult.committed) { + finalExperiment = { + ...experiment, + commit: gitResult.commit, + }; + } + } else { + gitSummary = + `Git: skipped commit (${params.status}) - ` + + "revert tracked changes yourself with git checkout -- . when you want to discard them."; + gitAction = { + action: "skip", + attempted: false, + }; + } + + try { + appendResultEntry(cwd, finalExperiment); + } catch (error) { + return { + content: [ + { + type: "text" as const, + text: `Failed to append autoresearch.jsonl: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + details: { + status: "error", + phase: "write", + }, + }; + } + + const baselineMetric = + currentResults.length > 0 ? currentResults[0].metric : experiment.metric; + const baselineSecondaryMetrics = findBaselineSecondaryMetrics( + currentResults, + knownSecondaryMetrics, + ); + const nextState: AutoresearchStateSnapshot = reconstructStateFromJsonl(cwd); + const queuedSteers = consumeAutoresearchSteers(cwd); + setAutoresearchRunInFlight(cwd, false); + + return { + content: [ + { + type: "text" as const, + text: buildResultText({ + state, + experiment: finalExperiment, + baselineMetric, + baselineSecondaryMetrics, + totalRunCount: finalExperiment.run, + gitSummary, + knownSecondaryMetrics, + queuedSteers, + }), + }, + ], + details: { + status: "ok", + experiment: finalExperiment, + state: nextState, + git: gitAction, + }, + }; + }, + }; +} + +type CurrentSegmentResult = { + readonly metric: number; + readonly metrics: Record; +}; + +function validateSecondaryMetrics( + knownMetrics: readonly SecondaryMetricDef[], + providedMetrics: Record, + force: boolean, +): string | null { + const knownNames = new Set(knownMetrics.map((metric) => metric.name)); + const providedNames = new Set(Object.keys(providedMetrics)); + + const missing = [...knownNames].filter((name) => !providedNames.has(name)); + if (missing.length > 0) { + return ( + `Missing secondary metrics: ${missing.join(", ")}\n\n` + + `You must provide all previously tracked metrics. Expected: ${[...knownNames].join(", ")}\n` + + `Got: ${[...providedNames].join(", ") || "(none)"}\n\n` + + `Fix: include ${missing.map((name) => `"${name}": `).join(", ")} in the metrics parameter.` + ); + } + + const added = [...providedNames].filter((name) => !knownNames.has(name)); + if (added.length > 0 && !force) { + return ( + `New secondary metric${added.length > 1 ? "s" : ""} not previously tracked: ${added.join(", ")}\n\n` + + `Existing metrics: ${[...knownNames].join(", ")}\n\n` + + "If this metric has proven very valuable to watch, call log_experiment again with force: true to add it. Otherwise, remove it from the metrics parameter." + ); + } + + return null; +} + +function mergeSecondaryMetrics( + knownMetrics: readonly SecondaryMetricDef[], + providedMetrics: Record, +): readonly SecondaryMetricDef[] { + const merged = [...knownMetrics]; + for (const metricName of Object.keys(providedMetrics)) { + if (!merged.find((metric) => metric.name === metricName)) { + merged.push({ + name: metricName, + unit: inferMetricUnit(metricName), + }); + } + } + return merged; +} + +function readCurrentSegmentResults(cwd: string, segment: number): CurrentSegmentResult[] { + const jsonlPath = getAutoresearchRootFilePath(cwd, "resultsLog"); + if (!fs.existsSync(jsonlPath)) { + return []; + } + + const results: CurrentSegmentResult[] = []; + let currentSegment = 0; + let hasSeenResult = false; + const lines = fs + .readFileSync(jsonlPath, "utf8") + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + + for (const line of lines) { + let entry: Record; + try { + entry = JSON.parse(line) as Record; + } catch { + continue; + } + + if (entry.type === "config") { + if (hasSeenResult) { + currentSegment += 1; + } + continue; + } + + hasSeenResult = true; + if (currentSegment !== segment || typeof entry.metric !== "number") { + continue; + } + + results.push({ + metric: entry.metric, + metrics: + entry.metrics && typeof entry.metrics === "object" + ? (entry.metrics as Record) + : {}, + }); + } + + return results; +} + +function findBaselineSecondaryMetrics( + currentResults: readonly CurrentSegmentResult[], + secondaryMetrics: readonly SecondaryMetricDef[], +): Record { + const baseline = + currentResults.length > 0 ? { ...currentResults[0].metrics } : {}; + + for (const metric of secondaryMetrics) { + if (baseline[metric.name] !== undefined) { + continue; + } + for (const result of currentResults) { + const value = result.metrics[metric.name]; + if (value !== undefined) { + baseline[metric.name] = value; + break; + } + } + } + + return baseline; +} + +function buildResultText(options: { + state: AutoresearchStateSnapshot; + experiment: { + run: number; + commit: string; + metric: number; + metrics: Record; + status: "keep" | "discard" | "crash"; + description: string; + }; + baselineMetric: number; + baselineSecondaryMetrics: Record; + totalRunCount: number; + gitSummary: string; + knownSecondaryMetrics: readonly SecondaryMetricDef[]; + queuedSteers: readonly string[]; +}): string { + let text = `Logged #${options.experiment.run}: ${options.experiment.status} - ${options.experiment.description}`; + text += `\nBaseline ${options.state.metricName}: ${formatMetric(options.baselineMetric, options.state.metricUnit)}`; + + if (options.experiment.run > 1 && options.experiment.status === "keep" && options.experiment.metric > 0) { + const delta = options.experiment.metric - options.baselineMetric; + const pct = options.baselineMetric === 0 ? null : (delta / options.baselineMetric) * 100; + text += ` | this: ${formatMetric(options.experiment.metric, options.state.metricUnit)}`; + if (pct !== null) { + const sign = delta > 0 ? "+" : ""; + text += ` (${sign}${pct.toFixed(1)}%)`; + } + } + + if (Object.keys(options.experiment.metrics).length > 0) { + const parts = Object.entries(options.experiment.metrics).map(([name, value]) => { + const metricDef = options.knownSecondaryMetrics.find((metric) => metric.name === name); + let part = `${name}: ${formatMetric(value, metricDef?.unit ?? "")}`; + const baselineValue = options.baselineSecondaryMetrics[name]; + if ( + baselineValue !== undefined && + options.experiment.run > 1 && + baselineValue !== 0 + ) { + const delta = value - baselineValue; + const sign = delta > 0 ? "+" : ""; + part += ` (${sign}${((delta / baselineValue) * 100).toFixed(1)}%)`; + } + return part; + }); + text += `\nSecondary: ${parts.join(" ")}`; + } + + text += `\n(${options.totalRunCount} experiments in current segment)`; + text += `\n${options.gitSummary}`; + + if (options.queuedSteers.length > 0) { + const steerLabel = options.queuedSteers.length === 1 ? "steer" : "steers"; + text += `\n\nQueued user ${steerLabel} captured during this experiment:`; + for (const steer of options.queuedSteers) { + text += `\n- ${steer}`; + } + text += + "\nTreat any immediate followup turn that repeats the same steer as the normal OpenClaw queue/backlog delivery for these messages."; + } + + return text; +} + +function inferMetricUnit(name: string): string { + if (name.endsWith("_µs") || name.includes("µs")) { + return "µs"; + } + if (name.endsWith("_ms") || name.includes("ms")) { + return "ms"; + } + if (name.endsWith("_s") || name.includes("sec")) { + return "s"; + } + return ""; +} + +function formatMetric(value: number, unit: string): string { + const rendered = + value === Math.round(value) ? `${Math.round(value)}` : value.toFixed(2); + return `${addCommas(rendered)}${unit}`; +} + +function addCommas(value: string): string { + const [integerPart, fractionalPart] = value.split("."); + const normalizedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return fractionalPart ? `${normalizedInteger}.${fractionalPart}` : normalizedInteger; +} diff --git a/extensions/openclaw-autoresearch/src/tools/run-experiment.ts b/extensions/openclaw-autoresearch/src/tools/run-experiment.ts new file mode 100644 index 0000000..d6fe32c --- /dev/null +++ b/extensions/openclaw-autoresearch/src/tools/run-experiment.ts @@ -0,0 +1,64 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { RunExperimentParams } from "./schemas.js"; +import { executeExperimentCommand } from "../execute.js"; +import { setAutoresearchRunInFlight } from "../runtime-state.js"; +import { resolveToolCwd } from "./tool-cwd.js"; + +export function createRunExperimentTool(api: OpenClawPluginApi) { + return { + name: "run_experiment", + label: "Run Experiment", + description: + "Run a shell command as an experiment. Times wall-clock duration, captures output, detects pass/fail via exit code. Use for any autoresearch experiment.", + parameters: RunExperimentParams, + async execute( + _toolCallId: string, + params: { + cwd?: string; + command: string; + timeout_seconds?: number; + }, + signal: AbortSignal, + onUpdate: ((update: unknown) => void | Promise) | undefined, + ) { + const cwd = resolveToolCwd(api, params.cwd); + setAutoresearchRunInFlight(cwd, true); + + if (onUpdate) { + await onUpdate({ + content: [{ type: "text" as const, text: `Running: ${params.command}` }], + details: { phase: "running" }, + }); + } + + let details; + try { + details = await executeExperimentCommand({ + command: params.command, + cwd, + timeoutSeconds: params.timeout_seconds, + signal, + }); + } catch (error) { + setAutoresearchRunInFlight(cwd, false); + throw error; + } + + let text = ""; + if (details.timedOut) { + text += `TIMEOUT after ${details.durationSeconds.toFixed(1)}s\n`; + } else if (!details.passed) { + text += `FAILED (exit code ${details.exitCode ?? "null"}) in ${details.durationSeconds.toFixed(1)}s\n`; + } else { + text += `PASSED in ${details.durationSeconds.toFixed(1)}s\n`; + } + + text += `\nLast 80 lines of output:\n${details.tailOutput || "(no output)"}`; + + return { + content: [{ type: "text" as const, text }], + details, + }; + }, + }; +} diff --git a/extensions/openclaw-autoresearch/src/tools/schemas.ts b/extensions/openclaw-autoresearch/src/tools/schemas.ts new file mode 100644 index 0000000..8622aaa --- /dev/null +++ b/extensions/openclaw-autoresearch/src/tools/schemas.ts @@ -0,0 +1,72 @@ +import { Type } from "@sinclair/typebox"; + +const CwdParam = Type.Optional( + Type.String({ + description: + "Optional working directory for repo-local autoresearch state. Use this when the tool call originates outside the target repo session cwd.", + }), +); + +export const InitExperimentParams = Type.Object({ + cwd: CwdParam, + name: Type.String({ + description: + 'Human-readable name for this experiment session (e.g. "Optimizing liquid for fastest execution and parsing")', + }), + metric_name: Type.String({ + description: + 'Display name for the primary metric (e.g. "total_µs", "bundle_kb", "val_bpb").', + }), + metric_unit: Type.Optional( + Type.String({ + description: + 'Unit for the primary metric. Use "µs", "ms", "s", "kb", "mb", or "" for unitless. Default: "".', + }), + ), + direction: Type.Optional( + Type.String({ + description: 'Whether "lower" or "higher" is better for the primary metric. Default: "lower".', + enum: ["lower", "higher"], + }), + ), +}); + +export const RunExperimentParams = Type.Object({ + cwd: CwdParam, + command: Type.String({ + description: "Shell command to run (e.g. 'pnpm test:vitest', 'uv run train.py')", + }), + timeout_seconds: Type.Optional( + Type.Number({ + description: "Kill after this many seconds (default: 600)", + }), + ), +}); + +export const LogExperimentParams = Type.Object({ + cwd: CwdParam, + commit: Type.String({ description: "Git commit hash (short, 7 chars)" }), + metric: Type.Number({ + description: + "The primary optimization metric value (e.g. seconds, val_bpb). Use 0 for crashes.", + }), + status: Type.String({ + description: "Result status for this experiment.", + enum: ["keep", "discard", "crash"], + }), + description: Type.String({ + description: "Short description of what this experiment tried.", + }), + metrics: Type.Optional( + Type.Record(Type.String(), Type.Number(), { + description: + 'Additional metrics to track as { name: value } pairs, e.g. { "compile_µs": 4200, "render_µs": 9800 }.', + }), + ), + force: Type.Optional( + Type.Boolean({ + description: + "Set to true to allow adding a new secondary metric that was not tracked before.", + }), + ), +}); diff --git a/extensions/openclaw-autoresearch/src/tools/tool-cwd.ts b/extensions/openclaw-autoresearch/src/tools/tool-cwd.ts new file mode 100644 index 0000000..309fa6b --- /dev/null +++ b/extensions/openclaw-autoresearch/src/tools/tool-cwd.ts @@ -0,0 +1,19 @@ +import path from "node:path"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; + +export function resolveToolCwd(api: OpenClawPluginApi, requestedCwd?: unknown): string { + const normalizedRequestedCwd = + typeof requestedCwd === "string" ? requestedCwd.trim() : ""; + + const cwd = normalizedRequestedCwd + ? path.isAbsolute(normalizedRequestedCwd) + ? normalizedRequestedCwd + : api.resolvePath(normalizedRequestedCwd) + : api.resolvePath("."); + + if (typeof cwd !== "string" || cwd.trim().length === 0) { + throw new Error("Could not resolve repo cwd for autoresearch tool execution."); + } + + return cwd; +} diff --git a/extensions/pi-autoresearch/index.ts b/extensions/pi-autoresearch/index.ts deleted file mode 100644 index ff177fb..0000000 --- a/extensions/pi-autoresearch/index.ts +++ /dev/null @@ -1,1464 +0,0 @@ -/** - * autoresearch — Pi Extension - * - * Generic autonomous experiment loop infrastructure. - * Domain-specific behavior comes from skills (what command to run, what to optimize). - * - * Provides: - * - `run_experiment` tool — runs any command, times it, captures output, detects pass/fail - * - `log_experiment` tool — records results with session-persisted state - * - Status widget showing experiment count + best metric - * - Ctrl+X toggle to expand/collapse full dashboard inline above the editor - * - Injects autoresearch.md into context on every turn via before_agent_start - */ - -import type { - ExtensionAPI, - ExtensionContext, - Theme, -} from "@mariozechner/pi-coding-agent"; -import { truncateTail } from "@mariozechner/pi-coding-agent"; -import { StringEnum } from "@mariozechner/pi-ai"; -import { Text, truncateToWidth, matchesKey, visibleWidth } from "@mariozechner/pi-tui"; -import { Type } from "@sinclair/typebox"; -import * as fs from "node:fs"; -import * as path from "node:path"; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface ExperimentResult { - commit: string; - metric: number; - /** Additional tracked metrics: { name: value } */ - metrics: Record; - status: "keep" | "discard" | "crash"; - description: string; - timestamp: number; - /** Segment index — increments on each config header. Current segment = highest. */ - segment: number; -} - -interface MetricDef { - name: string; - unit: string; -} - -interface ExperimentState { - results: ExperimentResult[]; - /** Baseline primary metric (from first experiment in current segment) */ - bestMetric: number | null; - bestDirection: "lower" | "higher"; - metricName: string; - metricUnit: string; - /** Definitions for secondary metrics (order preserved) */ - secondaryMetrics: MetricDef[]; - name: string | null; - /** Current segment index (incremented on each init_experiment) */ - currentSegment: number; -} - -interface RunDetails { - command: string; - exitCode: number | null; - durationSeconds: number; - passed: boolean; - crashed: boolean; - timedOut: boolean; - tailOutput: string; -} - -interface LogDetails { - experiment: ExperimentResult; - state: ExperimentState; -} - -// --------------------------------------------------------------------------- -// Tool Schemas -// --------------------------------------------------------------------------- - -const RunParams = Type.Object({ - command: Type.String({ - description: - "Shell command to run (e.g. 'pnpm test:vitest', 'uv run train.py')", - }), - timeout_seconds: Type.Optional( - Type.Number({ - description: "Kill after this many seconds (default: 600)", - }) - ), -}); - -const InitParams = Type.Object({ - name: Type.String({ - description: - 'Human-readable name for this experiment session (e.g. "Optimizing liquid for fastest execution and parsing")', - }), - metric_name: Type.String({ - description: - 'Display name for the primary metric (e.g. "total_µs", "bundle_kb", "val_bpb"). Shown in dashboard headers.', - }), - metric_unit: Type.Optional( - Type.String({ - description: - 'Unit for the primary metric. Use "µs", "ms", "s", "kb", "mb", or "" for unitless. Affects number formatting. Default: ""', - }) - ), - direction: Type.Optional( - Type.String({ - description: - 'Whether "lower" or "higher" is better for the primary metric. Default: "lower".', - }) - ), -}); - -const LogParams = Type.Object({ - commit: Type.String({ description: "Git commit hash (short, 7 chars)" }), - metric: Type.Number({ - description: - "The primary optimization metric value (e.g. seconds, val_bpb). 0 for crashes.", - }), - status: StringEnum(["keep", "discard", "crash"] as const), - description: Type.String({ - description: "Short description of what this experiment tried", - }), - metrics: Type.Optional( - Type.Record(Type.String(), Type.Number(), { - description: - 'Additional metrics to track as { name: value } pairs, e.g. { "compile_µs": 4200, "render_µs": 9800 }. These are shown alongside the primary metric for tradeoff monitoring.', - }) - ), - force: Type.Optional( - Type.Boolean({ - description: - "Set to true to allow adding a new secondary metric that wasn't tracked before. Only use for metrics that have proven very valuable to watch.", - }) - ), -}); - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -/** Format a number with comma-separated thousands: 15586 → "15,586" */ -function commas(n: number): string { - const s = String(Math.round(n)); - const parts: string[] = []; - for (let i = s.length; i > 0; i -= 3) { - parts.unshift(s.slice(Math.max(0, i - 3), i)); - } - return parts.join(","); -} - -/** Format number with commas, preserving one decimal for fractional values */ -function fmtNum(n: number, decimals: number = 0): string { - if (decimals > 0) { - const int = Math.floor(Math.abs(n)); - const frac = (Math.abs(n) - int).toFixed(decimals).slice(1); // ".3" - return (n < 0 ? "-" : "") + commas(int) + frac; - } - return commas(n); -} - -function formatNum(value: number | null, unit: string): string { - if (value === null) return "—"; - const u = unit || ""; - // Integers: no decimals - if (value === Math.round(value)) return fmtNum(value) + u; - // Fractional: 2 decimal places - return fmtNum(value, 2) + u; -} - -function isBetter( - current: number, - best: number, - direction: "lower" | "higher" -): boolean { - return direction === "lower" ? current < best : current > best; -} - -/** Get results in the current segment only */ -function currentResults(results: ExperimentResult[], segment: number): ExperimentResult[] { - return results.filter((r) => r.segment === segment); -} - -/** Baseline = first experiment in current segment */ -function findBaselineMetric(results: ExperimentResult[], segment: number): number | null { - const cur = currentResults(results, segment); - return cur.length > 0 ? cur[0].metric : null; -} - -/** - * Find secondary metric baselines from the first experiment in current segment. - * For metrics that didn't exist at baseline time, falls back to the first - * occurrence of that metric in the current segment. - */ -function findBaselineSecondary( - results: ExperimentResult[], - segment: number, - knownMetrics?: MetricDef[] -): Record { - const cur = currentResults(results, segment); - const base: Record = cur.length > 0 - ? { ...(cur[0].metrics ?? {}) } - : {}; - - // Fill in any known metrics missing from baseline with their first occurrence - if (knownMetrics) { - for (const sm of knownMetrics) { - if (base[sm.name] === undefined) { - for (const r of cur) { - const val = (r.metrics ?? {})[sm.name]; - if (val !== undefined) { - base[sm.name] = val; - break; - } - } - } - } - } - - return base; -} - -// --------------------------------------------------------------------------- -// Extension -// --------------------------------------------------------------------------- - -// --------------------------------------------------------------------------- -// Dashboard table renderer (pure function, no UI deps) -// --------------------------------------------------------------------------- - -function renderDashboardLines( - st: ExperimentState, - width: number, - th: Theme, - maxRows: number = 6 -): string[] { - const lines: string[] = []; - - if (st.results.length === 0) { - lines.push(` ${th.fg("dim", "No experiments yet.")}`); - return lines; - } - - const cur = currentResults(st.results, st.currentSegment); - const kept = cur.filter((r) => r.status === "keep").length; - const discarded = cur.filter((r) => r.status === "discard").length; - const crashed = cur.filter((r) => r.status === "crash").length; - - const baseline = st.bestMetric; - const baselineSec = findBaselineSecondary(st.results, st.currentSegment, st.secondaryMetrics); - - // Find best kept primary metric and its run number (current segment only) - let bestPrimary: number | null = null; - let bestSecondary: Record = {}; - let bestRunNum = 0; - for (let i = st.results.length - 1; i >= 0; i--) { - const r = st.results[i]; - if (r.segment !== st.currentSegment) continue; - if (r.status === "keep" && r.metric > 0) { - if (bestPrimary === null || isBetter(r.metric, bestPrimary, st.bestDirection)) { - bestPrimary = r.metric; - bestSecondary = r.metrics ?? {}; - bestRunNum = i + 1; - } - } - } - - // Runs summary - lines.push( - truncateToWidth( - ` ${th.fg("muted", "Runs:")} ${th.fg("text", String(st.results.length))}` + - ` ${th.fg("success", `${kept} kept`)}` + - (discarded > 0 ? ` ${th.fg("warning", `${discarded} discarded`)}` : "") + - (crashed > 0 ? ` ${th.fg("error", `${crashed} crashed`)}` : ""), - width - ) - ); - - // Baseline: first run's primary metric - lines.push( - truncateToWidth( - ` ${th.fg("muted", "Baseline:")} ${th.fg("dim", `★ ${st.metricName}: ${formatNum(baseline, st.metricUnit)} #1`)}`, - width - ) - ); - - - // Progress: best primary metric with delta + run number - if (bestPrimary !== null) { - let progressLine = ` ${th.fg("muted", "Progress:")} ${th.fg("warning", th.bold(`★ ${st.metricName}: ${formatNum(bestPrimary, st.metricUnit)}`))}${th.fg("dim", ` #${bestRunNum}`)}`; - - if (baseline !== null && baseline !== 0 && bestPrimary !== baseline) { - const pct = ((bestPrimary - baseline) / baseline) * 100; - const sign = pct > 0 ? "+" : ""; - const color = isBetter(bestPrimary, baseline, st.bestDirection) ? "success" : "error"; - progressLine += th.fg(color, ` (${sign}${pct.toFixed(1)}%)`); - } - - lines.push(truncateToWidth(progressLine, width)); - - // Progress secondary metrics on next line with deltas - if (st.secondaryMetrics.length > 0) { - const secParts: string[] = []; - for (const sm of st.secondaryMetrics) { - const val = bestSecondary[sm.name]; - const bv = baselineSec[sm.name]; - if (val !== undefined) { - let part = `${sm.name}: ${formatNum(val, sm.unit)}`; - if (bv !== undefined && bv !== 0 && val !== bv) { - const p = ((val - bv) / bv) * 100; - const s = p > 0 ? "+" : ""; - const c = val <= bv ? "success" : "error"; - part += th.fg(c, ` ${s}${p.toFixed(1)}%`); - } - secParts.push(part); - } - } - if (secParts.length > 0) { - lines.push( - truncateToWidth( - ` ${th.fg("dim", " ")}${th.fg("muted", secParts.join(" "))}`, - width - ) - ); - } - } - } - - lines.push(""); - - // Determine visible rows for column pruning - const effectiveMax = maxRows <= 0 ? st.results.length : maxRows; - const startIdx = Math.max(0, st.results.length - effectiveMax); - const visibleRows = st.results.slice(startIdx); - - // Only show secondary metric columns that have at least one value in visible rows - const secMetrics = st.secondaryMetrics.filter((sm) => - visibleRows.some((r) => (r.metrics ?? {})[sm.name] !== undefined) - ); - - // Column definitions - const col = { idx: 3, commit: 8, primary: 11, status: 8 }; - const secColWidth = 11; - const totalSecWidth = secMetrics.length * secColWidth; - const descW = Math.max( - 10, - width - col.idx - col.commit - col.primary - totalSecWidth - col.status - 6 - ); - - // Table header — primary metric name bolded with ★ - let headerLine = - ` ${th.fg("muted", "#".padEnd(col.idx))}` + - `${th.fg("muted", "commit".padEnd(col.commit))}` + - `${th.fg("warning", th.bold(("★ " + st.metricName).slice(0, col.primary - 1).padEnd(col.primary)))}`; - - for (const sm of secMetrics) { - headerLine += th.fg( - "muted", - sm.name.slice(0, secColWidth - 1).padEnd(secColWidth) - ); - } - - headerLine += - `${th.fg("muted", "status".padEnd(col.status))}` + - `${th.fg("muted", "description")}`; - - lines.push(truncateToWidth(headerLine, width)); - lines.push( - truncateToWidth( - ` ${th.fg("borderMuted", "─".repeat(width - 4))}`, - width - ) - ); - - // Baseline values for delta display (current segment only) - const baselinePrimary = findBaselineMetric(st.results, st.currentSegment); - const baselineSecondary = findBaselineSecondary( - st.results, - st.currentSegment, - st.secondaryMetrics - ); - - // Show max 6 recent runs, with a note about hidden earlier ones - if (startIdx > 0) { - lines.push( - truncateToWidth( - ` ${th.fg("dim", `… ${startIdx} earlier run${startIdx === 1 ? "" : "s"}`)}`, - width - ) - ); - } - - for (let i = startIdx; i < st.results.length; i++) { - const r = st.results[i]; - const isOld = r.segment !== st.currentSegment; - const isBaseline = !isOld && i === st.results.findIndex((x) => x.segment === st.currentSegment); - - const color = isOld - ? "dim" - : r.status === "keep" - ? "success" - : r.status === "crash" - ? "error" - : "warning"; - - // Primary metric with color coding - const primaryStr = formatNum(r.metric, st.metricUnit); - let primaryColor: string = isOld ? "dim" : "text"; - if (!isOld) { - if (isBaseline) { - primaryColor = "muted"; // baseline row - } else if ( - baselinePrimary !== null && - r.status === "keep" && - r.metric > 0 - ) { - if (isBetter(r.metric, baselinePrimary, st.bestDirection)) { - primaryColor = "success"; - } else if (r.metric !== baselinePrimary) { - primaryColor = "error"; - } - } - } - - const idxStr = th.fg("dim", String(i + 1).padEnd(col.idx)); - const commitStr = isOld ? "(old)".padEnd(col.commit) : r.commit.padEnd(col.commit); - - let rowLine = - ` ${idxStr}` + - `${th.fg(isOld ? "dim" : "accent", commitStr)}` + - `${th.fg(primaryColor, isOld ? primaryStr.padEnd(col.primary) : th.bold(primaryStr.padEnd(col.primary)))}`; - - // Secondary metrics - const rowMetrics = r.metrics ?? {}; - for (const sm of secMetrics) { - const val = rowMetrics[sm.name]; - if (val !== undefined) { - const secStr = formatNum(val, sm.unit); - let secColor: string = "dim"; - if (!isOld) { - const bv = baselineSecondary[sm.name]; - if (isBaseline) { - secColor = "muted"; // baseline row - } else if (bv !== undefined && bv !== 0) { - secColor = val <= bv ? "success" : "error"; - } - } - rowLine += th.fg(secColor, secStr.padEnd(secColWidth)); - } else { - rowLine += th.fg("dim", "—".padEnd(secColWidth)); - } - } - - rowLine += - `${th.fg(color, r.status.padEnd(col.status))}` + - `${th.fg("muted", r.description.slice(0, descW))}`; - - lines.push(truncateToWidth(rowLine, width)); - } - - return lines; -} - -// --------------------------------------------------------------------------- -// Extension -// --------------------------------------------------------------------------- - -export default function autoresearchExtension(pi: ExtensionAPI) { - let dashboardExpanded = false; - let autoresearchMode = false; - let lastCtx: ExtensionContext | null = null; - - // Message queue: user steers are held until the next log_experiment call - let messageQueue: string[] = []; - - // Running experiment state (for spinner in fullscreen overlay) - let runningExperiment: { startedAt: number; command: string } | null = null; - let overlayTui: { requestRender: () => void } | null = null; - let spinnerInterval: ReturnType | null = null; - let spinnerFrame = 0; - const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; - - let state: ExperimentState = { - results: [], - bestMetric: null, - bestDirection: "lower", - metricName: "metric", - metricUnit: "", - secondaryMetrics: [], - name: null, - currentSegment: 0, - }; - - // ----------------------------------------------------------------------- - // State reconstruction - // ----------------------------------------------------------------------- - - const reconstructState = (ctx: ExtensionContext) => { - state = { - results: [], - bestMetric: null, - bestDirection: "lower", - metricName: "metric", - metricUnit: "", - secondaryMetrics: [], - name: null, - currentSegment: 0, - }; - - // Primary: read from autoresearch.jsonl (alongside autoresearch.md/sh) - const jsonlPath = path.join(ctx.cwd, "autoresearch.jsonl"); - let loadedFromJsonl = false; - try { - if (fs.existsSync(jsonlPath)) { - let segment = 0; - const lines = fs.readFileSync(jsonlPath, "utf-8").trim().split("\n").filter(Boolean); - for (const line of lines) { - try { - const entry = JSON.parse(line); - - // Config header line — each header starts a new segment - if (entry.type === "config") { - if (entry.name) state.name = entry.name; - if (entry.metricName) state.metricName = entry.metricName; - if (entry.metricUnit !== undefined) state.metricUnit = entry.metricUnit; - if (entry.bestDirection) state.bestDirection = entry.bestDirection; - // Increment segment (first config = 0, second = 1, etc.) - if (state.results.length > 0) segment++; - state.currentSegment = segment; - continue; - } - - // Experiment result line - state.results.push({ - commit: entry.commit ?? "", - metric: entry.metric ?? 0, - metrics: entry.metrics ?? {}, - status: entry.status ?? "keep", - description: entry.description ?? "", - timestamp: entry.timestamp ?? 0, - segment, - }); - - // Register secondary metrics - for (const name of Object.keys(entry.metrics ?? {})) { - if (!state.secondaryMetrics.find((m) => m.name === name)) { - let unit = ""; - if (name.endsWith("_µs") || name.includes("µs")) unit = "µs"; - else if (name.endsWith("_ms") || name.includes("ms")) unit = "ms"; - else if (name.endsWith("_s") || name.includes("sec")) unit = "s"; - state.secondaryMetrics.push({ name, unit }); - } - } - } catch { - // Skip malformed lines - } - } - if (state.results.length > 0) { - loadedFromJsonl = true; - state.bestMetric = findBaselineMetric(state.results, state.currentSegment); - } - } - } catch { - // Fall through to session history - } - - // Fallback: reconstruct from session history (backward compat) - if (!loadedFromJsonl) { - for (const entry of ctx.sessionManager.getBranch()) { - if (entry.type !== "message") continue; - const msg = entry.message; - if (msg.role !== "toolResult" || msg.toolName !== "log_experiment") - continue; - const details = msg.details as LogDetails | undefined; - if (details?.state) { - state = details.state; - if (!state.secondaryMetrics) state.secondaryMetrics = []; - if (state.metricUnit === "s" && state.metricName === "metric") { - state.metricUnit = ""; - } - for (const r of state.results) { - if (!r.metrics) r.metrics = {}; - } - } - } - } - - // Also detect autoresearch mode from file presence - if (fs.existsSync(path.join(ctx.cwd, "autoresearch.md"))) { - autoresearchMode = true; - } - - updateWidget(ctx); - }; - - const updateWidget = (ctx: ExtensionContext) => { - if (!ctx.hasUI) return; - lastCtx = ctx; - - if (state.results.length === 0) { - ctx.ui.setWidget("autoresearch", undefined); - return; - } - - if (dashboardExpanded) { - // Expanded: full dashboard table rendered as widget - ctx.ui.setWidget("autoresearch", (_tui, theme) => { - const width = process.stdout.columns || 120; - const lines: string[] = []; - - const hintText = " ctrl+x collapse • ctrl+shift+x fullscreen "; - const labelPrefix = "🔬 autoresearch"; - const nameStr = state.name ? `: ${state.name}` : ""; - // 3 leading dashes + space + label + space + fill + hint - const maxLabelLen = width - 3 - 2 - hintText.length - 1; - let label = labelPrefix + nameStr; - if (label.length > maxLabelLen) { - label = label.slice(0, maxLabelLen - 1) + "…"; - } - const fillLen = Math.max(0, width - 3 - 1 - label.length - 1 - hintText.length); - lines.push( - truncateToWidth( - theme.fg("borderMuted", "───") + - theme.fg("accent", " " + label + " ") + - theme.fg("borderMuted", "─".repeat(fillLen)) + - theme.fg("dim", hintText), - width - ) - ); - - lines.push(...renderDashboardLines(state, width, theme)); - - return new Text(lines.join("\n"), 0, 0); - }); - } else { - // Collapsed: compact one-liner — compute everything inside render - ctx.ui.setWidget("autoresearch", (_tui, theme) => { - const cur = currentResults(state.results, state.currentSegment); - const kept = cur.filter((r) => r.status === "keep").length; - const crashed = cur.filter((r) => r.status === "crash").length; - const baseline = state.bestMetric; - const baselineSec = findBaselineSecondary(state.results, state.currentSegment, state.secondaryMetrics); - - // Find best kept primary metric, its secondary values, and run number - let bestPrimary: number | null = null; - let bestSec: Record = {}; - let bestRunNum = 0; - for (let i = state.results.length - 1; i >= 0; i--) { - const r = state.results[i]; - if (r.segment !== state.currentSegment) continue; - if (r.status === "keep" && r.metric > 0) { - if (bestPrimary === null || isBetter(r.metric, bestPrimary, state.bestDirection)) { - bestPrimary = r.metric; - bestSec = r.metrics ?? {}; - bestRunNum = i + 1; - } - } - } - - const displayVal = bestPrimary ?? baseline; - const parts = [ - theme.fg("accent", "🔬"), - theme.fg("muted", ` ${state.results.length} runs`), - theme.fg("success", ` ${kept} kept`), - crashed > 0 ? theme.fg("error", ` ${crashed}💥`) : "", - theme.fg("dim", " │ "), - theme.fg("warning", theme.bold(`★ ${state.metricName}: ${formatNum(displayVal, state.metricUnit)}`)), - bestRunNum > 0 ? theme.fg("dim", ` #${bestRunNum}`) : "", - ]; - - // Show delta % vs baseline for primary - if (baseline !== null && bestPrimary !== null && baseline !== 0 && bestPrimary !== baseline) { - const pct = ((bestPrimary - baseline) / baseline) * 100; - const sign = pct > 0 ? "+" : ""; - const deltaColor = isBetter(bestPrimary, baseline, state.bestDirection) ? "success" : "error"; - parts.push(theme.fg(deltaColor, ` (${sign}${pct.toFixed(1)}%)`)); - } - - // Show secondary metrics with delta % - if (state.secondaryMetrics.length > 0) { - for (const sm of state.secondaryMetrics) { - const val = bestSec[sm.name]; - const bv = baselineSec[sm.name]; - if (val !== undefined) { - parts.push(theme.fg("dim", " ")); - let secText = `${sm.name}: ${formatNum(val, sm.unit)}`; - if (bv !== undefined && bv !== 0 && val !== bv) { - const p = ((val - bv) / bv) * 100; - const s = p > 0 ? "+" : ""; - const c = val <= bv ? "success" : "error"; - secText += theme.fg(c, ` ${s}${p.toFixed(1)}%`); - } - parts.push(theme.fg("muted", secText)); - } - } - } - - if (state.name) { - parts.push(theme.fg("dim", ` │ ${state.name}`)); - } - - parts.push(theme.fg("dim", " (ctrl+x expand • ctrl+shift+x fullscreen)")); - - return new Text(parts.join(""), 0, 0); - }); - } - }; - - pi.on("session_start", async (_e, ctx) => reconstructState(ctx)); - pi.on("session_switch", async (_e, ctx) => reconstructState(ctx)); - pi.on("session_fork", async (_e, ctx) => reconstructState(ctx)); - pi.on("session_tree", async (_e, ctx) => reconstructState(ctx)); - - // Clear running experiment state when agent stops; check ideas file for continuation - pi.on("agent_end", async (_event, ctx) => { - runningExperiment = null; - if (overlayTui) overlayTui.requestRender(); - - if (!autoresearchMode) return; - - const ideasPath = path.join(ctx.cwd, "autoresearch.ideas.md"); - if (fs.existsSync(ideasPath)) { - // Ideas file exists — send continuation message to pick up where we left off - pi.sendUserMessage( - "The optimization loop stopped. Read autoresearch.ideas.md — use the ideas as inspiration for new experiment paths. " + - "Prune any ideas that are duplicated, already tried, or clearly bad. Then create experiments based on what remains. " + - "If nothing useful is left, see if you can come up with your own ideas. " + - "If you've exhausted all paths, delete autoresearch.ideas.md and write a final report." - ); - } - }); - - // Queue user messages during active autoresearch runs — flush after log_experiment - pi.on("input", async (event, ctx) => { - if (!autoresearchMode) return { action: "continue" as const }; - if (ctx.isIdle()) return { action: "continue" as const }; - // Only queue interactive user messages, not extension-injected ones - if (event.source !== "interactive") return { action: "continue" as const }; - // Don't queue commands (/, !, !!) - if (typeof event.text === "string" && /^[\/!]/.test(event.text.trim())) return { action: "continue" as const }; - - const text = typeof event.text === "string" ? event.text : ""; - if (!text.trim()) return { action: "continue" as const }; - - messageQueue.push(text); - ctx.ui.notify(`Queued steer (will apply after next log_experiment): "${text.slice(0, 60)}${text.length > 60 ? "…" : ""}"`, "info"); - return { action: "handled" as const }; - }); - - // When in autoresearch mode, add a static note to the system prompt. - // Only a short pointer — no file content, fully cache-safe. - pi.on("before_agent_start", async (event, ctx) => { - if (!autoresearchMode) return; - - const mdPath = path.join(ctx.cwd, "autoresearch.md"); - const ideasPath = path.join(ctx.cwd, "autoresearch.ideas.md"); - - let extra = - "\n\n## Autoresearch Mode (ACTIVE)" + - "\nYou are in autoresearch mode. Optimize the primary metric through an autonomous experiment loop." + - "\nUse init_experiment, run_experiment, and log_experiment tools. NEVER STOP until interrupted." + - `\nExperiment rules: ${mdPath} — read this file at the start of every session and after compaction.` + - "\n\n### Ideas Backlog" + - `\nIf you discover complex but promising optimizations you decide not to pursue yet, append them as bullet points to ${ideasPath}.` + - "\nThis serves as a backlog of future experiment paths. Don't let good ideas get lost — write them down." + - "\n\n### User Steers" + - "\nUser messages during experiments are ideas to try. They are automatically queued and delivered after your next log_experiment call." + - "\nDo NOT stop or ask for confirmation — finish your current experiment, log it, and then incorporate the user's idea in the next experiment."; - - if (messageQueue.length > 0) { - extra += `\n\n(${messageQueue.length} user steer${messageQueue.length > 1 ? "s" : ""} queued — will be delivered after next log_experiment.)`; - } - - return { - systemPrompt: event.systemPrompt + extra, - }; - }); - - // ----------------------------------------------------------------------- - // init_experiment tool — one-time setup - // ----------------------------------------------------------------------- - - pi.registerTool({ - name: "init_experiment", - label: "Init Experiment", - description: - "Initialize the experiment session. Call once before the first run_experiment to set the name, primary metric, unit, and direction. Writes the config header to autoresearch.jsonl.", - promptSnippet: - "Initialize experiment session (name, metric, unit, direction). Call once before first run.", - promptGuidelines: [ - "Call init_experiment exactly once at the start of an autoresearch session, before the first run_experiment.", - "If autoresearch.jsonl already exists with a config, do NOT call init_experiment again.", - "If the optimization target changes (different benchmark, metric, or workload), call init_experiment again to insert a new config header and reset the baseline.", - ], - parameters: InitParams, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const isReinit = state.results.length > 0; - - state.name = params.name; - state.metricName = params.metric_name; - state.metricUnit = params.metric_unit ?? ""; - if (params.direction === "lower" || params.direction === "higher") { - state.bestDirection = params.direction; - } - - // Reset results for new baseline segment - state.results = []; - state.bestMetric = null; - state.secondaryMetrics = []; - - // Write config header to jsonl (append for re-init, create for first) - try { - const jsonlPath = path.join(ctx.cwd, "autoresearch.jsonl"); - const config = JSON.stringify({ - type: "config", - name: state.name, - metricName: state.metricName, - metricUnit: state.metricUnit, - bestDirection: state.bestDirection, - }); - if (isReinit) { - fs.appendFileSync(jsonlPath, config + "\n"); - } else { - fs.writeFileSync(jsonlPath, config + "\n"); - } - } catch (e) { - return { - content: [{ - type: "text", - text: `⚠️ Failed to write autoresearch.jsonl: ${e instanceof Error ? e.message : String(e)}`, - }], - details: {}, - }; - } - - autoresearchMode = true; - updateWidget(ctx); - - const reinitNote = isReinit ? " (re-initialized — previous results archived, new baseline needed)" : ""; - return { - content: [{ - type: "text", - text: `✅ Experiment initialized: "${state.name}"${reinitNote}\nMetric: ${state.metricName} (${state.metricUnit || "unitless"}, ${state.bestDirection} is better)\nConfig written to autoresearch.jsonl. Now run the baseline with run_experiment.`, - }], - details: { state: { ...state } }, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("init_experiment ")); - text += theme.fg("accent", args.name ?? ""); - return new Text(text, 0, 0); - }, - - renderResult(result, _options, theme) { - const t = result.content[0]; - return new Text(t?.type === "text" ? t.text : "", 0, 0); - }, - }); - - // ----------------------------------------------------------------------- - // run_experiment tool - // ----------------------------------------------------------------------- - - pi.registerTool({ - name: "run_experiment", - label: "Run Experiment", - description: - "Run a shell command as an experiment. Times wall-clock duration, captures output, detects pass/fail via exit code. Use for any autoresearch experiment.", - promptSnippet: - "Run a timed experiment command (captures duration, output, exit code)", - promptGuidelines: [ - "Use run_experiment instead of bash when running experiment commands — it handles timing and output capture automatically.", - "After run_experiment, always call log_experiment to record the result.", - ], - parameters: RunParams, - - async execute(_toolCallId, params, signal, onUpdate, ctx) { - const timeout = (params.timeout_seconds ?? 600) * 1000; - - runningExperiment = { startedAt: Date.now(), command: params.command }; - if (overlayTui) overlayTui.requestRender(); - - onUpdate?.({ - content: [{ type: "text", text: `Running: ${params.command}` }], - details: { phase: "running" }, - }); - - const t0 = Date.now(); - - let result; - try { - result = await pi.exec("bash", ["-c", params.command], { - signal, - timeout, - cwd: ctx.cwd, - }); - } finally { - runningExperiment = null; - if (overlayTui) overlayTui.requestRender(); - } - - const durationSeconds = (Date.now() - t0) / 1000; - const output = (result.stdout + "\n" + result.stderr).trim(); - const passed = result.code === 0 && !result.killed; - - const details: RunDetails = { - command: params.command, - exitCode: result.code, - durationSeconds, - passed, - crashed: !passed, - timedOut: !!result.killed, - tailOutput: output.split("\n").slice(-80).join("\n"), - }; - - // Build LLM response - let text = ""; - if (details.timedOut) { - text += `⏰ TIMEOUT after ${durationSeconds.toFixed(1)}s\n`; - } else if (!passed) { - text += `💥 FAILED (exit code ${result.code}) in ${durationSeconds.toFixed(1)}s\n`; - } else { - text += `✅ PASSED in ${durationSeconds.toFixed(1)}s\n`; - } - - if (state.bestMetric !== null) { - text += `📊 Current best ${state.metricName}: ${formatNum(state.bestMetric, state.metricUnit)}\n`; - } - - text += `\nLast 80 lines of output:\n${details.tailOutput}`; - - const truncation = truncateTail(text, { - maxLines: 150, - maxBytes: 40000, - }); - - return { - content: [{ type: "text", text: truncation.content }], - details, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("run_experiment ")); - text += theme.fg("muted", args.command); - if (args.timeout_seconds) { - text += theme.fg("dim", ` (timeout: ${args.timeout_seconds}s)`); - } - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded, isPartial }, theme) { - if (isPartial) { - return new Text( - theme.fg("warning", "⏳ Running experiment..."), - 0, - 0 - ); - } - - const d = result.details as RunDetails | undefined; - if (!d) { - const t = result.content[0]; - return new Text(t?.type === "text" ? t.text : "", 0, 0); - } - - if (d.timedOut) { - let text = theme.fg( - "error", - `⏰ TIMEOUT ${d.durationSeconds.toFixed(1)}s` - ); - if (expanded) text += "\n" + theme.fg("dim", d.tailOutput.slice(-500)); - return new Text(text, 0, 0); - } - - if (d.crashed) { - let text = theme.fg( - "error", - `💥 FAIL exit=${d.exitCode} ${d.durationSeconds.toFixed(1)}s` - ); - if (expanded) text += "\n" + theme.fg("dim", d.tailOutput.slice(-500)); - return new Text(text, 0, 0); - } - - let text = - theme.fg("success", "✅ ") + - theme.fg("accent", `${d.durationSeconds.toFixed(1)}s`); - - if (expanded) { - text += "\n" + theme.fg("dim", d.tailOutput.slice(-1000)); - } - - return new Text(text, 0, 0); - }, - }); - - // ----------------------------------------------------------------------- - // log_experiment tool - // ----------------------------------------------------------------------- - - pi.registerTool({ - name: "log_experiment", - label: "Log Experiment", - description: - "Record an experiment result. Tracks metrics, updates the status widget and dashboard. Call after every run_experiment.", - promptSnippet: - "Log experiment result (commit, metric, status, description)", - promptGuidelines: [ - "Always call log_experiment after run_experiment to record the result.", - "After run_experiment, always call log_experiment to record the result.", - "log_experiment automatically runs git add -A && git commit with the description and a Result trailer. Do NOT commit manually before calling log_experiment.", - "Use status 'keep' if the PRIMARY metric improved. 'discard' if worse or unchanged. 'crash' if it failed. Secondary metrics are for monitoring — they almost never affect keep/discard. Only discard a primary improvement if a secondary metric degraded catastrophically, and explain why in the description.", - "If you discover complex but promising optimizations you won't pursue immediately, append them as bullet points to autoresearch.ideas.md. Don't let good ideas get lost.", - ], - parameters: LogParams, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - const secondaryMetrics = params.metrics ?? {}; - - // Validate secondary metrics consistency (after first experiment establishes them) - if (state.secondaryMetrics.length > 0) { - const knownNames = new Set(state.secondaryMetrics.map((m) => m.name)); - const providedNames = new Set(Object.keys(secondaryMetrics)); - - // Check for missing metrics - const missing = [...knownNames].filter((n) => !providedNames.has(n)); - if (missing.length > 0) { - return { - content: [{ - type: "text", - text: `❌ Missing secondary metrics: ${missing.join(", ")}\n\nYou must provide all previously tracked metrics. Expected: ${[...knownNames].join(", ")}\nGot: ${[...providedNames].join(", ") || "(none)"}\n\nFix: include ${missing.map((m) => `"${m}": `).join(", ")} in the metrics parameter.`, - }], - details: {}, - }; - } - - // Check for new metrics not yet tracked - const newMetrics = [...providedNames].filter((n) => !knownNames.has(n)); - if (newMetrics.length > 0 && !params.force) { - return { - content: [{ - type: "text", - text: `❌ New secondary metric${newMetrics.length > 1 ? "s" : ""} not previously tracked: ${newMetrics.join(", ")}\n\nExisting metrics: ${[...knownNames].join(", ")}\n\nIf this metric has proven very valuable to watch, call log_experiment again with force: true to add it. Otherwise, remove it from the metrics parameter.`, - }], - details: {}, - }; - } - } - - const experiment: ExperimentResult = { - commit: params.commit.slice(0, 7), - metric: params.metric, - metrics: secondaryMetrics, - status: params.status, - description: params.description, - timestamp: Date.now(), - segment: state.currentSegment, - }; - - state.results.push(experiment); - - // Register any new secondary metric names - for (const name of Object.keys(secondaryMetrics)) { - if (!state.secondaryMetrics.find((m) => m.name === name)) { - let unit = ""; - if (name.endsWith("_µs") || name.includes("µs")) unit = "µs"; - else if (name.endsWith("_ms") || name.includes("ms")) unit = "ms"; - else if (name.endsWith("_s") || name.includes("sec")) unit = "s"; - state.secondaryMetrics.push({ name, unit }); - } - } - - // Baseline = first run in current segment - state.bestMetric = findBaselineMetric(state.results, state.currentSegment); - - // Build response text - const curCount = currentResults(state.results, state.currentSegment).length; - let text = `Logged #${state.results.length}: ${experiment.status} — ${experiment.description}`; - - if (state.bestMetric !== null) { - text += `\nBaseline ${state.metricName}: ${formatNum(state.bestMetric, state.metricUnit)}`; - if (curCount > 1 && params.status === "keep" && params.metric > 0) { - const delta = params.metric - state.bestMetric; - const pct = ((delta / state.bestMetric) * 100).toFixed(1); - const sign = delta > 0 ? "+" : ""; - text += ` | this: ${formatNum(params.metric, state.metricUnit)} (${sign}${pct}%)`; - } - } - - // Show secondary metrics - if (Object.keys(secondaryMetrics).length > 0) { - const baselines = findBaselineSecondary(state.results, state.currentSegment, state.secondaryMetrics); - const parts: string[] = []; - for (const [name, value] of Object.entries(secondaryMetrics)) { - const def = state.secondaryMetrics.find((m) => m.name === name); - const unit = def?.unit ?? ""; - let part = `${name}: ${formatNum(value, unit)}`; - const bv = baselines[name]; - if (bv !== undefined && state.results.length > 1 && bv !== 0) { - const d = value - bv; - const p = ((d / bv) * 100).toFixed(1); - const s = d > 0 ? "+" : ""; - part += ` (${s}${p}%)`; - } - parts.push(part); - } - text += `\nSecondary: ${parts.join(" ")}`; - } - - text += `\n(${state.results.length} experiments total)`; - - // Auto-commit only on keep — discards/crashes get reverted anyway - if (params.status === "keep") { - try { - const resultData: Record = { - status: params.status, - [state.metricName || "metric"]: params.metric, - ...secondaryMetrics, - }; - const trailerJson = JSON.stringify(resultData); - const commitMsg = `${params.description}\n\nResult: ${trailerJson}`; - - const gitResult = await pi.exec("bash", ["-c", - `git add -A && git diff --cached --quiet && echo "NOTHING_TO_COMMIT" || git commit -m ${JSON.stringify(commitMsg)}` - ], { cwd: ctx.cwd, timeout: 10000 }); - - const gitOutput = (gitResult.stdout + gitResult.stderr).trim(); - if (gitOutput.includes("NOTHING_TO_COMMIT")) { - text += `\n📝 Git: nothing to commit (working tree clean)`; - } else if (gitResult.code === 0) { - const firstLine = gitOutput.split("\n")[0] || ""; - text += `\n📝 Git: committed — ${firstLine}`; - - // Update experiment record with the actual new commit hash - try { - const shaResult = await pi.exec("git", ["rev-parse", "--short=7", "HEAD"], { cwd: ctx.cwd, timeout: 5000 }); - const newSha = (shaResult.stdout || "").trim(); - if (newSha && newSha.length >= 7) { - experiment.commit = newSha; - } - } catch { - // Keep the original commit hash if rev-parse fails - } - } else { - text += `\n⚠️ Git commit failed (exit ${gitResult.code}): ${gitOutput.slice(0, 200)}`; - } - } catch (e) { - text += `\n⚠️ Git commit error: ${e instanceof Error ? e.message : String(e)}`; - } - } else { - text += `\n📝 Git: skipped commit (${params.status}) — revert with git checkout -- .`; - } - - // Persist to autoresearch.jsonl AFTER git commit (so commit hash is correct) - try { - const jsonlPath = path.join(ctx.cwd, "autoresearch.jsonl"); - fs.appendFileSync(jsonlPath, JSON.stringify({ - run: state.results.length, - ...experiment, - }) + "\n"); - } catch { - // Don't fail if write fails - } - - // Clear running experiment (log_experiment consumes the run) - runningExperiment = null; - - updateWidget(ctx); - - // Refresh fullscreen overlay if open - if (overlayTui) overlayTui.requestRender(); - - // Flush queued user steers — deliver as a single steer message - if (messageQueue.length > 0) { - const queued = messageQueue.splice(0); - const steerText = queued.length === 1 - ? `User steer (queued): ${queued[0]}` - : `User steers (queued):\n${queued.map((m) => `- ${m}`).join("\n")}`; - text += `\n\n📬 ${queued.length} queued steer${queued.length > 1 ? "s" : ""} from user — see next message.`; - pi.sendUserMessage(steerText, { deliverAs: "followUp" }); - } - - return { - content: [{ type: "text", text }], - details: { experiment, state: { ...state } } as LogDetails, - }; - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("log_experiment ")); - const color = - args.status === "keep" - ? "success" - : args.status === "crash" - ? "error" - : "warning"; - text += theme.fg(color, args.status); - text += " " + theme.fg("dim", args.description); - return new Text(text, 0, 0); - }, - - renderResult(result, _options, theme) { - const d = result.details as LogDetails | undefined; - if (!d) { - const t = result.content[0]; - return new Text(t?.type === "text" ? t.text : "", 0, 0); - } - - const { experiment: exp, state: s } = d; - const color = - exp.status === "keep" - ? "success" - : exp.status === "crash" - ? "error" - : "warning"; - const icon = - exp.status === "keep" ? "✓" : exp.status === "crash" ? "✗" : "–"; - - let text = - theme.fg(color, `${icon} `) + - theme.fg("accent", `#${s.results.length}`); - - - - text += " " + theme.fg("muted", exp.description); - - if (s.bestMetric !== null) { - text += - theme.fg("dim", " │ ") + - theme.fg("warning", theme.bold(`★ ${formatNum(s.bestMetric, s.metricUnit)}`)); - } - - // Show secondary metrics inline - if (Object.keys(exp.metrics).length > 0) { - const parts: string[] = []; - for (const [name, value] of Object.entries(exp.metrics)) { - const def = s.secondaryMetrics.find((m) => m.name === name); - parts.push(`${name}=${formatNum(value, def?.unit ?? "")}`); - } - text += theme.fg("dim", ` ${parts.join(" ")}`); - } - - return new Text(text, 0, 0); - }, - }); - - // ----------------------------------------------------------------------- - // Ctrl+R — toggle dashboard expand/collapse - // ----------------------------------------------------------------------- - - pi.registerShortcut("ctrl+x", { - description: "Toggle autoresearch dashboard", - handler: async (ctx) => { - if (state.results.length === 0) { - if (!autoresearchMode && !fs.existsSync(path.join(ctx.cwd, "autoresearch.md"))) { - ctx.ui.notify("No experiments yet — run /autoresearch to get started", "info"); - } else { - ctx.ui.notify("No experiments yet", "info"); - } - return; - } - dashboardExpanded = !dashboardExpanded; - updateWidget(ctx); - }, - }); - - // ----------------------------------------------------------------------- - // Ctrl+Shift+X — fullscreen scrollable dashboard overlay - // ----------------------------------------------------------------------- - - pi.registerShortcut("ctrl+shift+x", { - description: "Fullscreen autoresearch dashboard", - handler: async (ctx) => { - if (state.results.length === 0) { - ctx.ui.notify("No experiments yet", "info"); - return; - } - - await ctx.ui.custom( - (tui, theme, _kb, done) => { - let scrollOffset = 0; - // Store tui ref so run_experiment can trigger re-renders - overlayTui = tui; - - // Start spinner interval for elapsed time animation - spinnerInterval = setInterval(() => { - spinnerFrame = (spinnerFrame + 1) % SPINNER.length; - if (runningExperiment) tui.requestRender(); - }, 80); - - function formatElapsed(ms: number): string { - const s = Math.floor(ms / 1000); - const m = Math.floor(s / 60); - const sec = s % 60; - return m > 0 ? `${m}m${String(sec).padStart(2, "0")}s` : `${sec}s`; - } - - return { - render(width: number): string[] { - const termH = process.stdout.rows || 40; - // Content gets the full width — no box borders - const content = renderDashboardLines(state, width, theme, 0); - - // Add running experiment as next row in the list - if (runningExperiment) { - const elapsed = formatElapsed(Date.now() - runningExperiment.startedAt); - const frame = SPINNER[spinnerFrame % SPINNER.length]; - const nextIdx = state.results.length + 1; - content.push( - truncateToWidth( - ` ${theme.fg("dim", String(nextIdx).padEnd(3))}` + - theme.fg("warning", `${frame} running… ${elapsed}`), - width - ) - ); - } - - const totalRows = content.length; - const viewportRows = Math.max(4, termH - 4); // leave room for header/footer - - // Clamp scroll - const maxScroll = Math.max(0, totalRows - viewportRows); - if (scrollOffset > maxScroll) scrollOffset = maxScroll; - if (scrollOffset < 0) scrollOffset = 0; - - const out: string[] = []; - - // Header line - const titlePrefix = "🔬 autoresearch"; - const nameStr = state.name ? `: ${state.name}` : ""; - const maxTitleLen = width - 6; - let title = titlePrefix + nameStr; - if (title.length > maxTitleLen) { - title = title.slice(0, maxTitleLen - 1) + "…"; - } - const fillLen = Math.max(0, width - 3 - 1 - title.length - 1); - out.push( - truncateToWidth( - theme.fg("borderMuted", "───") + - theme.fg("accent", " " + title + " ") + - theme.fg("borderMuted", "─".repeat(fillLen)), - width - ) - ); - - // Content rows - const visible = content.slice(scrollOffset, scrollOffset + viewportRows); - for (const line of visible) { - out.push(truncateToWidth(line, width)); - } - // Fill remaining viewport - for (let i = visible.length; i < viewportRows; i++) { - out.push(""); - } - - // Footer line - const scrollInfo = totalRows > viewportRows - ? ` ${scrollOffset + 1}-${Math.min(scrollOffset + viewportRows, totalRows)}/${totalRows}` - : ""; - const helpText = ` ↑↓/j/k scroll • esc close${scrollInfo} `; - const footFill = Math.max(0, width - helpText.length); - out.push( - truncateToWidth( - theme.fg("borderMuted", "─".repeat(footFill)) + - theme.fg("dim", helpText), - width - ) - ); - - return out; - }, - - handleInput(data: string): void { - const termH = process.stdout.rows || 40; - const viewportRows = Math.max(4, termH - 4); - const totalRows = state.results.length + (runningExperiment ? 1 : 0) + 15; // rough estimate - const maxScroll = Math.max(0, totalRows - viewportRows); - - if (matchesKey(data, "escape") || data === "q") { - done(undefined); - return; - } - if (matchesKey(data, "up") || data === "k") { - scrollOffset = Math.max(0, scrollOffset - 1); - } else if (matchesKey(data, "down") || data === "j") { - scrollOffset = Math.min(maxScroll, scrollOffset + 1); - } else if (matchesKey(data, "pageup") || data === "u") { - scrollOffset = Math.max(0, scrollOffset - viewportRows); - } else if (matchesKey(data, "pagedown") || data === "d") { - scrollOffset = Math.min(maxScroll, scrollOffset + viewportRows); - } else if (data === "g") { - scrollOffset = 0; - } else if (data === "G") { - scrollOffset = maxScroll; - } - tui.requestRender(); - }, - - invalidate(): void {}, - - dispose(): void { - overlayTui = null; - if (spinnerInterval) { - clearInterval(spinnerInterval); - spinnerInterval = null; - } - }, - }; - }, - { - overlay: true, - overlayOptions: { - width: "95%", - maxHeight: "90%", - anchor: "center" as const, - }, - } - ); - }, - }); - - // ----------------------------------------------------------------------- - // /autoresearch command — enter autoresearch mode - // ----------------------------------------------------------------------- - - pi.registerCommand("autoresearch", { - description: "Toggle autoresearch mode on/off, or start a new experiment", - handler: async (args, ctx) => { - if (args === "off") { - autoresearchMode = false; - ctx.ui.notify("Autoresearch mode OFF", "info"); - return; - } - - autoresearchMode = true; - - const mdPath = path.join(ctx.cwd, "autoresearch.md"); - const hasRules = fs.existsSync(mdPath); - - if (hasRules) { - ctx.ui.notify("Autoresearch mode ON — rules loaded from autoresearch.md", "success"); - if (args) { - // User gave specific instructions, pass them along - pi.sendUserMessage(`Autoresearch mode active. ${args}`); - } else { - pi.sendUserMessage( - "Autoresearch mode active. Read autoresearch.md and autoresearch.sh, then resume the experiment loop." - ); - } - } else { - ctx.ui.notify("Autoresearch mode ON — no autoresearch.md found, setting up", "info"); - pi.sendUserMessage( - args - ? `Start autoresearch: ${args}` - : "Start autoresearch. No autoresearch.md found — gather context and set up the experiment (create autoresearch.md and autoresearch.sh)." - ); - } - }, - }); -} diff --git a/openclaw.plugin.json b/openclaw.plugin.json new file mode 100644 index 0000000..4e2f39b --- /dev/null +++ b/openclaw.plugin.json @@ -0,0 +1,14 @@ +{ + "id": "openclaw-autoresearch", + "name": "Autoresearch", + "description": "Faithful OpenClaw port of pi-autoresearch.", + "skills": [ + "./skills" + ], + "version": "1.0.0", + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/package-lock.json b/package-lock.json index 80bbc3e..c459a70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2406 +1,1030 @@ { - "name": "pi-autoresearch", + "name": "openclaw-autoresearch", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "pi-autoresearch", + "name": "openclaw-autoresearch", "version": "1.0.0", "license": "MIT", - "peerDependencies": { - "@mariozechner/pi-ai": "*", - "@mariozechner/pi-coding-agent": "*", - "@mariozechner/pi-tui": "*", - "@sinclair/typebox": "*" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", - "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", - "license": "MIT", - "peer": true, "dependencies": { - "json-schema-to-ts": "^3.1.1" + "@sinclair/typebox": "0.34.48" }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } + "devDependencies": { + "@types/node": "24.5.2", + "typescript": "5.9.2", + "vitest": "3.2.4" } }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1006.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1006.0.tgz", - "integrity": "sha512-xoReIImKWGEgI5+44ZqADIfjSQTx367d3wkH1kX8ZZNe70mUQxXDzLp1iWBk4FLjQyTnv0J0vMIvhSHVfvFxXA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/credential-provider-node": "^3.972.19", - "@aws-sdk/eventstream-handler-node": "^3.972.10", - "@aws-sdk/middleware-eventstream": "^3.972.7", - "@aws-sdk/middleware-host-header": "^3.972.7", - "@aws-sdk/middleware-logger": "^3.972.7", - "@aws-sdk/middleware-recursion-detection": "^3.972.7", - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/middleware-websocket": "^3.972.12", - "@aws-sdk/region-config-resolver": "^3.972.7", - "@aws-sdk/token-providers": "3.1006.0", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/core": "^3.23.9", - "@smithy/eventstream-serde-browser": "^4.2.11", - "@smithy/eventstream-serde-config-resolver": "^4.3.11", - "@smithy/eventstream-serde-node": "^4.2.11", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/hash-node": "^4.2.11", - "@smithy/invalid-dependency": "^4.2.11", - "@smithy/middleware-content-length": "^4.2.11", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-retry": "^4.4.40", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.39", - "@smithy/util-defaults-mode-node": "^4.2.42", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", - "@smithy/util-stream": "^4.5.17", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.973.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.19.tgz", - "integrity": "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/xml-builder": "^3.972.10", - "@smithy/core": "^3.23.9", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/signature-v4": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.17.tgz", - "integrity": "sha512-MBAMW6YELzE1SdkOniqr51mrjapQUv8JXSGxtwRjQV0mwVDutVsn22OPAUt4RcLRvdiHQmNBDEFP9iTeSVCOlA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.19.tgz", - "integrity": "sha512-9EJROO8LXll5a7eUFqu48k6BChrtokbmgeMWmsH7lBb6lVbtjslUYz/ShLi+SHkYzTomiGBhmzTW7y+H4BxsnA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.17", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.18.tgz", - "integrity": "sha512-vthIAXJISZnj2576HeyLBj4WTeX+I7PwWeRkbOa0mVX39K13SCGxCgOFuKj2ytm9qTlLOmXe4cdEnroteFtJfw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/credential-provider-env": "^3.972.17", - "@aws-sdk/credential-provider-http": "^3.972.19", - "@aws-sdk/credential-provider-login": "^3.972.18", - "@aws-sdk/credential-provider-process": "^3.972.17", - "@aws-sdk/credential-provider-sso": "^3.972.18", - "@aws-sdk/credential-provider-web-identity": "^3.972.18", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/credential-provider-imds": "^4.2.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.18.tgz", - "integrity": "sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.19.tgz", - "integrity": "sha512-yDWQ9dFTr+IMxwanFe7+tbN5++q8psZBjlUwOiCXn1EzANoBgtqBwcpYcHaMGtn0Wlfj4NuXdf2JaEx1lz5RaQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.17", - "@aws-sdk/credential-provider-http": "^3.972.19", - "@aws-sdk/credential-provider-ini": "^3.972.18", - "@aws-sdk/credential-provider-process": "^3.972.17", - "@aws-sdk/credential-provider-sso": "^3.972.18", - "@aws-sdk/credential-provider-web-identity": "^3.972.18", - "@aws-sdk/types": "^3.973.5", - "@smithy/credential-provider-imds": "^4.2.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.17.tgz", - "integrity": "sha512-c8G8wT1axpJDgaP3xzcy+q8Y1fTi9A2eIQJvyhQ9xuXrUZhlCfXbC0vM9bM1CUXiZppFQ1p7g0tuUMvil/gCPg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.18.tgz", - "integrity": "sha512-YHYEfj5S2aqInRt5ub8nDOX8vAxgMvd84wm2Y3WVNfFa/53vOv9T7WOAqXI25qjj3uEcV46xxfqdDQk04h5XQA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/token-providers": "3.1005.0", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1005.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1005.0.tgz", - "integrity": "sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.18.tgz", - "integrity": "sha512-OqlEQpJ+J3T5B96qtC1zLLwkBloechP+fezKbCH0sbd2cCc0Ra55XpxWpk/hRj69xAOYtHvoC4orx6eTa4zU7g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.10.tgz", - "integrity": "sha512-g2Z9s6Y4iNh0wICaEqutgYgt/Pmhv5Ev9G3eKGFe2w9VuZDhc76vYdop6I5OocmpHV79d4TuLG+JWg5rQIVDVA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/eventstream-codec": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.7.tgz", - "integrity": "sha512-VWndapHYCfwLgPpCb/xwlMKG4imhFzKJzZcKOEioGn7OHY+6gdr0K7oqy1HZgbLa3ACznZ9fku+DzmAi8fUC0g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.7.tgz", - "integrity": "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.7.tgz", - "integrity": "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.7.tgz", - "integrity": "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.20", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.20.tgz", - "integrity": "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@smithy/core": "^3.23.9", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-retry": "^4.2.11", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.12.tgz", - "integrity": "sha512-iyPP6FVDKe/5wy5ojC0akpDFG1vX3FeCUU47JuwN8xfvT66xlEI8qUJZPtN55TJVFzzWZJpWL78eqUE31md08Q==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-format-url": "^3.972.7", - "@smithy/eventstream-codec": "^4.2.11", - "@smithy/eventstream-serde-browser": "^4.2.11", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/protocol-http": "^5.3.11", - "@smithy/signature-v4": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.8.tgz", - "integrity": "sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/middleware-host-header": "^3.972.7", - "@aws-sdk/middleware-logger": "^3.972.7", - "@aws-sdk/middleware-recursion-detection": "^3.972.7", - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/region-config-resolver": "^3.972.7", - "@aws-sdk/types": "^3.973.5", - "@aws-sdk/util-endpoints": "^3.996.4", - "@aws-sdk/util-user-agent-browser": "^3.972.7", - "@aws-sdk/util-user-agent-node": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/core": "^3.23.9", - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/hash-node": "^4.2.11", - "@smithy/invalid-dependency": "^4.2.11", - "@smithy/middleware-content-length": "^4.2.11", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-retry": "^4.4.40", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/protocol-http": "^5.3.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.39", - "@smithy/util-defaults-mode-node": "^4.2.42", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.7.tgz", - "integrity": "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/config-resolver": "^4.4.10", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1006.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1006.0.tgz", - "integrity": "sha512-eCBaQI1w5PcliOdh8Y0YONOim2zNSTEK4E7gXYC4vIqiT/lzVODIFxmpc8oOBLPSANzcr9daIPPtjQ2C75dLFg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.973.19", - "@aws-sdk/nested-clients": "^3.996.8", - "@aws-sdk/types": "^3.973.5", - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.5.tgz", - "integrity": "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.4.tgz", - "integrity": "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-endpoints": "^3.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-format-url": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.7.tgz", - "integrity": "sha512-V+PbnWfUl93GuFwsOHsAq7hY/fnm9kElRqR8IexIJr5Rvif9e614X5sGSyz3mVSf1YAZ+VTy63W1/pGdA55zyA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/querystring-builder": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.7.tgz", - "integrity": "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.5", - "@smithy/types": "^4.13.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.5.tgz", - "integrity": "sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.20", - "@aws-sdk/types": "^3.973.5", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.10.tgz", - "integrity": "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "fast-xml-parser": "5.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@google/genai": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.44.0.tgz", - "integrity": "sha512-kRt9ZtuXmz+tLlcNntN/VV4LRdpl6ZOu5B1KbfNgfR65db15O6sUQcwnwLka8sT/V6qysD93fWrgJHF2L7dA9A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "peer": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT", - "peer": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "peer": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@mariozechner/clipboard": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.2.tgz", - "integrity": "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", "optional": true, - "peer": true, + "os": [ + "aix" + ], "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.2", - "@mariozechner/clipboard-darwin-universal": "0.3.2", - "@mariozechner/clipboard-darwin-x64": "0.3.2", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", - "@mariozechner/clipboard-linux-x64-musl": "0.3.2", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.2.tgz", - "integrity": "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "android" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.2.tgz", - "integrity": "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg==", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.2.tgz", - "integrity": "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "cpu": [ "x64" ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.2.tgz", - "integrity": "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "cpu": [ "arm64" ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.2.tgz", - "integrity": "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.2.tgz", - "integrity": "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.2.tgz", - "integrity": "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.2.tgz", - "integrity": "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.2.tgz", - "integrity": "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "netbsd" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.2.tgz", - "integrity": "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "cpu": [ "x64" ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/@mariozechner/jiti": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz", - "integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==", - "license": "MIT", - "peer": true, - "dependencies": { - "std-env": "^3.10.0", - "yoctocolors": "^2.1.2" - }, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@mariozechner/pi-agent-core": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.57.1.tgz", - "integrity": "sha512-WXsBbkNWOObFGHkhixaT8GXJpHDd3+fn8QntYF+4R8Sa9WB90ENXWidO6b7vcKX+JX0jjO5dIsQxmzosARJKlg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@mariozechner/pi-ai": "^0.57.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-ai": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.57.1.tgz", - "integrity": "sha512-Bd/J4a3YpdzJVyHLih0vDSdB0QPL4ti0XsAwtHOK/8eVhB0fHM1CpcgIrcBFJ23TMcKXMi0qamz18ERfp8tmgg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@anthropic-ai/sdk": "^0.73.0", - "@aws-sdk/client-bedrock-runtime": "^3.983.0", - "@google/genai": "^1.40.0", - "@mistralai/mistralai": "1.14.1", - "@sinclair/typebox": "^0.34.41", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "chalk": "^5.6.2", - "openai": "6.26.0", - "partial-json": "^0.1.7", - "proxy-agent": "^6.5.0", - "undici": "^7.19.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@mariozechner/pi-coding-agent": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.57.1.tgz", - "integrity": "sha512-u5MQEduj68rwVIsRsqrWkJYiJCyPph/a6bMoJAQKo1sb+Pc17Y/ojwa+wGssnUMjEB38AQKofWTVe8NFEpSWNw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.57.1", - "@mariozechner/pi-ai": "^0.57.1", - "@mariozechner/pi-tui": "^0.57.1", - "@silvia-odwyer/photon-node": "^0.3.4", - "chalk": "^5.5.0", - "cli-highlight": "^2.1.11", - "diff": "^8.0.2", - "extract-zip": "^2.0.1", - "file-type": "^21.1.1", - "glob": "^13.0.1", - "hosted-git-info": "^9.0.2", - "ignore": "^7.0.5", - "marked": "^15.0.12", - "minimatch": "^10.2.3", - "proper-lockfile": "^4.1.2", - "strip-ansi": "^7.1.0", - "undici": "^7.19.1", - "yaml": "^2.8.2" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=20.6.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "^0.3.2" - } - }, - "node_modules/@mariozechner/pi-tui": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.57.1.tgz", - "integrity": "sha512-cjoRghLbeAHV0tTJeHgZXaryUi5zzBZofeZ7uJun1gztnckLLRjoVeaPTujNlc5BIfyKvFqhh1QWCZng/MXlpg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/mime-types": "^2.1.4", - "chalk": "^5.5.0", - "get-east-asian-width": "^1.3.0", - "marked": "^15.0.12", - "mime-types": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - }, - "optionalDependencies": { - "koffi": "^2.9.0" - } - }, - "node_modules/@mistralai/mistralai": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", - "integrity": "sha512-IiLmmZFCCTReQgPAT33r7KQ1nYo5JPdvGkrkZqA8qQ2qB1GHgs5LoP5K2ICyrjnpw2n8oSxMM/VP+liiKcGNlQ==", - "peer": true, - "dependencies": { - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.24.1" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, "license": "MIT", "optional": true, - "peer": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause", - "peer": true + "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/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause", - "peer": true + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause", - "peer": true + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause", - "peer": true + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause", - "peer": true + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "license": "Apache-2.0", - "peer": true + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@sinclair/typebox": { "version": "0.34.48", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "license": "MIT" + }, + "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", - "peer": true - }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.11.tgz", - "integrity": "sha512-Hj4WoYWMJnSpM6/kchsm4bUNTL9XiSyhvoMb2KIq4VJzyDt7JpGHUZHkVNPZVC7YE1tf8tPeVauxpFBKGW4/KQ==", - "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.10.tgz", - "integrity": "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.3.2", - "@smithy/util-middleware": "^4.2.11", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "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/@smithy/core": { - "version": "3.23.9", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.9.tgz", - "integrity": "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/middleware-serde": "^4.2.12", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-stream": "^4.5.17", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.11.tgz", - "integrity": "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.11.tgz", - "integrity": "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.13.0", - "@smithy/util-hex-encoding": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.11.tgz", - "integrity": "sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.11.tgz", - "integrity": "sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.11.tgz", - "integrity": "sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.11.tgz", - "integrity": "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/eventstream-codec": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.13.tgz", - "integrity": "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.3.11", - "@smithy/querystring-builder": "^4.2.11", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.11.tgz", - "integrity": "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.11.tgz", - "integrity": "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.11.tgz", - "integrity": "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.23", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.23.tgz", - "integrity": "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.23.9", - "@smithy/middleware-serde": "^4.2.12", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.11", - "@smithy/util-middleware": "^4.2.11", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.40", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.40.tgz", - "integrity": "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/service-error-classification": "^4.2.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-retry": "^4.2.11", - "@smithy/uuid": "^1.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.12.tgz", - "integrity": "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.11.tgz", - "integrity": "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.11.tgz", - "integrity": "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/property-provider": "^4.2.11", - "@smithy/shared-ini-file-loader": "^4.4.6", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.14", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.14.tgz", - "integrity": "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/abort-controller": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/querystring-builder": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.11.tgz", - "integrity": "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.11.tgz", - "integrity": "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.11.tgz", - "integrity": "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "@smithy/util-uri-escape": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.11.tgz", - "integrity": "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.11.tgz", - "integrity": "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.6.tgz", - "integrity": "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.11.tgz", - "integrity": "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.11", - "@smithy/util-uri-escape": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.3.tgz", - "integrity": "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.23.9", - "@smithy/middleware-endpoint": "^4.4.23", - "@smithy/middleware-stack": "^4.2.11", - "@smithy/protocol-http": "^5.3.11", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.17", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", - "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.11.tgz", - "integrity": "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/querystring-parser": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", - "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", - "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", - "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", - "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.39", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.39.tgz", - "integrity": "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/property-provider": "^4.2.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.42", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.42.tgz", - "integrity": "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/config-resolver": "^4.4.10", - "@smithy/credential-provider-imds": "^4.2.11", - "@smithy/node-config-provider": "^4.3.11", - "@smithy/property-provider": "^4.2.11", - "@smithy/smithy-client": "^4.12.3", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.2.tgz", - "integrity": "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", - "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.11.tgz", - "integrity": "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.11.tgz", - "integrity": "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/service-error-classification": "^4.2.11", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "4.5.17", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.17.tgz", - "integrity": "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.13", - "@smithy/node-http-handler": "^4.4.14", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", - "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/uuid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", - "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "license": "MIT", - "peer": true - }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT", - "peer": true - }, - "node_modules/@types/mime-types": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", - "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", - "license": "MIT", - "peer": true + "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": "25.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", - "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.12.0" } }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@types/node": "*" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://opencollective.com/vitest" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ajv": "^8.0.0" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "ajv": "^8.0.0" + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { - "ajv": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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", - "peer": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT", - "peer": true - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "peer": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/basic-ftp": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", - "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT", - "peer": true - }, - "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "license": "ISC", - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "peer": true - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" + "node": ">= 16" } }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2413,1805 +1037,591 @@ } } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT", - "peer": true - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "peer": true - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "peer": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=6.0" + "node": ">=18" }, "optionalDependencies": { - "source-map": "~0.6.1" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "peer": true, "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "@types/estree": "^1.0.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "peer": true - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/fast-xml-builder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.0.tgz", - "integrity": "sha512-7mtITW/we2/wTUZqMyBOR2F8xP4CRxMiSEcQxPIqdRWdO2L/HZSOlzoNyghmyDwNB8BDxePooV1ZTJpkOUhdRg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "path-expression-matcher": "^1.1.2" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "peer": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/file-type": { - "version": "21.3.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.1.tgz", - "integrity": "sha512-SrzXX46I/zsRDjTb82eucsGg0ODq2NpGDp4HcsFKApPy8P8vACjpJRDoGGMfEzhFC0ry61ajd7f72J3603anBA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "peer": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, "license": "Apache-2.0", - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" - }, "engines": { - "node": ">=18" + "node": ">=12.0.0" } }, - "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "peer": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=18" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "peer": true, - "dependencies": { - "pump": "^3.0.0" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "peer": true, - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/google-auth-library": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz", - "integrity": "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "7.1.3", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "peer": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", - "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", - "license": "ISC", - "peer": true, - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "peerDependenciesMeta": { + "picomatch": { + "optional": true } - ], - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" } }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "peer": true - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "peer": true, - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/koffi": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.1.tgz", - "integrity": "sha512-mnc0C0crx/xMSljb5s9QbnLrlFHprioFO1hkXyuSuO/QtbpLDa0l/uM21944UfQunMKmp3/r789DTDxVyyH6aA==", + "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, "license": "MIT", "optional": true, - "peer": true, - "funding": { - "url": "https://liberapay.com/Koromix" - } - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "peer": true, + "os": [ + "darwin" + ], "engines": { - "node": "20 || >=22" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", - "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" }, - "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": ">=16 || 14 >=14.17" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "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==", - "license": "MIT", - "peer": true + "dev": true, + "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "peer": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "license": "ISC", - "peer": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "license": "Apache-2.0", - "peer": true, "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, "engines": { - "node": ">= 14" + "node": ">= 14.16" } }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0", - "peer": true - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "license": "MIT", - "peer": true - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "license": "MIT", - "peer": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "license": "MIT", - "peer": true - }, - "node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "license": "MIT", - "peer": true - }, - "node_modules/path-expression-matcher": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.2.tgz", - "integrity": "sha512-LXWqJmcpp2BKOEmgt4CyuESFmBfPuhJlAHKJsFzuJU6CxErWk75BrO+Ni77M9OxHN6dCYKM4vj+21Z6cOL96YQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT", - "peer": true - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "peer": true, "engines": { "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "peer": true - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "peer": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "peer": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC", - "peer": true - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "peer": true + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } }, - "node_modules/shebang-command": { + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "peer": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "peer": true - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "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", - "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } }, + "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/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "license": "MIT", - "peer": true + "dev": true, + "license": "MIT" }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "peer": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" + "js-tokens": "^9.0.1" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "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": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "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", - "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@tokenizer/token": "^0.3.0" - }, - "engines": { - "node": ">=18" + "node": ">=12.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "peer": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "license": "MIT", - "peer": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", - "license": "MIT", - "peer": true, - "dependencies": { - "@borewit/text-codec": "^0.2.1", - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/ts-algebra": { + "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, "license": "MIT", - "peer": true - }, - "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==", - "license": "0BSD", - "peer": true - }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "license": "MIT", - "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.0" } }, - "node_modules/undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=20.18.1" + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT", - "peer": true + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "peer": true, "dependencies": { - "isexe": "^2.0.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { - "node-which": "bin/node-which" + "vite": "bin/vite.js" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "url": "https://github.com/vitejs/vite?sponsor=1" }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "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==", - "license": "ISC", - "peer": true - }, - "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { - "bufferutil": { + "@types/node": { "optional": true }, - "utf-8-validate": { + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "peer": true, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, "bin": { - "yaml": "bin.mjs" + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">= 14.6" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { - "url": "https://github.com/sponsors/eemeli" + "url": "https://opencollective.com/vitest" } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", - "license": "ISC", - "peer": true, + "url": "https://opencollective.com/vitest" + }, "peerDependencies": { - "zod": "^3.25 || ^4" + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "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", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" } } } diff --git a/package.json b/package.json index b38488d..50a0a72 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,29 @@ { - "name": "pi-autoresearch", + "name": "openclaw-autoresearch", "version": "1.0.0", - "description": "Autonomous experiment loop for pi — run, measure, keep or discard. Inspired by karpathy/autoresearch.", - "keywords": ["pi-package"], - "license": "MIT", - "pi": { - "extensions": ["./extensions"], - "skills": ["./skills"] + "description": "Faithful OpenClaw port of pi-autoresearch.", + "type": "module", + "scripts": { + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", + "validate": "npm run typecheck && npm run test" }, - "peerDependencies": { - "@mariozechner/pi-ai": "*", - "@mariozechner/pi-coding-agent": "*", - "@mariozechner/pi-tui": "*", - "@sinclair/typebox": "*" + "keywords": [ + "openclaw-plugin", + "autoresearch" + ], + "license": "MIT", + "devDependencies": { + "@types/node": "24.5.2", + "typescript": "5.9.2", + "vitest": "3.2.4" + }, + "dependencies": { + "@sinclair/typebox": "0.34.48" + }, + "openclaw": { + "extensions": [ + "./extensions/openclaw-autoresearch/index.ts" + ] } } diff --git a/pi-autoresearch.png b/pi-autoresearch.png deleted file mode 100644 index 2cdbdbe..0000000 Binary files a/pi-autoresearch.png and /dev/null differ diff --git a/skills/autoresearch-create/SKILL.md b/skills/autoresearch-create/SKILL.md index d865f22..a22c028 100644 --- a/skills/autoresearch-create/SKILL.md +++ b/skills/autoresearch-create/SKILL.md @@ -11,7 +11,7 @@ Autonomous experiment loop: try ideas, keep what works, discard what doesn't, ne - **`init_experiment`** — configure session (name, metric, unit, direction). Call again to re-initialize with a new baseline when the optimization target changes. - **`run_experiment`** — runs command, times it, captures output. -- **`log_experiment`** — records result. `keep` auto-commits. `discard`/`crash` → `git checkout -- .` to revert. Always include secondary `metrics` dict. Dashboard: ctrl+x. +- **`log_experiment`** — records result. `keep` auto-commits. `discard`/`crash` → `git checkout -- .` to revert. Always include secondary `metrics` dict. ## Setup @@ -86,4 +86,6 @@ When there is no `autoresearch.ideas.md` file and the loop ends, the research is ## User Steers -User messages sent while an experiment is running are **automatically queued** and delivered to you after your next `log_experiment` call. Finish your current experiment first — don't stop or ask for confirmation. Incorporate the user's idea in the next experiment. +If the host exposes the OpenClaw message hooks, user steers that arrive while an experiment is running are captured and surfaced after your next `log_experiment` call. OpenClaw may also preserve the same steer in the normal followup backlog, so if the next turn repeats a steer you already saw in `log_experiment`, treat it as the same request rather than a brand new branch of work. + +Finish the current experiment first, then incorporate the user's idea in the next experiment. Don't stop mid-experiment or ask for confirmation unless the user explicitly interrupts the loop. diff --git a/test/command.test.ts b/test/command.test.ts new file mode 100644 index 0000000..13dcef8 --- /dev/null +++ b/test/command.test.ts @@ -0,0 +1,120 @@ +import { describe, expect, it, vi } from "vitest"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { AUTORESEARCH_ROOT_FILES } from "../extensions/openclaw-autoresearch/src/files.js"; +import { + buildAutoresearchCommandText, + registerAutoresearchCommand, +} from "../extensions/openclaw-autoresearch/src/commands/autoresearch.js"; +import { getAutoresearchRuntimeState } from "../extensions/openclaw-autoresearch/src/runtime-state.js"; + +function createTempDir(): string { + return fs.mkdtempSync(path.join(os.tmpdir(), "autoresearch-command-test-")); +} + +describe("buildAutoresearchCommandText", () => { + it("guides setup when no canonical files exist", () => { + const cwd = createTempDir(); + + expect(buildAutoresearchCommandText(cwd, "default")).toContain( + "Recommended OpenClaw entrypoint: `/autoresearch` or `/autoresearch setup `.", + ); + expect(buildAutoresearchCommandText(cwd, "default")).toContain( + "Direct skill fallback: `/skill autoresearch-create`.", + ); + }); + + it("routes active sessions back to the canonical files", () => { + const cwd = createTempDir(); + fs.writeFileSync( + path.join(cwd, AUTORESEARCH_ROOT_FILES.sessionDoc), + "# Autoresearch\n\n## Objective\n\nReduce runtime.\n", + ); + fs.writeFileSync( + path.join(cwd, AUTORESEARCH_ROOT_FILES.resultsLog), + [ + JSON.stringify({ + type: "config", + name: "Runtime optimization", + metricName: "total_ms", + metricUnit: "ms", + bestDirection: "lower", + }), + JSON.stringify({ + run: 1, + commit: "abc1234", + metric: 101, + metrics: {}, + status: "keep", + description: "baseline", + timestamp: 1700000000000, + segment: 0, + }), + ].join("\n"), + ); + + const text = buildAutoresearchCommandText(cwd, "status"); + + expect(text).toContain("Autoresearch session detected at repo root:"); + expect(text).toContain("Read `autoresearch.md` before resuming or changing the loop."); + expect(text).toContain("Mode: active"); + expect(text).toContain("Runtime mode: auto"); + expect(text).toContain("Baseline: 101ms"); + }); + + it("registers a mode-aware /autoresearch command that primes resume instructions", () => { + const cwd = createTempDir(); + fs.writeFileSync( + path.join(cwd, AUTORESEARCH_ROOT_FILES.sessionDoc), + "# Autoresearch\n\n## Objective\n\nReduce runtime.\n", + ); + + const api = { + resolvePath: vi.fn(() => cwd), + registerCommand: vi.fn(), + }; + + registerAutoresearchCommand(api as never); + + const command = api.registerCommand.mock.calls[0]?.[0]; + expect(command?.name).toBe("autoresearch"); + + const result = command.handler({ args: "resume focus parser cache", cwd }); + expect(result.text).toContain("Autoresearch mode ON."); + expect(result.text).toContain("Captured resume instruction: focus parser cache"); + expect(getAutoresearchRuntimeState(cwd)).toMatchObject({ + mode: "on", + pendingCommand: { + kind: "resume", + args: "focus parser cache", + }, + }); + }); + + it("uses the explicit command flow for setup before falling back to the raw skill call", () => { + const cwd = createTempDir(); + const api = { + resolvePath: vi.fn(() => cwd), + registerCommand: vi.fn(), + }; + + registerAutoresearchCommand(api as never); + + const command = api.registerCommand.mock.calls[0]?.[0]; + const result = command.handler({ args: "", cwd }); + + expect(result.text).toContain("Autoresearch mode ON."); + expect(result.text).toContain( + "Next step: send a normal message so the next agent turn can gather setup details.", + ); + expect(result.text).toContain("Direct skill fallback: `/skill autoresearch-create`."); + expect(getAutoresearchRuntimeState(cwd)).toMatchObject({ + mode: "on", + pendingCommand: { + kind: "setup", + args: null, + }, + }); + }); +}); diff --git a/test/fixtures/active-session/autoresearch.ideas.md b/test/fixtures/active-session/autoresearch.ideas.md new file mode 100644 index 0000000..6f7535a --- /dev/null +++ b/test/fixtures/active-session/autoresearch.ideas.md @@ -0,0 +1,3 @@ +- Retry the parser change with a safer cache key +- Separate compile and runtime metrics +- Investigate benchmark startup overhead diff --git a/test/fixtures/active-session/autoresearch.jsonl b/test/fixtures/active-session/autoresearch.jsonl new file mode 100644 index 0000000..05f1dc4 --- /dev/null +++ b/test/fixtures/active-session/autoresearch.jsonl @@ -0,0 +1,6 @@ +{"type":"config","name":"Parser optimization","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"} +{"run":1,"commit":"abc1234","metric":125,"metrics":{"compile_ms":50},"status":"keep","description":"baseline","timestamp":1700000000000,"segment":0} +{"run":2,"commit":"def5678","metric":120,"metrics":{"compile_ms":48},"status":"discard","description":"attempt 1","timestamp":1700000001000,"segment":0} +{"type":"config","name":"Parser optimization","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"} +{"run":1,"commit":"9876543","metric":130,"metrics":{"compile_ms":55,"bundle_kb":10},"status":"keep","description":"new baseline","timestamp":1700000002000,"segment":1} +{"run":2,"commit":"7654321","metric":118,"metrics":{"compile_ms":44,"bundle_kb":9},"status":"keep","description":"keep winner","timestamp":1700000003000,"segment":1} diff --git a/test/fixtures/active-session/autoresearch.md b/test/fixtures/active-session/autoresearch.md new file mode 100644 index 0000000..714bc40 --- /dev/null +++ b/test/fixtures/active-session/autoresearch.md @@ -0,0 +1,5 @@ +# Autoresearch + +## Objective + +Reduce benchmark runtime without changing behavior. diff --git a/test/hooks.test.ts b/test/hooks.test.ts new file mode 100644 index 0000000..b9d97e9 --- /dev/null +++ b/test/hooks.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it, vi } from "vitest"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { AUTORESEARCH_ROOT_FILES } from "../extensions/openclaw-autoresearch/src/files.js"; +import { + buildBeforePromptBuildContext, + registerAutoresearchHooks, +} from "../extensions/openclaw-autoresearch/src/hooks.js"; +import { + getAutoresearchRuntimeState, + queueAutoresearchSteer, + setAutoresearchContinuationReminder, + setAutoresearchPendingCommand, + setAutoresearchRunInFlight, + setAutoresearchRuntimeMode, +} from "../extensions/openclaw-autoresearch/src/runtime-state.js"; + +function createTempDir(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} + +function seedActiveSession(cwd: string): void { + fs.writeFileSync( + path.join(cwd, AUTORESEARCH_ROOT_FILES.sessionDoc), + "# Autoresearch\n\n## Objective\n\nReduce runtime.\n\n## What's Been Tried\n\n- baseline\n", + ); + fs.writeFileSync( + path.join(cwd, AUTORESEARCH_ROOT_FILES.ideasBacklog), + "- retry the parser cache\n- measure compile_ms separately\n", + ); + fs.writeFileSync( + path.join(cwd, AUTORESEARCH_ROOT_FILES.resultsLog), + [ + JSON.stringify({ + type: "config", + name: "Runtime optimization", + metricName: "total_ms", + metricUnit: "ms", + bestDirection: "lower", + }), + JSON.stringify({ + run: 1, + commit: "abc1234", + metric: 101, + metrics: {}, + status: "keep", + description: "baseline", + timestamp: 1700000000000, + segment: 0, + }), + ].join("\n"), + ); +} + +describe("autoresearch hooks", () => { + it("builds a stronger before_prompt_build context for active sessions", () => { + const cwd = createTempDir("autoresearch-hooks-context-"); + seedActiveSession(cwd); + setAutoresearchRuntimeMode(cwd, "on"); + setAutoresearchPendingCommand(cwd, { + kind: "resume", + args: "focus parser cache", + }); + queueAutoresearchSteer(cwd, "try branchless parsing"); + setAutoresearchContinuationReminder(cwd, true); + + const context = buildBeforePromptBuildContext(cwd); + + expect(context).toContain("## Autoresearch Mode (ACTIVE)"); + expect(context).toContain("Never stop unless the user explicitly interrupts the loop."); + expect(context).toContain("Additional resume instruction from /autoresearch: focus parser cache"); + expect(context).toContain("The previous autoresearch run ended with pending ideas."); + expect(context).toContain("1 user steer arrived during the current experiment window"); + + const secondContext = buildBeforePromptBuildContext(cwd); + expect(secondContext).not.toContain("The previous autoresearch run ended with pending ideas."); + }); + + it("registers OpenClaw-compatible hooks and queues steer messages only while experiments are in flight", () => { + const cwd = createTempDir("autoresearch-hooks-runtime-"); + const handlers = new Map unknown>(); + const api = { + resolvePath: vi.fn(() => cwd), + on: vi.fn((hookName: string, handler: (event: unknown, ctx: { cwd?: string }) => unknown) => { + handlers.set(hookName, handler); + }), + }; + + registerAutoresearchHooks(api as never); + + expect(handlers.has("message_received")).toBe(true); + + setAutoresearchRunInFlight(cwd, false); + handlers.get("message_received")?.({ text: "ignore this" }, { cwd }); + expect(getAutoresearchRuntimeState(cwd).queuedSteers).toEqual([]); + + setAutoresearchRunInFlight(cwd, true); + handlers.get("message_received")?.({ text: "/help" }, { cwd }); + handlers.get("message_received")?.({ text: "try branchless parsing" }, { cwd }); + + expect(getAutoresearchRuntimeState(cwd).queuedSteers).toEqual([ + "try branchless parsing", + ]); + }); +}); diff --git a/test/lifecycle.test.ts b/test/lifecycle.test.ts new file mode 100644 index 0000000..60d0fbe --- /dev/null +++ b/test/lifecycle.test.ts @@ -0,0 +1,528 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { createInitExperimentTool } from "../extensions/openclaw-autoresearch/src/tools/init-experiment.js"; +import { createRunExperimentTool } from "../extensions/openclaw-autoresearch/src/tools/run-experiment.js"; +import { createLogExperimentTool } from "../extensions/openclaw-autoresearch/src/tools/log-experiment.js"; +import { getAutoresearchRootFilePath } from "../extensions/openclaw-autoresearch/src/files.js"; +import * as gitModule from "../extensions/openclaw-autoresearch/src/git.js"; +import { + getAutoresearchRuntimeState, + queueAutoresearchSteer, +} from "../extensions/openclaw-autoresearch/src/runtime-state.js"; + +function createTempDir(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} + +function createAbortSignal(): AbortSignal { + return new AbortController().signal; +} + +function createApi(cwd: string) { + return { + resolvePath: vi.fn(() => cwd), + }; +} + +function readJsonl(cwd: string): Array> { + const jsonlPath = getAutoresearchRootFilePath(cwd, "resultsLog"); + return fs + .readFileSync(jsonlPath, "utf8") + .split("\n") + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => JSON.parse(line) as Record); +} + +async function initExperiment( + cwd: string, + params: { + name: string; + metric_name: string; + metric_unit?: string; + direction?: "lower" | "higher"; + }, +) { + return await createInitExperimentTool(createApi(cwd) as never).execute( + "tool-call", + params, + createAbortSignal(), + undefined, + ); +} + +async function runExperiment( + cwd: string, + params: { + command: string; + timeout_seconds?: number; + }, + onUpdate?: (update: unknown) => void | Promise, +) { + return await createRunExperimentTool(createApi(cwd) as never).execute( + "tool-call", + params, + createAbortSignal(), + onUpdate, + ); +} + +async function logExperiment( + cwd: string, + params: { + commit: string; + metric: number; + status: "keep" | "discard" | "crash"; + description: string; + metrics?: Record; + force?: boolean; + }, +) { + return await createLogExperimentTool(createApi(cwd) as never).execute( + "tool-call", + params, + createAbortSignal(), + undefined, + ); +} + +async function seedExperiment(cwd: string) { + await initExperiment(cwd, { + name: "Parser optimization", + metric_name: "total_ms", + metric_unit: "ms", + direction: "lower", + }); +} + +describe("experiment lifecycle tools", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("writes the first config header to the root autoresearch.jsonl file", async () => { + const cwd = createTempDir("autoresearch-init-"); + + const result = await initExperiment(cwd, { + name: "Parser optimization", + metric_name: "total_ms", + metric_unit: "ms", + direction: "lower", + }); + + expect(result.details).toMatchObject({ + status: "ok", + state: { + name: "Parser optimization", + metricName: "total_ms", + metricUnit: "ms", + bestDirection: "lower", + currentSegment: 0, + }, + }); + expect(readJsonl(cwd)).toEqual([ + { + type: "config", + name: "Parser optimization", + metricName: "total_ms", + metricUnit: "ms", + bestDirection: "lower", + }, + ]); + }); + + it("appends a new config header on re-init instead of overwriting prior history", async () => { + const cwd = createTempDir("autoresearch-reinit-"); + + await initExperiment(cwd, { + name: "Parser optimization", + metric_name: "total_ms", + metric_unit: "ms", + direction: "lower", + }); + fs.appendFileSync( + getAutoresearchRootFilePath(cwd, "resultsLog"), + `${JSON.stringify({ + run: 1, + commit: "abc1234", + metric: 120, + metrics: {}, + status: "keep", + description: "baseline", + timestamp: 1700000000000, + segment: 0, + })}\n`, + ); + + const result = await initExperiment(cwd, { + name: "Parser optimization v2", + metric_name: "total_ms", + metric_unit: "ms", + direction: "lower", + }); + + expect(result.content[0]?.type).toBe("text"); + expect((result.content[0] as { text: string }).text).toContain("re-initialized"); + expect(readJsonl(cwd)).toEqual([ + { + type: "config", + name: "Parser optimization", + metricName: "total_ms", + metricUnit: "ms", + bestDirection: "lower", + }, + { + run: 1, + commit: "abc1234", + metric: 120, + metrics: {}, + status: "keep", + description: "baseline", + timestamp: 1700000000000, + segment: 0, + }, + { + type: "config", + name: "Parser optimization v2", + metricName: "total_ms", + metricUnit: "ms", + bestDirection: "lower", + }, + ]); + expect(result.details).toMatchObject({ + status: "ok", + state: { + currentSegment: 1, + currentRunCount: 0, + }, + }); + }); + + it("reports a successful run and emits a running update", async () => { + const cwd = createTempDir("autoresearch-run-success-"); + const updates: unknown[] = []; + + const result = await runExperiment( + cwd, + { + command: "printf 'hello\\nworld\\n'", + }, + (update) => { + updates.push(update); + }, + ); + + expect(updates).toEqual([ + { + content: [{ type: "text", text: "Running: printf 'hello\\nworld\\n'" }], + details: { phase: "running" }, + }, + ]); + expect(result.details).toMatchObject({ + command: "printf 'hello\\nworld\\n'", + exitCode: 0, + passed: true, + crashed: false, + timedOut: false, + stdout: "hello\nworld\n", + stderr: "", + tailOutput: "hello\nworld", + }); + expect((result.content[0] as { text: string }).text).toContain("PASSED"); + expect((result.content[0] as { text: string }).text).toContain( + "Last 80 lines of output:\nhello\nworld", + ); + }); + + it("reports failed runs with exit code and stderr in the captured tail", async () => { + const cwd = createTempDir("autoresearch-run-failure-"); + + const result = await runExperiment(cwd, { + command: "echo 'bad stderr' >&2; exit 7", + }); + + expect(result.details).toMatchObject({ + exitCode: 7, + passed: false, + crashed: true, + timedOut: false, + stdout: "", + stderr: "bad stderr\n", + tailOutput: "bad stderr", + }); + expect((result.content[0] as { text: string }).text).toContain( + "FAILED (exit code 7)", + ); + }); + + it("marks timed out runs and keeps the output-tail shape to the last 80 lines", async () => { + const cwd = createTempDir("autoresearch-run-timeout-"); + + const timeoutResult = await runExperiment(cwd, { + command: "echo start; sleep 0.2; echo done", + timeout_seconds: 0.05, + }); + + expect(timeoutResult.details).toMatchObject({ + exitCode: null, + passed: false, + crashed: true, + timedOut: true, + }); + expect((timeoutResult.content[0] as { text: string }).text).toContain("TIMEOUT"); + expect(timeoutResult.details.stdout).toContain("start\n"); + expect(timeoutResult.details.stdout).not.toContain("done\n"); + + const tailResult = await runExperiment(cwd, { + command: + "i=1; while [ $i -le 100 ]; do echo line-$i; i=$((i+1)); done", + }); + + const tailLines = tailResult.details.tailOutput.split("\n"); + expect(tailLines).toHaveLength(80); + expect(tailLines[0]).toBe("line-21"); + expect(tailLines[79]).toBe("line-100"); + expect(tailResult.details.tailOutput).not.toContain("line-20"); + }); + + it("appends a result row to autoresearch.jsonl and preserves secondary metrics in state", async () => { + const cwd = createTempDir("autoresearch-log-append-"); + await seedExperiment(cwd); + + const result = await logExperiment(cwd, { + commit: "abc1234", + metric: 120, + status: "discard", + description: "baseline", + metrics: { + compile_ms: 15, + }, + }); + + const rows = readJsonl(cwd); + expect(rows).toHaveLength(2); + expect(rows[1]).toMatchObject({ + run: 1, + commit: "abc1234", + metric: 120, + metrics: { + compile_ms: 15, + }, + status: "discard", + description: "baseline", + segment: 0, + }); + expect(result.details).toMatchObject({ + status: "ok", + experiment: { + run: 1, + commit: "abc1234", + }, + state: { + currentRunCount: 1, + totalRunCount: 1, + secondaryMetrics: [{ name: "compile_ms", unit: "ms" }], + }, + git: { + action: "skip", + attempted: false, + }, + }); + expect((result.content[0] as { text: string }).text).toContain( + "Git: skipped commit (discard) - revert tracked changes yourself with git checkout -- .", + ); + }); + + it("validates missing and newly added secondary metrics unless forced", async () => { + const cwd = createTempDir("autoresearch-log-metrics-"); + await seedExperiment(cwd); + const baselineResult = await logExperiment(cwd, { + commit: "abc1234", + metric: 120, + status: "discard", + description: "baseline", + metrics: { + compile_ms: 15, + }, + }); + expect(baselineResult.details).toMatchObject({ status: "ok" }); + + const missingMetric = await logExperiment(cwd, { + commit: "def5678", + metric: 110, + status: "discard", + description: "missing secondary metric", + metrics: {}, + }); + + expect(missingMetric.details).toMatchObject({ + status: "error", + phase: "validate", + }); + expect((missingMetric.content[0] as { text: string }).text).toContain( + "Missing secondary metrics: compile_ms", + ); + + const newMetricWithoutForce = await logExperiment(cwd, { + commit: "def5678", + metric: 110, + status: "discard", + description: "add bundle size", + metrics: { + compile_ms: 12, + bundle_kb: 7, + }, + }); + + expect(newMetricWithoutForce.details).toMatchObject({ + status: "error", + phase: "validate", + }); + expect((newMetricWithoutForce.content[0] as { text: string }).text).toContain( + "New secondary metric not previously tracked: bundle_kb", + ); + + const forcedNewMetric = await logExperiment(cwd, { + commit: "def5678", + metric: 110, + status: "discard", + description: "add bundle size", + metrics: { + compile_ms: 12, + bundle_kb: 7, + }, + force: true, + }); + + expect(forcedNewMetric.details).toMatchObject({ + status: "ok", + state: { + secondaryMetrics: [ + { name: "compile_ms", unit: "ms" }, + { name: "bundle_kb", unit: "kb" }, + ], + }, + }); + }); + + it("routes keep to commit and leaves discard/crash as manual reverts while preserving logged statuses", async () => { + const cwd = createTempDir("autoresearch-log-status-"); + await seedExperiment(cwd); + const commitSpy = vi.spyOn(gitModule, "commitKeptExperiment").mockReturnValue({ + attempted: true, + committed: true, + commit: "def5678", + summary: "Git: committed - [main def5678] keep improved parser", + command: { + code: 0, + stdout: "[main def5678] keep improved parser\n", + stderr: "", + combinedOutput: "[main def5678] keep improved parser", + }, + }); + const keepResult = await logExperiment(cwd, { + commit: "abc1234", + metric: 95, + status: "keep", + description: "keep improved parser", + }); + + expect(keepResult.details).toMatchObject({ + status: "ok", + git: { + action: "commit", + attempted: true, + committed: true, + commit: "def5678", + }, + experiment: { + commit: "def5678", + status: "keep", + }, + }); + expect(commitSpy).toHaveBeenCalledTimes(1); + + const discardResult = await logExperiment(cwd, { + commit: "def5678", + metric: 130, + status: "discard", + description: "discard regression", + }); + + expect(discardResult.details).toMatchObject({ + status: "ok", + git: { + action: "skip", + attempted: false, + }, + experiment: { + status: "discard", + }, + }); + expect((discardResult.content[0] as { text: string }).text).toContain( + "revert tracked changes yourself with git checkout -- .", + ); + + const crashResult = await logExperiment(cwd, { + commit: "def5678", + metric: 0, + status: "crash", + description: "crashed benchmark", + }); + + expect(crashResult.details).toMatchObject({ + status: "ok", + git: { + action: "skip", + attempted: false, + }, + experiment: { + status: "crash", + metric: 0, + }, + }); + expect((crashResult.content[0] as { text: string }).text).toContain( + "revert tracked changes yourself with git checkout -- .", + ); + + const statuses = readJsonl(cwd) + .filter((entry) => entry.type !== "config") + .map((entry) => entry.status); + expect(statuses).toEqual(["keep", "discard", "crash"]); + }); + + it("keeps the experiment window open across run_experiment and surfaces queued steers in log_experiment", async () => { + const cwd = createTempDir("autoresearch-queued-steers-"); + await seedExperiment(cwd); + + await runExperiment(cwd, { + command: "printf 'baseline\\n'", + }); + + expect(getAutoresearchRuntimeState(cwd)).toMatchObject({ + runInFlight: true, + }); + + queueAutoresearchSteer(cwd, "try a parser cache"); + queueAutoresearchSteer(cwd, "watch compile_ms too"); + + const result = await logExperiment(cwd, { + commit: "abc1234", + metric: 120, + status: "discard", + description: "baseline", + }); + + expect((result.content[0] as { text: string }).text).toContain( + "Queued user steers captured during this experiment:", + ); + expect((result.content[0] as { text: string }).text).toContain("- try a parser cache"); + expect((result.content[0] as { text: string }).text).toContain("- watch compile_ms too"); + expect(getAutoresearchRuntimeState(cwd)).toMatchObject({ + runInFlight: false, + queuedSteers: [], + }); + }); +}); diff --git a/test/plugin.test.ts b/test/plugin.test.ts new file mode 100644 index 0000000..7b8fa8a --- /dev/null +++ b/test/plugin.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it, vi } from "vitest"; +import plugin from "../extensions/openclaw-autoresearch/index.js"; + +describe("plugin registration", () => { + it("registers the command, OpenClaw hooks, and all tool surfaces", () => { + const api = { + resolvePath: vi.fn(() => "/tmp/repo"), + registerTool: vi.fn(), + registerCommand: vi.fn(), + on: vi.fn(), + }; + + plugin.register(api); + + expect(api.registerCommand).toHaveBeenCalledTimes(1); + expect(api.on).toHaveBeenCalledTimes(4); + expect(api.on.mock.calls.map(([hookName]) => hookName)).toEqual([ + "before_prompt_build", + "message_received", + "agent_end", + "session_end", + ]); + expect(api.registerTool).toHaveBeenCalledTimes(4); + expect(api.registerTool.mock.calls.map(([tool]) => tool.name)).toEqual([ + "init_experiment", + "run_experiment", + "log_experiment", + "autoresearch_status", + ]); + }); +}); diff --git a/test/shims/openclaw-plugin-sdk-core.ts b/test/shims/openclaw-plugin-sdk-core.ts new file mode 100644 index 0000000..0576abe --- /dev/null +++ b/test/shims/openclaw-plugin-sdk-core.ts @@ -0,0 +1,25 @@ +export type CommandRegistration = { + name: string; + description: string; + acceptsArgs?: boolean; + handler: (ctx: { args?: string; cwd?: string }) => { text: string }; +}; + +export type ToolRegistration = { + name: string; +}; + +export type OpenClawPluginApi = { + resolvePath(path: string): string; + registerTool(tool: ToolRegistration): void; + registerCommand(command: CommandRegistration): void; + on?(hookName: string, handler: (event: unknown, ctx: { cwd?: string }) => unknown): void; + registerHook?( + hookName: string, + handler: (event: { systemPrompt?: string }, ctx: { cwd?: string }) => { systemPrompt?: string } | void, + ): void; +}; + +export function emptyPluginConfigSchema(): Record { + return {}; +} diff --git a/test/state.test.ts b/test/state.test.ts new file mode 100644 index 0000000..6d92702 --- /dev/null +++ b/test/state.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import path from "node:path"; +import { reconstructStateFromJsonl } from "../extensions/openclaw-autoresearch/src/state.js"; + +const activeSessionFixture = path.resolve("test/fixtures/active-session"); + +describe("reconstructStateFromJsonl", () => { + it("rebuilds the active session snapshot from canonical fixtures", () => { + const state = reconstructStateFromJsonl(activeSessionFixture); + + expect(state.name).toBe("Parser optimization"); + expect(state.mode).toBe("active"); + expect(state.hasSessionDoc).toBe(true); + expect(state.currentSegment).toBe(1); + expect(state.currentRunCount).toBe(2); + expect(state.totalRunCount).toBe(4); + expect(state.currentBaselineMetric).toBe(130); + expect(state.currentBestMetric).toBe(118); + expect(state.lastRun).toMatchObject({ + run: 2, + commit: "7654321", + metric: 118, + status: "keep", + description: "keep winner", + segment: 1, + }); + expect(state.secondaryMetrics).toEqual([ + { name: "compile_ms", unit: "ms" }, + { name: "bundle_kb", unit: "kb" }, + ]); + expect(state.ideas).toEqual({ + hasBacklog: true, + pendingCount: 3, + preview: [ + "Retry the parser change with a safer cache key", + "Separate compile and runtime metrics", + "Investigate benchmark startup overhead", + ], + }); + }); +}); diff --git a/test/status.test.ts b/test/status.test.ts new file mode 100644 index 0000000..0123d64 --- /dev/null +++ b/test/status.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "vitest"; +import { formatAutoresearchStatusText } from "../extensions/openclaw-autoresearch/src/tools/autoresearch-status.js"; +import { reconstructStateFromJsonl } from "../extensions/openclaw-autoresearch/src/state.js"; +import { getAutoresearchRuntimeState } from "../extensions/openclaw-autoresearch/src/runtime-state.js"; +import path from "node:path"; + +const activeSessionFixture = path.resolve("test/fixtures/active-session"); + +describe("formatAutoresearchStatusText", () => { + it("renders a concise status summary from reconstructed state", () => { + const text = formatAutoresearchStatusText( + reconstructStateFromJsonl(activeSessionFixture), + getAutoresearchRuntimeState(activeSessionFixture), + ); + + expect(text).toContain("Session: Parser optimization"); + expect(text).toContain("Runtime mode: auto"); + expect(text).toContain("Experiment window: idle"); + expect(text).toContain("Queued steers: 0"); + expect(text).toContain("Metric: total_ms (ms, lower is better)"); + expect(text).toContain("Runs: 2 current / 4 total"); + expect(text).toContain("Best kept: 118ms"); + expect(text).toContain("Last run: #2 keep 118ms 7654321 keep winner"); + expect(text).toContain( + "Ideas preview: Retry the parser change with a safer cache key | Separate compile and runtime metrics | Investigate benchmark startup overhead", + ); + }); +}); diff --git a/test/tool-execute.test.ts b/test/tool-execute.test.ts new file mode 100644 index 0000000..e7b776f --- /dev/null +++ b/test/tool-execute.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it, vi } from "vitest"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { createInitExperimentTool } from "../extensions/openclaw-autoresearch/src/tools/init-experiment.js"; +import { createAutoresearchStatusTool } from "../extensions/openclaw-autoresearch/src/tools/autoresearch-status.js"; +import { createRunExperimentTool } from "../extensions/openclaw-autoresearch/src/tools/run-experiment.js"; +import { createLogExperimentTool } from "../extensions/openclaw-autoresearch/src/tools/log-experiment.js"; +import { AUTORESEARCH_ROOT_FILES } from "../extensions/openclaw-autoresearch/src/files.js"; + +function createTempDir(): string { + return fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "autoresearch-tool-test-"))); +} + +function createApi(cwd: string) { + return { + resolvePath: vi.fn(() => cwd), + }; +} + +describe("autoresearch tools", () => { + it("init_experiment resolves cwd from api.resolvePath instead of expecting a fifth execute arg", async () => { + const cwd = createTempDir(); + const api = createApi(cwd); + const tool = createInitExperimentTool(api as never); + + const result = await tool.execute( + "call-1", + { + name: "Repo robustness", + metric_name: "escaped_mutations", + metric_unit: "", + direction: "lower", + }, + new AbortController().signal, + undefined, + ); + + expect(api.resolvePath).toHaveBeenCalledWith("."); + expect(result.details).toMatchObject({ status: "ok" }); + expect(fs.existsSync(path.join(cwd, AUTORESEARCH_ROOT_FILES.resultsLog))).toBe(true); + }); + + it("autoresearch_status resolves cwd from api.resolvePath", async () => { + const cwd = createTempDir(); + const api = createApi(cwd); + await createInitExperimentTool(api as never).execute( + "call-1", + { + name: "Repo robustness", + metric_name: "escaped_mutations", + metric_unit: "", + direction: "lower", + }, + new AbortController().signal, + undefined, + ); + + const result = await createAutoresearchStatusTool(api as never).execute( + "call-2", + {}, + new AbortController().signal, + undefined, + ); + + expect(api.resolvePath).toHaveBeenCalledWith("."); + expect(result.content[0]?.text).toContain("Session: Repo robustness"); + expect(result.content[0]?.text).toContain("Metric: escaped_mutations"); + }); + + it("run_experiment resolves cwd from api.resolvePath", async () => { + const cwd = createTempDir(); + const api = createApi(cwd); + const updates: unknown[] = []; + + const result = await createRunExperimentTool(api as never).execute( + "call-3", + { command: "node -e \"console.log('ok')\"", timeout_seconds: 10 }, + new AbortController().signal, + async (update) => { + updates.push(update); + }, + ); + + expect(api.resolvePath).toHaveBeenCalledWith("."); + expect(updates.length).toBe(1); + expect(result.details).toMatchObject({ passed: true, timedOut: false }); + }); + + it("log_experiment resolves cwd from api.resolvePath", async () => { + const cwd = createTempDir(); + const api = createApi(cwd); + await createInitExperimentTool(api as never).execute( + "call-1", + { + name: "Repo robustness", + metric_name: "escaped_mutations", + metric_unit: "", + direction: "lower", + }, + new AbortController().signal, + undefined, + ); + + const result = await createLogExperimentTool(api as never).execute( + "call-4", + { + commit: "abc1234", + metric: 5, + status: "discard", + description: "baseline", + }, + new AbortController().signal, + undefined, + ); + + expect(api.resolvePath).toHaveBeenCalledWith("."); + expect(result.details).toMatchObject({ status: "ok" }); + expect(result.content[0]?.text).toContain("Logged #1: discard - baseline"); + }); + + it("supports explicit cwd overrides for nested repo tool execution", async () => { + const workspaceCwd = createTempDir(); + const repoCwd = createTempDir(); + const api = createApi(workspaceCwd); + + const initResult = await createInitExperimentTool(api as never).execute( + "call-1", + { + cwd: repoCwd, + name: "Nested repo robustness", + metric_name: "escaped_mutations", + metric_unit: "", + direction: "lower", + }, + new AbortController().signal, + undefined, + ); + + expect(initResult.details).toMatchObject({ status: "ok" }); + expect(api.resolvePath).not.toHaveBeenCalled(); + expect(fs.existsSync(path.join(repoCwd, AUTORESEARCH_ROOT_FILES.resultsLog))).toBe(true); + expect(fs.existsSync(path.join(workspaceCwd, AUTORESEARCH_ROOT_FILES.resultsLog))).toBe(false); + + const runResult = await createRunExperimentTool(api as never).execute( + "call-2", + { + cwd: repoCwd, + command: "node -e \"console.log(process.cwd())\"", + timeout_seconds: 10, + }, + new AbortController().signal, + undefined, + ); + + expect(runResult.details).toMatchObject({ passed: true, timedOut: false }); + expect(runResult.details.stdout.trim()).toBe(repoCwd); + + const statusResult = await createAutoresearchStatusTool(api as never).execute( + "call-3", + { cwd: repoCwd }, + new AbortController().signal, + undefined, + ); + + expect(statusResult.content[0]?.text).toContain("Session: Nested repo robustness"); + + const logResult = await createLogExperimentTool(api as never).execute( + "call-4", + { + cwd: repoCwd, + commit: "abc1234", + metric: 5, + status: "discard", + description: "baseline", + }, + new AbortController().signal, + undefined, + ); + + expect(logResult.details).toMatchObject({ status: "ok" }); + expect(logResult.content[0]?.text).toContain("Logged #1: discard - baseline"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3b3cf22 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node", "vitest/globals"], + "baseUrl": ".", + "paths": { + "openclaw/plugin-sdk/core": ["./test/shims/openclaw-plugin-sdk-core.ts"] + } + }, + "include": [ + "extensions/openclaw-autoresearch/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..a516f1b --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "vitest/config"; +import { fileURLToPath } from "node:url"; + +const rootDir = fileURLToPath(new URL(".", import.meta.url)); +const shimPath = fileURLToPath( + new URL("./test/shims/openclaw-plugin-sdk-core.ts", import.meta.url), +); + +export default defineConfig({ + resolve: { + alias: { + "openclaw/plugin-sdk/core": shimPath, + }, + }, + test: { + environment: "node", + include: ["test/**/*.test.ts"], + root: rootDir, + }, +});