From 45c3da7bcd2ce91d5828c0a84bfe1dfa088d932a Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Sat, 18 Jul 2026 11:13:38 -0700 Subject: [PATCH 01/32] feat: add OpenCode 2 beta adapter --- .github/workflows/ci.yml | 49 +++ .github/workflows/release-v2.yml | 79 ++++ OPENCODE_V2_IMPACT_REPORT.md | 617 +++++++++++++++++++++++++++++++ README.md | 19 + bun.lock | 201 ++++++++++ package.json | 7 +- packages/v2/LICENSE | 21 ++ packages/v2/README.md | 15 + packages/v2/package.json | 40 ++ src/config/agent.ts | 14 +- src/config/global.ts | 9 +- src/execution/shell.ts | 15 +- src/executor.ts | 51 +-- src/index.ts | 44 ++- src/v2.ts | 4 + src/v2/plugin.ts | 247 +++++++++++++ tests/agent.config.test.ts | 9 + tests/config.global.test.ts | 17 + tests/e2e.v2.test.ts | 100 +++++ tests/execution.shell.test.ts | 16 + tests/executor.host.test.ts | 38 ++ tests/v2.package.test.ts | 20 + tests/v2.plugin.test.ts | 193 ++++++++++ tsconfig.build.json | 5 + tsconfig.json | 4 + 25 files changed, 1781 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-v2.yml create mode 100644 OPENCODE_V2_IMPACT_REPORT.md create mode 100644 packages/v2/LICENSE create mode 100644 packages/v2/README.md create mode 100644 packages/v2/package.json create mode 100644 src/v2.ts create mode 100644 src/v2/plugin.ts create mode 100644 tests/e2e.v2.test.ts create mode 100644 tests/executor.host.test.ts create mode 100644 tests/v2.package.test.ts create mode 100644 tests/v2.plugin.test.ts create mode 100644 tsconfig.build.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c417e19 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: ci + +on: + pull_request: + push: + branches: + - main + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm test + + - name: Build V1 and V2 + run: npm run build:all + + - name: Verify V1 package + run: npm pack --dry-run + + - name: Verify V2 package + run: npm pack --dry-run + working-directory: packages/v2 diff --git a/.github/workflows/release-v2.yml b/.github/workflows/release-v2.yml new file mode 100644 index 0000000..45a4a5f --- /dev/null +++ b/.github/workflows/release-v2.yml @@ -0,0 +1,79 @@ +name: release-v2 + +on: + push: + tags: + - "v2-*" + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + +permissions: + contents: read + id-token: write + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + + - name: Validate tag and package version + run: | + node -e ' + const fs = require("node:fs"); + const tag = process.env.GITHUB_REF_NAME || ""; + const version = JSON.parse(fs.readFileSync("packages/v2/package.json", "utf8")).version; + if (tag !== `v2-${version}`) { + throw new Error(`tag/version mismatch: tag=${tag} package=${version}`); + } + ' + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Verify repository + run: npm run lint && npm run typecheck && npm test + + - name: Build and pack V2 + run: npm --prefix ../.. run build:v2 && npm pack --dry-run + working-directory: packages/v2 + + - name: Test packed package in OpenCode 2 + run: npm run test:v2:e2e + + publish: + needs: verify + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Publish V2 beta + run: npm publish --access public --provenance --tag next + working-directory: packages/v2 diff --git a/OPENCODE_V2_IMPACT_REPORT.md b/OPENCODE_V2_IMPACT_REPORT.md new file mode 100644 index 0000000..4bf20b6 --- /dev/null +++ b/OPENCODE_V2_IMPACT_REPORT.md @@ -0,0 +1,617 @@ +# OpenCode 2 Impact and Migration Report + +Status: Implemented beta prototype +Last updated: 2026-07-17 +Doc Class: report +Doc Type: research +Report Type: engineering migration +Decision Status: accepted for opt-in beta +Authority: advisory +Canonical Source: `OPENCODE_V2_IMPACT_REPORT.md` +Report topic: OpenCode 2 plugin and client migration + +## Summary + +OpenCode 2 is currently a beta distributed separately from stable OpenCode 1. The upstream project explicitly warns that its APIs, configuration, data, and plugin APIs may still change. It also explicitly states that OpenCode 1 plugins do not work in OpenCode 2. + +`opencode-command-hooks` will not load in OpenCode 2 in its current form. The package exports an OpenCode 1 plugin function that returns a hooks object. OpenCode 2 requires a default plugin descriptor with a unique `id` and `setup` or `effect` function, and hooks are registered imperatively through the new context. + +The current OpenCode 2 beta has enough API surface to prototype most core behavior: + +- `ctx.tool.hook("execute.before", ...)` and `ctx.tool.hook("execute.after", ...)` can replace the v1 tool hooks. +- `ctx.event.subscribe()` exposes the public event stream for session lifecycle handling. +- `ctx.session.synthetic()` appears to be the intended replacement for injecting context without creating a normal user prompt. +- Existing v1-shaped config and agent Markdown locations are intended to be translated in memory. + +However, a production migration should not begin yet. Important contracts remain unstable or incomplete: + +- The beta guide and current `v2` branch disagree on at least one hook name: the guide uses `ctx.session.hook("request")`, while source currently defines `ctx.session.hook("context")`. +- The guide and source also differ on some tool context field names. +- Server plugins have no public equivalent to v1 `client.tui.showToast()` or `client.app.log()`. +- Plugin reload isolation and config-directory reload have open defects. +- The package must determine the correct location/workspace rather than use `process.cwd()` in the long-lived v2 service. + +The recommended release strategy is to preserve `opencode-command-hooks` as the v1 line and publish any beta port under a separate package name, such as `opencode-command-hooks-v2`, using exact prerelease versions. Do not move the existing package's npm `latest` tag to a v2-only artifact while a large v1 population remains. + +## Implementation Addendum + +The opt-in beta adapter is now implemented in this repository: + +- `src/v2/plugin.ts` owns the V2 Promise adapter and keeps callback failures non-blocking. +- `packages/v2` produces the separate `opencode-command-hooks-v2` package without changing the V1 package identity or release tag. +- The package pins `@opencode-ai/plugin@0.0.0-next-15800` exactly. +- Tool hooks use direct V2 event input, and `subagent`/`agent` are normalized to the existing `task`/`subagent_type` config vocabulary. +- Session lookup supplies the authoritative project directory and agent for config discovery and command execution. +- Injection uses `ctx.session.synthetic()` with `resume: false`; toast requests produce one explicit unsupported diagnostic. +- The event consumer handles `session.created` and compatibility `session.idle`, deduplicates event IDs, and is closed during plugin cleanup. +- Unit, V1 regression, packed-artifact, and gated pinned-host E2E tests cover the adapter. +- A dedicated V2 release workflow requires the pinned host E2E to pass before npm publication. + +The pinned `0.0.0-next-15800` host currently returns an empty plugin list in the standalone E2E even for native config and local discovery. Its logs show the known config-directory watcher failure and no plugin-load attempt. The reproducible test remains gated behind `OPENCODE2_E2E=1`; publishing remains blocked until that upstream host behavior is fixed or a newer exact build passes. + +## Research Question + +- How will OpenCode 2 affect `opencode-command-hooks`? +- Which features can be migrated now, which require redesign, and which are blocked? +- How should v1 and v2 support coexist while users upgrade at different times? + +## Scope + +### In Scope + +- OpenCode 2 beta plugin loader and authoring API. +- Tool and session lifecycle contracts. +- Session context injection, notifications, logging, location, and configuration. +- Direct impact on the current repository. +- Package compatibility, release strategy, testing, rollout, and rollback. + +### Out of Scope + +- Implementing the v2 adapter. +- Changing the package, source, tests, release workflow, or npm metadata. +- Promising compatibility with an API that upstream still labels beta. + +## Method + +- Inspected this repository's package metadata, source, tests, generated `dist`, and release workflow. +- Used Firecrawl for current OpenCode 1 docs, OpenCode 2 beta docs, upstream issues, pull requests, and source pages. +- Used Context7 for the current OpenCode plugin API documentation and examples. +- Audited upstream Git branches at these snapshots: + - `dev`: `45cd8d76920839e4a7b6b931c4e26b52e1495636`, 2026-07-17. + - `v2`: `987242b3e83ff7ed870b376b94394d068c7d8790`, 2026-07-17. +- Queried npm metadata for current dist-tags. At research time, stable was `1.18.3`; beta/next/dev remained prerelease channels. +- Ran five parallel focused research tracks covering the v2 API, local impact, lifecycle contracts, coexistence strategy, and rollout risks. + +## Current State + +### Stable and beta are separate + +OpenCode 1 remains the stable line. The OpenCode 2 beta is installed from `@opencode-ai/cli@next` and runs as `opencode2`, allowing side-by-side evaluation with the stable `opencode` executable. + +Upstream's migration documentation says: + +- Plugins use a new API. +- Server APIs and clients use new contracts. +- V1 plugins will not work in V2. +- Detailed plugin migration guidance will be published after the API is finalized. +- Existing v1 server config, agent definitions, commands, skills, and `.opencode` files are intended to remain compatible through in-memory translation. + +### Two upstream development surfaces matter + +The default `dev` branch and the dedicated `v2` branch currently expose different v2 plugin capabilities. + +- `dev` has early v2 package exports but lacks tool, event, and session domains in its public context. +- `v2`, which backs the beta docs, has tool hooks, event subscription, session actions, and broader client capabilities. + +This branch divergence is a strong signal not to infer a stable contract from package paths alone. Migration work should pin and test a specific OpenCode beta build and matching `@opencode-ai/plugin` package. + +## Impact Matrix + +| Surface | Current implementation | OpenCode 2 impact | Severity | Proposed treatment | +| --- | --- | --- | --- | --- | +| Plugin export | `src/index.ts:253-405` exports a v1 `Plugin` function returning hooks | V2 loader accepts only a default `{ id, setup }` or `{ id, effect }` descriptor | Critical | New v2 entrypoint/package | +| Hook registration | Returned keys such as `tool.execute.before`, `tool.execute.after`, `event`, `chat.params` | V2 setup registers hooks imperatively through `ctx.tool.hook`, `ctx.event.subscribe`, and other domains | Critical | V2 host adapter | +| Before-tool payload | Separate `input` and mutable `output.args` | One event object with mutable `event.input`; IDs and agent are included directly | High | Normalize into shared internal context | +| After-tool payload | Args cached from before hook; `tool.result` fallback and dedupe | V2 after event includes `input`, `result`, `output`, and `outputPaths`; no cache should be necessary | High | Remove v1 cache/fallback from v2 adapter | +| Session lifecycle | Generic v1 `event` handling for `session.created`, `session.idle`, and `tool.result` | V2 has a typed event stream and durable execution events; envelope uses `data`, not v1 `properties` | High | Event normalization and dedupe | +| Session injection | `client.session.promptAsync({ path, body })` | New client shape is flat; `ctx.session.synthetic({ sessionID, text, ... })` is the likely context-only primitive | High | Injection port with explicit semantics tests | +| Toast | `client.tui.showToast({ body })` | No public server-plugin toast capability in current v2 context | High for feature parity | Defer, omit with diagnostics, or add a future TUI companion | +| Logging | `client.app.log({ body })` | No public v2 server-plugin logging facade found | Medium | Internal logger port; use supported diagnostics when finalized | +| Runtime directory | `process.cwd()` drives config discovery and command execution | V2 is a long-lived, multi-location service; ambient cwd is not a reliable project location | Critical | Capture plugin location and pass cwd explicitly | +| Agent Markdown | Directly reads `.opencode/agents` and legacy `.opencode/agent`; custom `hooks` frontmatter | V2 intends to keep all v1 agent directories compatible, but custom-field handling and agent identity must be verified | High | Preserve parser; replace discovery/context adapter | +| Provider option stripping | `chat.params` deletes `hooks` and `command_hooks` | No direct v2 equivalent is documented; v2 config decoding may ignore extras, but this must be tested | Medium | Contract test before removing behavior | +| Plugin config key | README tells users to use v1 `plugin` | Native v2 key is `plugins`; v1-shaped files are translated in memory | Medium | Do not force config migration for trial users; document native form separately | +| Shell execution | Global `Bun.$` with ambient cwd | V2 does not inject v1 `$`; Bun remains available but cwd must be explicit | High | Command runner port accepting cwd | +| Error semantics | Plugin catches failures so hooks remain non-blocking | V2 docs state an uncaught runtime-hook failure fails the intercepted operation | Critical | Keep a catch-all inside every v2 callback | +| Dependencies | `@opencode-ai/plugin` and SDK are broad v1 ranges | V2 docs require matching plugin package and host versions during beta | High | Exact prerelease pins in the v2 package | +| Test harness | E2E writes v1 `plugin` config and invokes `opencode` | Needs separate v1 stable and pinned `opencode2` suites | High | Versioned contract matrix | + +## Detailed Findings + +### 1. The current package cannot load in v2 + +The package's root export is a function: + +```ts +export const CommandHooksPlugin: Plugin = async ({ client }) => { + return { + event: async (...) => {}, + "tool.execute.before": async (...) => {}, + "tool.execute.after": async (...) => {}, + } +} +``` + +The v2 loader imports the module and validates its default export as one of: + +```ts +{ id: string, setup: function } +{ id: string, effect: function } +``` + +Invalid modules are skipped and logged. There is no automatic adapter for a v1 function returning hooks. + +Confidence: high. + +### 2. Tool hooks are migratable, but their contract is different + +The current v2 beta source exposes: + +```ts +await ctx.tool.hook("execute.before", async (event) => { + event.input = updatedInput +}) + +await ctx.tool.hook("execute.after", async (event) => { + // event.input, event.result, event.output, event.outputPaths +}) +``` + +Useful differences from v1: + +- `event.agent` is present directly. +- `event.input` is available to the after hook. +- The v1 `toolCallArgsCache` should not be needed in a v2 adapter. +- The undocumented `tool.result` fallback should not be carried into v2. +- Hook names inside the plugin context omit the `tool.` prefix. + +Risks: + +- Tool input is typed as `unknown` and must be narrowed. +- Built-in tool IDs and subagent input shapes must be observed in an actual beta build. The current plugin assumes a tool named `task` with `subagent_type`; v2 terminology and permission actions use `subagent`. +- The v2 guide says uncaught hook failures fail the intercepted operation. This plugin's non-blocking promise depends on handling every expected and unexpected error internally. + +Confidence: high for API shape, medium for built-in tool identities. + +### 3. Session lifecycle needs an adapter, not direct string substitution + +The v2 public event stream includes durable events such as: + +- `session.execution.started` +- `session.execution.succeeded` +- `session.execution.failed` +- `session.execution.interrupted` +- `session.input.admitted` +- `session.input.promoted` + +Compatibility events including `session.created` and `session.idle` also appear in generated client types, but the long-term event contract remains in motion. V2 events use an envelope with fields such as `type`, `data`, `durable`, and `location`; the current code expects v1 `event.properties`. + +Recommended normalization: + +- Preserve the public command-hooks config vocabulary initially: `session.start`, `session.created`, and `session.idle`. +- Normalize v2 `session.created` to start. +- Decide whether idle means compatibility `session.idle` or durable `session.execution.succeeded`; test both and dedupe by event/session/sequence. +- Resolve agent identity with the session client if the lifecycle event does not carry it. +- Do not add a v2 `session.end` mapping until upstream defines one. The current repository advertises `session.end` in `src/types/hooks.ts:173-178` but does not implement it in `src/index.ts`; that is an existing gap, not a v2 regression. + +The v2 Promise event stream is an `AsyncIterable`. Setup must start a cancellable background consumer and return cleanup. It must not await an infinite subscription inside `setup`. + +Confidence: medium-high. Event names are implemented, but upstream explicitly warns they may change. + +### 4. `session.synthetic` is the strongest injection candidate + +The current plugin injects text with v1 `session.promptAsync`. The v2 session API provides both normal prompts and synthetic messages. Synthetic messages carry text, optional description/metadata, and delivery policy, and are represented as synthetic context in the model-facing history. + +This maps closely to the plugin's intent: add deterministic hook output to context without impersonating a normal user prompt. + +Proposed v2 mapping: + +```ts +await ctx.session.synthetic({ + sessionID, + text: message, + description: "opencode-command-hooks", + metadata: { hookId }, +}) +``` + +Before adoption, tests must establish: + +- Whether default delivery queues or steers an active execution. +- Whether injection after a tool is visible at the intended continuation boundary. +- Whether synthetic insertion can cause recursion or an unwanted new model turn. +- Ordering when several hooks inject sequentially. +- Behavior after failed/interrupted executions. + +Confidence: medium. The API exists and its data model fits, but exact operational semantics need runtime proof. + +### 5. Toast parity is currently blocked + +The v2 server plugin context does not expose a TUI client or toast method. A separate TUI plugin surface has UI toast capabilities, but a server plugin cannot currently call them through the documented v2 context. + +Options, in order: + +1. Keep `toast` in the shared config but report it as unsupported in the v2 beta package. +2. Wait for an upstream server notification capability. +3. Build a separate TUI companion only if upstream provides a supported bridge between server and TUI plugin surfaces. + +Do not silently claim toast success in v2. Injection and command execution should continue even when toast is unavailable. + +Confidence: high. + +### 6. Ambient cwd is unsafe in the v2 service + +The current implementation searches from and executes in `process.cwd()`: + +- `src/config/global.ts:268-274` +- `src/config/agent.ts:74-81` +- `src/execution/shell.ts:177-196` + +OpenCode 2 uses a background service with location-scoped plugin hosts. One process may serve multiple directories/workspaces, so process cwd is not a valid location contract. + +The v2 client returns location metadata on several calls. A beta adapter may be able to capture the host location from a location-aware client response such as `ctx.agent.list()`, but upstream should ideally expose location directly in plugin context. The adapter should not be released until it has a deterministic directory source. + +The shared command engine should receive cwd as an explicit argument. Configuration search and agent-file resolution should receive the same location rather than reading global process state. + +Confidence: high for the risk, medium for the final upstream location API. + +### 7. Existing config can coexist, but native v2 config differs + +V2 intends to translate v1 config in memory. Existing users can keep: + +```jsonc +{ + "plugin": ["opencode-command-hooks"] +} +``` + +Native v2 configuration uses: + +```jsonc +{ + "plugins": [ + "opencode-command-hooks-v2@" + ] +} +``` + +Tuple package options become `{ "package": "...", "options": { ... } }`. + +Do not advise users to convert shared config to native v2 while they still run v1 against the same file. Upstream explicitly recommends keeping v1-shaped config during side-by-side evaluation. + +The plugin's own `command-hooks.jsonc` contract is independent of OpenCode's root config and can remain stable. Its location resolution must still become host-location-aware. + +Confidence: high. + +### 8. The v2 beta is not stable enough for a production default + +Evidence of active churn and risk includes: + +- Official warning that plugin entrypoints, hooks, draft shapes, and configuration may change before stable release. +- Guide/source disagreement over `session.hook("request")` versus `session.hook("context")`. +- Guide/source disagreement over some tool context field names. +- An open issue where a bad plugin tool can poison all sessions in a location and replacement is not atomic. +- An open issue where changes inside config directories fail to trigger command, agent, and plugin reloads. +- An open issue where self-update can orphan the v2 background service and hang clients. +- A same-day fix restoring `opencode2 plugin list`, whose discussion reiterates intentional v1 incompatibility. + +Confidence: high. + +### 9. The current release artifact needs a clean baseline before branching + +`package.json` publishes `dist/index.js`, but the checked-in `dist` does not match current source behavior. For example, the source contains `chat.params` stripping and newer after-hook dedupe behavior that are absent from the built artifact inspected during this research. + +This is not caused by v2, but it matters for migration: + +- A v2 port should not fork from behavior that exists only in source and not in the shipped artifact. +- Tests should exercise the packed artifact, not only `src`. +- Release CI builds before publish, but local E2E and repository inspection can still test stale `dist`. + +Establish and document the intended v1 behavior before extracting shared code. + +Confidence: high. + +## Recommended Compatibility Strategy + +### Decision + +Keep the existing npm package as the v1 product line. Publish the v2 beta port under a separate package name and exact versions. + +Suggested naming: + +- V1: `opencode-command-hooks` +- V2 beta: `opencode-command-hooks-v2` + +Why this is safest: + +- Existing bare v1 specs continue resolving to a v1-compatible artifact. +- Moving npm `latest` cannot accidentally break all unpinned v1 users. +- V2 users opt in explicitly and can roll back by removing one package and restoring the other. +- Each package can use independent dependencies, entrypoints, tests, and release cadence while the v2 API churns. +- Support communication is unambiguous. + +### Alternatives considered + +| Option | Benefit | Risk | Recommendation | +| --- | --- | --- | --- | +| Separate v2 package | Strongest isolation and rollback | Temporary package-name fragmentation | Recommended during beta and transition | +| Same package, v2 on `next`/`v2` dist-tag | Preserves one package name | A future `latest` change can break v1 users; mutable tags complicate reproducibility | Acceptable only with exact versions and strict release discipline | +| Same package, v2 major on `latest` | Conventional semver | OpenCode auto-installs bare specs; many users do not pin plugin versions | Do not do while v1 remains common | +| One dual-runtime root export | One artifact | Hosts do not select a major-specific export; loader contracts differ; older v1 loaders may reject descriptors | Do not use as the primary strategy | + +### Version policy + +- Keep npm `latest` for `opencode-command-hooks` on the supported v1 line. +- Publish v2 beta releases only as exact versions of the separate package. +- Pin `@opencode-ai/plugin` and other v2 packages to the exact matching beta build. +- Do not depend on `next`, `beta`, or `latest` ranges inside a production plugin release. +- Add host compatibility metadata when upstream confirms enforcement for the target host, but treat it as a guardrail rather than the compatibility mechanism. +- Never unpublish v1 versions needed for rollback. + +## Target Architecture + +Do not duplicate the entire plugin. Keep host-independent behavior shared and isolate host contracts. + +### Shared core + +Retain or extract only behavior that does not know about OpenCode: + +- Zod schemas and config validation. +- Global/project merge semantics. +- Hook matching. +- Template interpolation. +- Sequential command execution and output truncation. +- Non-blocking execution policy. + +### V1 adapter + +Own: + +- Legacy returned hook object. +- V1 event payload normalization. +- V1 SDK nested `path`/`body` calls. +- V1 toast and structured logging. +- Existing tool-args cache and fallback only as long as supported versions need them. + +### V2 adapter + +Own: + +- `Plugin.define({ id, setup })` default export. +- Imperative hook registration and cleanup. +- V2 tool event normalization. +- Event-stream background consumer and dedupe. +- Session lookup for missing agent context. +- Synthetic context injection. +- Explicit location/workspace and command cwd. +- Explicit unsupported-toast diagnostics until parity exists. + +Use the Promise v2 API unless Effect provides a concrete capability the plugin needs. The current codebase is Promise-based, and choosing Effect only for conformity would increase migration scope without improving behavior. + +## Draft Migration Plan + +### Gate 0: Watch upstream and freeze assumptions + +Actions: + +- Track the v2 plugin guide, `v2` branch, package exports, and migration page. +- Record one exact OpenCode 2 build and matching plugin/client package versions for every spike. +- Open or follow upstream questions for location access, server-side notifications/logging, synthetic delivery semantics, and the `request`/`context` hook-name mismatch. + +Exit criteria: + +- Tool before/after, event subscription, session synthetic, and location contracts are documented consistently with source for a pinned build. + +### Gate 1: Stabilize the v1 baseline + +Actions: + +- Rebuild `dist` and verify the packed package matches source. +- Add packed-artifact tests for current v1 behavior. +- Decide and document the actual support status of `session.end`, slash-command filters, and `tool.result` fallback. +- Pass runtime directory explicitly through the internal command/config APIs without changing v1 behavior. + +Exit criteria: + +- One reproducible v1 contract suite passes against the oldest and newest supported OpenCode 1 releases. +- `npm pack` contents are the artifact used by tests. + +### Gate 2: Introduce host ports + +Actions: + +- Define narrow internal ports for lifecycle input, location, injection, notification, and logging. +- Keep schemas, matching, templates, execution, and merge logic host-neutral. +- Move v1-specific caches and payload aliases behind the v1 adapter. + +Exit criteria: + +- Existing v1 behavior passes unchanged through the adapter boundary. +- No v2 dependency is required by the v1 package. + +### Gate 3: Build a pinned v2 prototype + +Actions: + +- Create a default v2 Promise plugin descriptor with a stable plugin ID. +- Register `execute.before` and `execute.after` hooks. +- Consume session events in a cancellable background task. +- Use direct v2 `event.input` in both phases; do not carry the v1 args cache. +- Resolve the plugin location deterministically and run commands with explicit cwd. +- Inject with `ctx.session.synthetic()` behind an adapter. +- Treat toast as unsupported with one clear diagnostic, not as a silent no-op. +- Catch all callback failures to preserve non-blocking tool execution. + +Exit criteria: + +- Prototype passes against one exact beta build. +- Before/after hooks, tool-argument filtering, session start/idle, injection, config reload, and cleanup have runtime evidence. +- A hook command failure never fails the intercepted OpenCode tool. + +### Gate 4: Add a dual-host contract matrix + +Minimum matrix: + +| Dimension | V1 | V2 beta | +| --- | --- | --- | +| Host executable | `opencode` | `opencode2` | +| Plugin install | packed v1 package | packed exact v2 package | +| Config shape | v1 `plugin` | v1 translated and native v2 `plugins` | +| Tool phases | before and after | before and after | +| Tool filters | name and args | name and args | +| Agent hooks | project/global, singular/plural dirs | translated legacy and preferred v2 dirs | +| Session lifecycle | created and idle | created and selected execution/idle mapping | +| Injection | v1 async prompt behavior | v2 synthetic behavior | +| Toast | supported | explicit unsupported status or supported replacement | +| Location | single project and nested cwd | two simultaneous locations/workspaces | +| Failures | nonzero shell, timeout, malformed config | same plus hook callback exception | +| Reload | config and plugin updates | config and plugin updates, cleanup, no duplicate hooks | + +Exit criteria: + +- No unexplained behavioral divergence. +- All expected degradations are documented and surfaced to users. +- Open v2 reload defects are fixed upstream or mitigated and tested. + +### Gate 5: Publish an opt-in v2 beta package + +Actions: + +- Publish `opencode-command-hooks-v2@` without changing the v1 package's `latest` tag. +- State the exact supported OpenCode 2 beta build. +- Provide side-by-side install, verification, and rollback steps. +- Require users to verify active plugin IDs with `opencode2 api get /api/plugin`. +- Collect issue reports with host version, plugin version, event type, and server logs. + +Exit criteria: + +- At least 20 representative successful canary runs across multiple projects. +- Zero tool-blocking plugin failures. +- No unresolved injection recursion, location, or duplicate-event defects. + +### Gate 6: Decide the stable package future + +After OpenCode 2 and its plugin API are stable, choose one: + +- Keep permanent `-v2` package separation while v1 remains maintained. +- Move v2 to a new major of `opencode-command-hooks`, but keep npm `latest` on the v1 line until users have clear pinning/migration guidance. +- Eventually move `latest` to v2 only after a declared migration window and adoption threshold. + +Do not make this decision during the upstream beta. + +## User Migration and Rollback + +### Side-by-side trial + +1. Keep `opencode` and `opencode-command-hooks` unchanged. +2. Install a pinned `opencode2` beta separately. +3. Use v1-shaped shared config initially so both hosts can read it. +4. Add the exact v2 plugin package only in an isolated v2 test config or disposable environment. +5. Verify plugin loading, model/provider credentials, agents, permissions, and hooks before regular use. + +### Production migration + +1. Record the exact working v1 OpenCode and plugin versions. +2. Remove the v1 plugin entry and add the exact v2 package entry in one controlled config change. +3. Verify active plugin ID, before/after execution, session injection, and project cwd. +4. Keep the old v1 config and package version available through at least one stable OpenCode 2 release cycle. + +### Rollback + +1. Stop using `opencode2` for the affected workflow. +2. Restore the exact prior v1 plugin spec and v1 config. +3. Restart OpenCode so cached plugin state and registrations are cleared. +4. Diagnose cache only if an exact package does not reload; cache deletion should not be the normal rollback mechanism. + +Rollback triggers: + +- A hook exception blocks a tool. +- Hook commands execute in the wrong directory. +- Injection triggers an unintended model turn or recursion. +- Duplicate lifecycle events execute hooks twice. +- Required toast behavior is silently lost. +- Plugin reload breaks unrelated sessions or tools. +- A beta service update causes hangs or unbounded retries. + +## Recommendations + +1. Do not port the production package until the v2 API is documented consistently with the beta source. +2. Keep the current npm package and `latest` tag v1-compatible. +3. Use a separate v2 beta package with exact OpenCode/plugin dependency pins. +4. Stabilize the packed v1 artifact before extracting shared code. +5. Separate host adapters from the schema/matching/execution core. +6. Use v2 tool hooks directly and avoid carrying v1 cache/fallback complexity forward. +7. Prototype injection with `session.synthetic`, but require runtime proof of delivery and recursion semantics. +8. Treat toast and structured logging as explicit parity gaps, not silent no-ops. +9. Make project location and shell cwd explicit before supporting the v2 background service. +10. Maintain a dual-host contract test matrix and a documented exact-version rollback path. + +## Open Questions + +- Will the stable hook be named `session.request`, `session.context`, or something else? +- What is the stable way for a server plugin to obtain its location and workspace directly? +- Will server plugins receive a supported logging and user-notification capability? +- What synthetic delivery policy is correct for injection during an active tool execution? +- Which v2 event should define the user-facing `session.idle` compatibility behavior? +- What are the stable built-in tool IDs and subagent input shapes? +- Are custom agent-frontmatter fields ignored, preserved, or forwarded into provider request options? +- Will plugin replacement become atomic and retain the last known good generation? +- What semver or compatibility guarantee will apply to the stable v2 plugin API? + +## Evidence and References + +### Primary current sources + +- [OpenCode 2 beta introduction](https://v2.opencode.ai/) - Beta status, warnings, `@next` installation, and `opencode2`. Retrieved with Firecrawl. +- [Migrate from V1](https://v2.opencode.ai/migrate-v1) - Intentional breaking changes, v1 config translation, plugin incompatibility, side-by-side guidance, and deferred migration details. Retrieved with Firecrawl. +- [OpenCode 2 plugin guide](https://v2.opencode.ai/build/plugins) - Current beta loader, setup model, context, transforms, tool hooks, version matching, and publication guidance. Retrieved with Firecrawl and corroborated with Context7. +- [OpenCode 1 plugin guide](https://opencode.ai/docs/plugins/) - Current stable returned-hooks API, injected client, event list, toast/logging, and install behavior. Retrieved with Firecrawl and Context7. +- [V2 plugin loader source](https://github.com/anomalyco/opencode/blob/v2/packages/core/src/plugin/supervisor.ts) - Default export validation, package loading, ordering, discovery, and reload flow. Retrieved with Firecrawl; verified in the upstream clone. +- [V2 Promise plugin contract](https://github.com/anomalyco/opencode/blob/v2/packages/plugin/src/v2/promise/plugin.ts) - `{ id, setup }`, context domains, and cleanup contract. Verified in the upstream clone; indexed by Context7. +- [V2 tool contract](https://github.com/anomalyco/opencode/blob/v2/packages/plugin/src/v2/effect/tool.ts) - Mutable before/after event fields and hook names. Retrieved with Firecrawl; verified in the upstream clone. +- [V2 session contract](https://github.com/anomalyco/opencode/blob/v2/packages/plugin/src/v2/effect/session.ts) - Session client subset and session hook surface. Verified in the upstream clone. +- [V2 host wiring](https://github.com/anomalyco/opencode/blob/v2/packages/core/src/plugin/host.ts) - Implemented event, tool, session, and location-scoped behavior. Verified in the upstream clone. +- [V2 session event schema](https://github.com/anomalyco/opencode/blob/v2/packages/schema/src/session-event.ts) - Durable execution and synthetic event contracts. Verified in the upstream clone. +- [V2 API design map](https://github.com/anomalyco/opencode/blob/dev/specs/v2/api.html) - Revised client/server operation model and session-pinned context. Retrieved with Firecrawl. +- [V2 core instructions](https://github.com/anomalyco/opencode/blob/dev/specs/v2/instructions.md) - Architectural direction toward typed, domain-oriented, sequential plugin hooks. Retrieved with Firecrawl. + +### Stability and risk sources + +- [PR #37540: restore plugin list diagnostics](https://github.com/anomalyco/opencode/pull/37540) - Reiterates intentional v1 plugin incompatibility and shows same-day beta repair. Retrieved with Firecrawl. +- [Issue #37533: v2 plugin list and loading report](https://github.com/anomalyco/opencode/issues/37533) - Maintainer comment that v1 plugins are incompatible with v2. Retrieved through GitHub CLI after Firecrawl discovery. +- [Issue #35963: isolate invalid plugin tools during reload](https://github.com/anomalyco/opencode/issues/35963) - Open plugin validation, atomic replacement, and location-wide failure risk. Retrieved with Firecrawl. +- [Issue #37429: config directory changes do not trigger reload](https://github.com/anomalyco/opencode/issues/37429) - Open command/agent/plugin reload defect. Retrieved with Firecrawl. +- [Issue #37521: self-update can orphan the service](https://github.com/anomalyco/opencode/issues/37521) - Open background-service hang and recovery risk. Retrieved with Firecrawl. +- [Issue #34832: v2 pre-beta event/schema audit](https://github.com/anomalyco/opencode/issues/34832) - Explicit concern about durable event names and migration-sensitive contracts. Retrieved with Firecrawl. +- [Issue #35016: execution/retry/error event redesign](https://github.com/anomalyco/opencode/issues/35016) - Direction replacing ambiguous status with typed execution lifecycle events. Retrieved with Firecrawl. +- [Issue #33896: v2 plugin skill discovery](https://github.com/anomalyco/opencode/issues/33896) - Evidence that published v2 plugin capabilities are still receiving compatibility fixes. Retrieved with Firecrawl and GitHub CLI. +- [Issue #7641: plugin and v2 SDK type mismatch](https://github.com/anomalyco/opencode/issues/7641) - Historical evidence of nested v1 versus flattened v2 client types. Retrieved through GitHub CLI after Firecrawl discovery. +- [OpenCode releases](https://github.com/anomalyco/opencode/releases) - Stable release status. Retrieved with Firecrawl and GitHub CLI. +- [npm registry: `@opencode-ai/plugin`](https://www.npmjs.com/package/@opencode-ai/plugin) - Stable and prerelease package channels. Queried with npm after Firecrawl discovery. + +## Limitations + +- OpenCode 2 is changing daily; this report is a point-in-time analysis for 2026-07-17. +- Some upstream beta documentation already differs from the current `v2` branch source. +- A V2 adapter, packed package test, and pinned-host E2E smoke test are now present. The host smoke currently reproduces the upstream plugin-loading blocker described above. +- No claim here should override a later stable OpenCode 2 migration guide or package contract. + +## Follow-ups + +- [x] Recheck the beta guide, exact published package types, and `v2` source before implementation. +- [ ] Obtain upstream clarification on session hook naming, location access, toast/logging, and synthetic delivery. +- [x] Add packed V2 artifact verification while preserving the V1 regression suite. +- [x] Use `opencode-command-hooks-v2` as the isolated beta package name. +- [ ] Confirm npm ownership before publishing any prerelease. +- [ ] Enable the pinned host E2E gate after upstream plugin loading succeeds. +- [ ] Expand to a dual-host runtime matrix when the blocking host defects are resolved. diff --git a/README.md b/README.md index d60b03b..dd8cfbe 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ hooks: - [Features](#features) - [Installation](#installation) +- [OpenCode 2 Beta](#opencode-2-beta) - [Configuration](#configuration) - [Examples](#examples) - [Template Placeholders](#template-placeholders) @@ -170,6 +171,24 @@ Add to your `opencode.json`: } ``` +## OpenCode 2 Beta + +OpenCode 2 uses a different plugin API, so the V1 package above does not load there. The opt-in beta artifact is built separately and pins the exact OpenCode 2 plugin contract it supports. It is not published while the pinned host loading test remains blocked upstream; after that gate passes, its config will be: + +```jsonc +{ + "plugins": ["opencode-command-hooks-v2@0.1.0-beta.0"] +} +``` + +Version `0.1.0-beta.0` targets `@opencode-ai/cli@0.0.0-next-15800`. Keep versions exact while the upstream API is beta, and verify the plugin ID with `opencode2 api get /api/plugin`. + +The V2 adapter supports tool before/after hooks, `toolArgs` filters, agent frontmatter hooks, session start/idle hooks, explicit project-directory execution, and injection through synthetic context. Existing V1 config vocabulary remains valid: the V2 `subagent` tool and its `agent` argument are normalized to `task` and `subagent_type` for matching. + +V2 server plugins cannot currently show TUI toasts. When a matched hook requests one, the adapter emits one diagnostic and continues command execution and injection. Synthetic injections use `resume: false` so an idle hook does not create an unsolicited model turn. + +Run the pinned host smoke test with `npm run test:v2:e2e`. Keep the V1 package available for rollback while OpenCode 2 remains beta. + ## Configuration ### Config Locations diff --git a/bun.lock b/bun.lock index 22fbbd9..e06e7b6 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-15800", "@types/bun": "latest", "@types/js-yaml": "^4.0.5", "eslint": "^9.39.1", @@ -24,6 +25,14 @@ }, }, "packages": { + "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], + + "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + + "@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -50,10 +59,52 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], + + "@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-next-15800", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-15800", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-beta.98", "google-auth-library": "10.5.0" } }, "sha512-UzbIVWqpH7KjS42VIpmeN9artdf6iQ/RGvYw0lraNlm4X7XnRn87nh/3mNdpqTrFDVzkbJyrxNvB4pIXBRc36Q=="], + + "@opencode-ai/client": ["@opencode-ai/client@0.0.0-next-15800", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-next-15800", "@opencode-ai/schema": "0.0.0-next-15800" }, "peerDependencies": { "effect": "4.0.0-beta.98" }, "optionalPeers": ["effect"] }, "sha512-tFxRkQuWwfk/fdIECXkEdJUi2z09jWTFFbWBpgT233IWixjjhiKH2zfjLtpfkW8kTb4IPCs0vPWgzEvUWTT+Uw=="], + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.218", "", { "dependencies": { "@opencode-ai/sdk": "1.0.218", "zod": "4.1.8" } }, "sha512-7fsCx2SIu7TRNn3cdHiaIU7r1/XgnVTBufGnlezH+OZwHP+gILBjqtuMn+1L0e1npnl12CvS3FRNvg/3jr6yhA=="], + "@opencode-ai/plugin-v2": ["@opencode-ai/plugin@0.0.0-next-15800", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-next-15800", "@opencode-ai/client": "0.0.0-next-15800", "@opencode-ai/schema": "0.0.0-next-15800", "@opencode-ai/sdk": "0.0.0-next-15800", "@standard-schema/spec": "^1.1.0", "effect": "4.0.0-beta.98", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-YHt9uHjNke0UH8W2kVxvdaxLHA9/02ZTh9hldPhfh3D42QAAwEeJJIqKgpFc6Jf+7nmCNSjQEfzpEYzlrUQliA=="], + + "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-next-15800", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-15800", "effect": "4.0.0-beta.98" } }, "sha512-epbGuT+R5ly1MWG/5wUo/i51j1PK2+RsG6rAiGAw3nWOSObyWw4Y2YaXRO4bvIGF5+0bJh9MAJD/jKgNn2f+7w=="], + + "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-next-15800", "", { "dependencies": { "effect": "4.0.0-beta.98" } }, "sha512-QiF9CmntEVIthT3n0oAtdxzq/9C/efqHWlk3y4yiF9u2S1Znj/KmGtyezYylvAlEdUgZTXoMpu7sib2iXLCf5g=="], + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.218", "", {}, "sha512-c6ss6UPAMskSVQUecuhNvPLFngyVh2Os9o0kpVjoqJJ16HXhzjVSk5axgh3ueQrfP5aZfg5o6l6srmjuCTPNnQ=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@smithy/core": ["@smithy/core@3.29.5", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ=="], + + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="], + + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + + "@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="], + + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.4.10", "", { "dependencies": { "@smithy/core": "^3.29.5", "tslib": "^2.6.2" } }, "sha512-DcPJ8L79nxi/6QA29F9m+OcIR9/KIXN80i5YMQEVy3fZUUgsE9d5HRj5WVKt9id9XucnnxlSpChg8qNkRApPPg=="], + + "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.4.10", "", { "dependencies": { "@smithy/core": "^3.29.5", "tslib": "^2.6.2" } }, "sha512-1LrWRBgKOEsMIFVPpAHr1aCj0f+C4hyMynVu4fkWvfvA/CHw6lny74Y6Ep9YJDt4XM837jUDi9pLAV2R5ebGWg=="], + + "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -88,16 +139,28 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], @@ -112,10 +175,22 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + + "effect": ["effect@4.0.0-beta.98", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-oz+bsG5h+6RNrw4t5GMfQrk/xBS8ROoqkYsuvRhBr5O7mCOrpvH/hbw+QrDzvKIpX4HJClwm86F94c87W0sJxg=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], @@ -134,6 +209,10 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], @@ -142,92 +221,170 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + + "gaxios": ["gaxios@7.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg=="], + + "gcp-metadata": ["gcp-metadata@8.1.3", "", { "dependencies": { "gaxios": "7.1.3", "google-logging-utils": "1.1.3", "json-bigint": "^1.0.0" } }, "sha512-ziTrzUhhpL9Zk5k0HHzgP/KIpWDJT0VMBC/ynt/QIBvTW+UUcSivQRl6VlwTf/EilDxtSWklHoRsKy1c4k+59w=="], + + "glob": ["glob@10.5.0", "", { "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" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], + + "google-logging-utils": ["google-logging-utils@1.1.4", "", {}, "sha512-LXxE6ND+JHhDPYtPFt3oeWrjxFPrnRZCZ4At6lpbi1ZDSAN7bmGET9s53vvARbxT93p+xpeDUbc/nCR4C+7vcw=="], + + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], + + "multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="], + "ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -238,24 +395,68 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], + + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@opencode-ai/plugin-v2/@opencode-ai/sdk": ["@opencode-ai/sdk@0.0.0-next-15800", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-MtdELCZl50RNM3W/etlU18FxgCVt+i17FE+QNAFidYmr0bQK/FXeIjtZ+wlZ867kEh0+aZoKur9VemA3ExyqDA=="], + + "@opencode-ai/plugin-v2/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "gcp-metadata/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + + "gcp-metadata/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], } } diff --git a/package.json b/package.json index b879efd..a8cd352 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,12 @@ "types" ], "scripts": { - "build": "tsc", + "build": "rm -rf dist && tsc -p tsconfig.build.json", + "build:v2": "bun build src/v2.ts --outfile packages/v2/dist/index.js --target=bun --external @opencode-ai/plugin/v2", + "build:all": "npm run build && npm run build:v2", "test": "bun test", + "test:v2": "bun test tests/v2.plugin.test.ts tests/v2.package.test.ts", + "test:v2:e2e": "OPENCODE2_E2E=1 bun test tests/e2e.v2.test.ts", "lint": "eslint src tests --ext .ts", "typecheck": "tsc --noEmit", "ci:check": "npm run lint && npm run typecheck && (command -v bun >/dev/null 2>&1 && bun test || echo \"bun not found; skipping tests\")", @@ -54,6 +58,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-15800", "@types/bun": "latest", "@types/js-yaml": "^4.0.5", "eslint": "^9.39.1", diff --git a/packages/v2/LICENSE b/packages/v2/LICENSE new file mode 100644 index 0000000..18b4ee6 --- /dev/null +++ b/packages/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Shane Bishop + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/v2/README.md b/packages/v2/README.md new file mode 100644 index 0000000..f194bb4 --- /dev/null +++ b/packages/v2/README.md @@ -0,0 +1,15 @@ +# opencode-command-hooks-v2 + +Beta package for OpenCode 2. It targets `@opencode-ai/cli@0.0.0-next-15800` and uses the same `.opencode/command-hooks.jsonc` and agent frontmatter configuration as the V1 package. + +```jsonc +{ + "plugins": ["opencode-command-hooks-v2@0.1.0-beta.0"] +} +``` + +Tool before/after hooks, argument filters, agent hooks, session start/idle hooks, shell execution, and synthetic context injection are supported. Commands run in the session's project directory. + +OpenCode 2 server plugins cannot currently show TUI toasts. A toast setting produces one diagnostic while command execution and injection continue. + +Keep the V1 package and configuration available for rollback while OpenCode 2 remains beta. Verify loading with `opencode2 api get /api/plugin`. diff --git a/packages/v2/package.json b/packages/v2/package.json new file mode 100644 index 0000000..86e7c8c --- /dev/null +++ b/packages/v2/package.json @@ -0,0 +1,40 @@ +{ + "name": "opencode-command-hooks-v2", + "version": "0.1.0-beta.0", + "description": "OpenCode 2 beta adapter for declarative command hooks", + "type": "module", + "main": "dist/index.js", + "exports": "./dist/index.js", + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "scripts": { + "prepack": "npm --prefix ../.. run build:v2" + }, + "keywords": [ + "opencode", + "opencode2", + "plugin", + "hooks", + "shell" + ], + "author": "Shane Bishop <71288697+shanebishop1@users.noreply.github.com>", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/shanebishop1/opencode-command-hooks.git", + "directory": "packages/v2" + }, + "bugs": { + "url": "https://github.com/shanebishop1/opencode-command-hooks/issues" + }, + "homepage": "https://github.com/shanebishop1/opencode-command-hooks#opencode-2-beta", + "dependencies": { + "@opencode-ai/plugin": "0.0.0-next-15800" + }, + "engines": { + "bun": ">=1.0.0" + } +} diff --git a/src/config/agent.ts b/src/config/agent.ts index cd5b041..0b6128e 100644 --- a/src/config/agent.ts +++ b/src/config/agent.ts @@ -62,7 +62,10 @@ const projectDirs = (startDir: string): string[] => { * // Or: null (if neither exists) * ``` */ -export async function resolveAgentPath(agentName: string): Promise { +export async function resolveAgentPath( + agentName: string, + startDir = process.cwd(), +): Promise { // Validate agent name to prevent directory traversal if (!agentName || agentName.includes("/") || agentName.includes("..")) { logger.debug(`Invalid agent name: ${agentName}`); @@ -72,7 +75,7 @@ export async function resolveAgentPath(agentName: string): Promise { +export async function loadAgentConfig( + agentName: string, + startDir = process.cwd(), +): Promise { // Resolve the agent path - const agentPath = await resolveAgentPath(agentName); + const agentPath = await resolveAgentPath(agentName, startDir); if (!agentPath) { logger.debug(`No agent file found for: ${agentName}, returning empty config`); diff --git a/src/config/global.ts b/src/config/global.ts index 2d7bd65..1fcf762 100644 --- a/src/config/global.ts +++ b/src/config/global.ts @@ -15,7 +15,8 @@ import { mergeConfigs } from "./merge.js"; import { join, dirname } from "path"; import { homedir } from "os"; import { stat } from "fs/promises"; -import JsoncParser, { type ParseError } from "jsonc-parser"; +import * as JsoncParser from "jsonc-parser"; +import type { ParseError } from "jsonc-parser"; import { logger } from "../logging.js"; /** @@ -265,12 +266,12 @@ const loadConfigFromPath = async ( * * @returns Promise resolving to GlobalConfigResult */ -export const loadGlobalConfig = async (): Promise => { +export const loadGlobalConfig = async (startDir = process.cwd()): Promise => { try { - logger.debug(`loadGlobalConfig: starting search from: ${process.cwd()}`); + logger.debug(`loadGlobalConfig: starting search from: ${startDir}`); // Step 1: Load project config first (to check ignoreGlobalConfig flag) - const projectConfigPath = await findProjectConfigFile(process.cwd()); + const projectConfigPath = await findProjectConfigFile(startDir); const projectResult = projectConfigPath ? await loadConfigFromPath(projectConfigPath, "project") : { config: emptyConfig(), error: null }; diff --git a/src/execution/shell.ts b/src/execution/shell.ts index 9740f99..030bcb7 100644 --- a/src/execution/shell.ts +++ b/src/execution/shell.ts @@ -48,7 +48,7 @@ const truncateText = (text: string | undefined, limit: number): string => { */ export async function executeCommand( command: string, - options?: { truncateOutput?: number } + options?: { truncateOutput?: number; cwd?: string } ): Promise { const truncateLimit = options?.truncateOutput ?? DEFAULT_TRUNCATE_LIMIT const hookId = "command" // Will be set by caller @@ -60,7 +60,7 @@ export async function executeCommand( try { // Execute command using Bun's $ template literal with nothrow to prevent throwing on non-zero exit // We need to use dynamic template literal evaluation - const result = await executeShellCommand(command) + const result = await executeShellCommand(command, options?.cwd) const stdout = truncateText(result.stdout, truncateLimit) const stderr = truncateText(result.stderr, truncateLimit) @@ -114,7 +114,7 @@ export async function executeCommand( export async function executeCommands( commands: string | string[], hookId: string, - options?: { truncateOutput?: number } + options?: { truncateOutput?: number; cwd?: string } ): Promise { const truncateLimit = options?.truncateOutput ?? DEFAULT_TRUNCATE_LIMIT const commandArray = Array.isArray(commands) ? commands : [commands] @@ -131,7 +131,7 @@ export async function executeCommands( logger.debug(`[${hookId}] Executing: ${command}`) } - const result = await executeShellCommand(command) + const result = await executeShellCommand(command, options?.cwd) const stdout = truncateText(result.stdout, truncateLimit) const stderr = truncateText(result.stderr, truncateLimit) @@ -175,13 +175,16 @@ export async function executeCommands( * @returns Object with stdout, stderr, and exitCode */ const executeShellCommand = async ( - command: string + command: string, + cwd?: string, ): Promise<{ stdout: string; stderr: string; exitCode: number }> => { try { // Use Bun's $ template literal to execute the command // The nothrow() method prevents throwing on non-zero exit codes // The quiet() method suppresses output and returns Buffers - const result = await Bun.$`${{ raw: command }}`.nothrow().quiet() + const shell = Bun.$`${{ raw: command }}` + if (cwd) shell.cwd(cwd) + const result = await shell.nothrow().quiet() // Extract stdout and stderr as text // result.stdout and result.stderr are Buffers, convert to string diff --git a/src/executor.ts b/src/executor.ts index d25d5b7..da29efb 100644 --- a/src/executor.ts +++ b/src/executor.ts @@ -15,7 +15,6 @@ * Errors are logged and optionally injected into the session, but never thrown. */ -import type { OpencodeClient } from "@opencode-ai/sdk" import type { ToolHook, SessionHook, @@ -26,6 +25,19 @@ import { executeCommands } from "./execution/shell.js" import { interpolateTemplate } from "./execution/template.js" import { logger } from "./logging.js" +export interface HookToast { + title?: string + message: string + variant?: "info" | "success" | "warning" | "error" + duration?: number +} + +export interface HookHost { + cwd: string + inject: (sessionId: string, message: string, hookId: string) => Promise + toast: (toast: HookToast) => Promise +} + /** * Check if a value matches a pattern (string, array of strings, or wildcard) */ @@ -138,7 +150,7 @@ const formatErrorMessage = (hookId: string, error: unknown): string => { * @returns Promise that resolves when toast is shown */ const showToast = async ( - client: OpencodeClient, + host: HookHost, title: string | undefined, message: string, variant: "info" | "success" | "warning" | "error" = "info", @@ -148,14 +160,7 @@ const showToast = async ( const finalTitle = title || "OpenCode Command Hook" logger.debug(`Showing toast: title="${finalTitle}", message="${message.substring(0, 50)}...", variant="${variant}", duration=${duration}`) - await client.tui.showToast({ - body: { - title: finalTitle, - message, - variant, - duration, - }, - }) + await host.toast({ title: finalTitle, message, variant, duration }) logger.info(`[toast] ${finalTitle}: ${message}`) } catch (error) { @@ -178,19 +183,15 @@ const showToast = async ( * @returns Promise that resolves when message is injected */ const injectMessage = async ( - client: OpencodeClient, + host: HookHost, sessionId: string, - message: string + message: string, + hookId: string, ): Promise => { try { logger.debug(`Injecting message into session ${sessionId}`) - await client.session.promptAsync({ - path: { id: sessionId }, - body: { - parts: [{ type: "text", text: message }], - }, - }) + await host.inject(sessionId, message, hookId) logger.info(`[inject] Message injected into session ${sessionId}: ${message.substring(0, 100)}${message.length > 100 ? '...' : ''}`) } catch (error) { @@ -217,7 +218,7 @@ const injectMessage = async ( const executeHook = async ( hook: ToolHook | SessionHook, context: HookExecutionContext, - client: OpencodeClient, + host: HookHost, truncationLimit?: number ): Promise => { const hookType = isToolHook(hook) ? "tool" : "session" @@ -237,7 +238,7 @@ const executeHook = async ( ? await executeCommands( hook.run, hook.id, - truncationLimit !== undefined ? { truncateOutput: truncationLimit } : undefined + { truncateOutput: truncationLimit, cwd: host.cwd } ) : [] @@ -260,7 +261,7 @@ const executeHook = async ( // If inject is configured, prepare and inject the message if (hook.inject) { const message = interpolateTemplate(hook.inject, templateContext) - await injectMessage(client, context.sessionId, message) + await injectMessage(host, context.sessionId, message, hook.id) } // If toast is configured, interpolate and show toast notification @@ -269,7 +270,7 @@ const executeHook = async ( const toastMessage = interpolateTemplate(hook.toast.message, templateContext) await showToast( - client, + host, toastTitle, toastMessage, hook.toast.variant || "info", @@ -281,7 +282,7 @@ const executeHook = async ( logger.error(errorMessage) try { - await injectMessage(client, context.sessionId, errorMessage) + await injectMessage(host, context.sessionId, errorMessage, hook.id) } catch (injectionError) { const injectionErrorMsg = injectionError instanceof Error @@ -342,7 +343,7 @@ const isToolHook = (hook: ToolHook | SessionHook): hook is ToolHook => { export async function executeHooks( hooks: (ToolHook | SessionHook)[], context: HookExecutionContext, - client: OpencodeClient, + host: HookHost, truncationLimit?: number ): Promise { try { @@ -353,7 +354,7 @@ export async function executeHooks( // Execute each hook for (const hook of hooks) { try { - await executeHook(hook, context, client, truncationLimit) + await executeHook(hook, context, host, truncationLimit) } catch (error) { // Catch errors from individual hook execution and continue // (errors are already logged and injected by the hook execution functions) diff --git a/src/index.ts b/src/index.ts index da99b86..3af0e30 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin" import type { Config, OpencodeClient } from "@opencode-ai/sdk" import type { CommandHooksConfig, HookExecutionContext } from "./types/hooks.js" import { createLogger, setGlobalLogger, logger } from "./logging.js" -import { executeHooks, filterSessionHooks, filterToolHooks } from "./executor.js" +import { executeHooks, filterSessionHooks, filterToolHooks, type HookHost } from "./executor.js" import { normalizeString } from "./utils.js" import { loadGlobalConfig } from "./config/global.js" import { loadAgentConfig } from "./config/agent.js" @@ -131,7 +131,8 @@ const handleSessionEvent = async ( eventType: "session.created" | "session.idle", sessionId: string | undefined, agent: string | undefined, - client: OpencodeClient + client: OpencodeClient, + host: HookHost, ): Promise => { if (!sessionId) { logger.debug(`${eventType} event missing session ID`) @@ -143,7 +144,7 @@ const handleSessionEvent = async ( } try { - const { config: globalConfig, error: globalConfigError } = await loadGlobalConfig() + const { config: globalConfig, error: globalConfigError } = await loadGlobalConfig(host.cwd) await notifyConfigError(globalConfigError, sessionId, client) const markdownConfig = { tool: [], session: [] } @@ -163,7 +164,7 @@ const handleSessionEvent = async ( agent: agent || "unknown", } - await executeHooks(matchedHooks, context, client, mergedConfig.truncationLimit) + await executeHooks(matchedHooks, context, host, mergedConfig.truncationLimit) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) logger.error(`Error handling ${eventType} event: ${errorMessage}`) @@ -177,7 +178,8 @@ const handleToolExecutionHook = async ( phase: "before" | "after", input: { tool: string; sessionID: string; callID?: string }, toolArgs: Record | undefined, - client: OpencodeClient + client: OpencodeClient, + host: HookHost, ): Promise => { if (phase === "before") { storeToolArgs(input.callID, toolArgs) @@ -188,7 +190,7 @@ const handleToolExecutionHook = async ( } try { - const { config: globalConfig, error: globalConfigError } = await loadGlobalConfig() + const { config: globalConfig, error: globalConfigError } = await loadGlobalConfig(host.cwd) await notifyConfigError(globalConfigError, input.sessionID, client) let agentConfig: CommandHooksConfig = { tool: [], session: [] } @@ -198,7 +200,7 @@ const handleToolExecutionHook = async ( subagentType = normalizeString(toolArgs.subagent_type) || undefined if (subagentType) { logger.debug(`Detected task tool call with subagent_type: ${subagentType}`) - agentConfig = await loadAgentConfig(subagentType) + agentConfig = await loadAgentConfig(subagentType, host.cwd) } } @@ -222,7 +224,7 @@ const handleToolExecutionHook = async ( toolArgs, } - await executeHooks(matchedHooks, context, client, mergedConfig.truncationLimit) + await executeHooks(matchedHooks, context, host, mergedConfig.truncationLimit) } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) logger.error(`Error handling tool.execute.${phase}: ${errorMessage}`) @@ -253,6 +255,21 @@ const handleToolExecutionHook = async ( export const CommandHooksPlugin: Plugin = async ({ client }) => { const clientLogger = createLogger(client) setGlobalLogger(clientLogger) + const typedClient = client as OpencodeClient + const host: HookHost = { + cwd: process.cwd(), + inject: async (sessionId, message) => { + await typedClient.session.promptAsync({ + path: { id: sessionId }, + body: { parts: [{ type: "text", text: message }] }, + }) + }, + toast: async ({ title, message, variant, duration }) => { + await typedClient.tui.showToast({ + body: { title, message, variant: variant ?? "info", duration }, + }) + }, + } try { logger.info("Initializing OpenCode Command Hooks plugin...") @@ -295,7 +312,7 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { const sessionId = info?.id ? normalizeString(info.id) : undefined const agent = normalizeString(event.properties?.agent) - await handleSessionEvent("session.created", sessionId, agent, client as OpencodeClient) + await handleSessionEvent("session.created", sessionId, agent, typedClient, host) } // Handle session.idle event @@ -305,7 +322,7 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { const sessionId = normalizeString(event.properties?.sessionID) const agent = normalizeString(event.properties?.agent) - await handleSessionEvent("session.idle", sessionId, agent, client as OpencodeClient) + await handleSessionEvent("session.idle", sessionId, agent, typedClient, host) } // Backward-compat fallback for older OpenCode event streams. @@ -341,7 +358,8 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { "after", { tool: toolName, sessionID: sessionId, callID: callId }, storedToolArgs, - client as OpencodeClient, + typedClient, + host, ) } }, @@ -361,7 +379,7 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { `Tool args: ${JSON.stringify(output.args)}` ) - await handleToolExecutionHook("before", input, output.args, client as OpencodeClient) + await handleToolExecutionHook("before", input, output.args, typedClient, host) }, /** @@ -383,7 +401,7 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { } const storedToolArgs = getToolArgs(input.callID) - await handleToolExecutionHook("after", input, storedToolArgs, client as OpencodeClient) + await handleToolExecutionHook("after", input, storedToolArgs, typedClient, host) }, } diff --git a/src/v2.ts b/src/v2.ts new file mode 100644 index 0000000..adbe61e --- /dev/null +++ b/src/v2.ts @@ -0,0 +1,4 @@ +import { Plugin } from "@opencode-ai/plugin/v2" +import { createV2Plugin } from "./v2/plugin.js" + +export default Plugin.define(createV2Plugin()) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts new file mode 100644 index 0000000..98ebda9 --- /dev/null +++ b/src/v2/plugin.ts @@ -0,0 +1,247 @@ +import type { CommandHooksConfig, HookExecutionContext } from "../types/hooks.js" +import { loadGlobalConfig } from "../config/global.js" +import { loadAgentConfig } from "../config/agent.js" +import { mergeConfigs } from "../config/merge.js" +import { executeHooks, filterSessionHooks, filterToolHooks, type HookHost } from "../executor.js" +import { normalizeString } from "../utils.js" + +type V2ToolEvent = { + tool: string + sessionID: string + callID?: string + agent?: string + input: unknown +} + +type V2Event = { + id?: string + type: string + data?: Record + location?: { directory?: string; workspaceID?: string } +} + +type V2SessionInfo = { + id: string + agent?: string + location: { directory: string; workspaceID?: string } +} + +export interface V2Context { + tool: { + hook: ( + name: "execute.before" | "execute.after", + callback: (event: V2ToolEvent) => Promise | void, + ) => Promise<{ dispose: () => Promise }> + } + event: { + subscribe: () => AsyncIterable + } + session: { + get: (input: { sessionID: string }) => Promise + synthetic: (input: { + sessionID: string + text: string + description?: string + metadata?: Record + resume?: boolean + }) => Promise + } +} + +export interface V2Plugin { + id: string + setup: (context: V2Context) => Promise<(() => Promise) | void> +} + +const emptyConfig = (): CommandHooksConfig => ({ tool: [], session: [] }) + +const asRecord = (value: unknown): Record | undefined => + typeof value === "object" && value !== null && !Array.isArray(value) + ? value as Record + : undefined + +const toolContext = (event: V2ToolEvent): { + toolName: string + toolArgs: Record | undefined + callingAgent: string | undefined + agentConfigName: string | undefined +} => { + const originalArgs = asRecord(event.input) + if (event.tool !== "subagent") { + return { + toolName: event.tool, + toolArgs: originalArgs, + callingAgent: normalizeString(event.agent) || undefined, + agentConfigName: undefined, + } + } + + const agent = normalizeString(originalArgs?.agent) || undefined + return { + // Keep the public command-hooks vocabulary compatible with V1. + toolName: "task", + toolArgs: originalArgs ? { ...originalArgs, subagent_type: agent } : originalArgs, + callingAgent: agent, + agentConfigName: agent, + } +} + +const diagnostic = (message: string, error?: unknown): void => { + const detail = error instanceof Error ? error.message : error === undefined ? "" : String(error) + console.error(`[opencode-command-hooks-v2] ${message}${detail ? `: ${detail}` : ""}`) +} + +export const createV2Plugin = (): V2Plugin => ({ + id: "opencode-command-hooks.v2", + setup: async (ctx) => { + const notifiedConfigErrors = new Set() + const handledEvents = new Set() + let warnedToastUnsupported = false + let stopped = false + + const notifyConfigError = (error: string | null, directory: string): void => { + if (!error) return + const key = `${directory}:${error}` + if (notifiedConfigErrors.has(key)) return + notifiedConfigErrors.add(key) + diagnostic(error) + } + + const createHost = (directory: string): HookHost => ({ + cwd: directory, + inject: async (sessionId, message, hookId) => { + await ctx.session.synthetic({ + sessionID: sessionId, + text: message, + description: "opencode-command-hooks", + metadata: { hookId }, + resume: false, + }) + }, + toast: async () => { + if (warnedToastUnsupported) return + warnedToastUnsupported = true + console.warn( + "[opencode-command-hooks-v2] Toast notifications are not available to V2 server plugins; hook execution and injection will continue.", + ) + }, + }) + + const sessionInfo = async ( + sessionID: string, + fallback?: { directory?: string; agent?: string }, + ): Promise<{ directory: string; agent: string | undefined }> => { + try { + const session = await ctx.session.get({ sessionID }) + return { + directory: session.location.directory, + agent: normalizeString(session.agent) || normalizeString(fallback?.agent) || undefined, + } + } catch (error) { + if (fallback?.directory) { + return { + directory: fallback.directory, + agent: normalizeString(fallback.agent) || undefined, + } + } + throw error + } + } + + const handleTool = async (phase: "before" | "after", event: V2ToolEvent): Promise => { + try { + const resolved = await sessionInfo(event.sessionID, { agent: event.agent }) + const normalized = toolContext(event) + const { config: globalConfig, error } = await loadGlobalConfig(resolved.directory) + notifyConfigError(error, resolved.directory) + + let agentConfig = emptyConfig() + if (normalized.agentConfigName) { + agentConfig = await loadAgentConfig(normalized.agentConfigName, resolved.directory) + } + + const { config } = mergeConfigs(globalConfig, agentConfig) + const hooks = filterToolHooks(config.tool ?? [], { + phase, + toolName: normalized.toolName, + callingAgent: normalized.callingAgent, + slashCommand: undefined, + toolArgs: normalized.toolArgs, + }) + const context: HookExecutionContext = { + sessionId: event.sessionID, + agent: normalized.callingAgent ?? resolved.agent ?? "unknown", + tool: normalized.toolName, + callId: event.callID, + toolArgs: normalized.toolArgs, + } + await executeHooks(hooks, context, createHost(resolved.directory), config.truncationLimit) + } catch (error) { + diagnostic(`Failed to handle tool.execute.${phase}`, error) + } + } + + const handleSession = async (event: V2Event): Promise => { + if (event.type !== "session.created" && event.type !== "session.idle") return + if (event.id && handledEvents.has(event.id)) return + if (event.id) { + handledEvents.add(event.id) + if (handledEvents.size > 1000) { + const oldest = handledEvents.values().next().value + if (oldest) handledEvents.delete(oldest) + } + } + + const sessionID = normalizeString(event.data?.sessionID) + if (!sessionID) return + + try { + const resolved = await sessionInfo(sessionID, { + directory: normalizeString(event.location?.directory) || undefined, + agent: normalizeString(event.data?.agent) || undefined, + }) + const { config, error } = await loadGlobalConfig(resolved.directory) + notifyConfigError(error, resolved.directory) + const hooks = filterSessionHooks(config.session ?? [], { + event: event.type, + agent: resolved.agent, + }) + await executeHooks( + hooks, + { sessionId: sessionID, agent: resolved.agent ?? "unknown" }, + createHost(resolved.directory), + config.truncationLimit, + ) + } catch (error) { + diagnostic(`Failed to handle ${event.type}`, error) + } + } + + const registrations = [ + await ctx.tool.hook("execute.before", event => handleTool("before", event)), + await ctx.tool.hook("execute.after", event => handleTool("after", event)), + ] + + let iterator: AsyncIterator | undefined + const eventTask = (async () => { + try { + iterator = ctx.event.subscribe()[Symbol.asyncIterator]() + while (!stopped) { + const next = await iterator.next() + if (next.done) break + await handleSession(next.value) + } + } catch (error) { + if (!stopped) diagnostic("Event subscription failed", error) + } + })() + + return async () => { + stopped = true + const disposed = Promise.allSettled(registrations.map(registration => registration.dispose())) + await iterator?.return?.() + await eventTask + await disposed + } + }, +}) diff --git a/tests/agent.config.test.ts b/tests/agent.config.test.ts index 89b7c78..c112998 100644 --- a/tests/agent.config.test.ts +++ b/tests/agent.config.test.ts @@ -76,6 +76,15 @@ describe("Agent Configuration", () => { } }); + it("should resolve from an explicit project directory", async () => { + const agentPath = join(testProjectDir, ".opencode", "agents", "located-agent.md"); + await writeFile(agentPath, "---\ndescription: Located agent\n---\n# Located agent content"); + + const result = await resolveAgentPath("located-agent", testProjectDir); + + expect(realpathSync(result!)).toBe(realpathSync(agentPath)); + }); + it("should prioritize project-level plural over project-level singular", async () => { const pluralPath = join(testProjectDir, ".opencode", "agents", "project-priority.md"); const singularPath = join(testProjectDir, ".opencode", "agent", "project-priority.md"); diff --git a/tests/config.global.test.ts b/tests/config.global.test.ts index 8c8bd04..7301d1b 100644 --- a/tests/config.global.test.ts +++ b/tests/config.global.test.ts @@ -243,6 +243,23 @@ describe("Global Configuration", () => { }); describe("Project config discovery", () => { + it("uses an explicit project directory without changing process cwd", async () => { + await writeProjectConfig({ + tool: [ + { + id: "explicit-directory-hook", + when: { phase: "before", tool: "bash" }, + run: "pwd", + }, + ], + }); + + const result = await loadGlobalConfig(testProjectDir); + + expect(process.cwd()).toBe(originalCwd); + expect(result.config.tool?.map(hook => hook.id)).toEqual(["explicit-directory-hook"]); + }); + it("should find project config in parent directory", async () => { const nestedDir = join(testProjectDir, "src", "components"); await mkdir(nestedDir, { recursive: true }); diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts new file mode 100644 index 0000000..150b855 --- /dev/null +++ b/tests/e2e.v2.test.ts @@ -0,0 +1,100 @@ +import { afterAll, beforeAll, describe, expect, it } from "bun:test" +import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "fs/promises" +import { tmpdir } from "os" +import { join, resolve } from "path" +import { $ } from "bun" + +const enabled = process.env.OPENCODE2_E2E === "1" +const cliVersion = "0.0.0-next-15800" +let projectDirectory = "" + +describe("OpenCode V2 package E2E", () => { + beforeAll(async () => { + if (!enabled) return + + projectDirectory = await mkdtemp(join(tmpdir(), "opencode-hooks-v2-e2e-")) + await $`git init -q`.cwd(projectDirectory) + await $`npm run build:v2`.quiet() + await $`npm pack --ignore-scripts --pack-destination ${projectDirectory}`.cwd("packages/v2").quiet() + await writeFile( + join(projectDirectory, "package.json"), + JSON.stringify({ private: true, type: "module" }), + ) + await $`npm install ${join(projectDirectory, "opencode-command-hooks-v2-0.1.0-beta.0.tgz")} @opencode-ai/cli@${cliVersion}` + .cwd(projectDirectory) + .quiet() + await mkdir(join(projectDirectory, ".opencode"), { recursive: true }) + await writeFile( + join(projectDirectory, "opencode.jsonc"), + JSON.stringify({ + plugins: [join(projectDirectory, "node_modules", "opencode-command-hooks-v2", "dist", "index.js")], + }), + ) + await writeFile( + join(projectDirectory, ".opencode", "command-hooks.jsonc"), + JSON.stringify({ tool: [], session: [] }), + ) + }, 120_000) + + afterAll(async () => { + if (projectDirectory) { + await rm(projectDirectory, { recursive: true, force: true }) + } + }) + + it("loads the packed plugin in the pinned OpenCode 2 host", async () => { + if (!enabled) { + console.log("Skipping V2 E2E: set OPENCODE2_E2E=1 to run") + return + } + + const binary = resolve(projectDirectory, "node_modules", ".bin", "opencode2") + const config = join(projectDirectory, "opencode.jsonc") + const home = join(projectDirectory, "home") + await mkdir(home, { recursive: true }) + const environment = { + ...process.env, + HOME: home, + XDG_CONFIG_HOME: join(home, ".config"), + XDG_DATA_HOME: join(home, ".local", "share"), + XDG_CACHE_HOME: join(home, ".cache"), + OPENCODE_CONFIG: config, + OPENCODE_LOG_LEVEL: "trace", + } + const request = async (args: string[]) => { + const processHandle = Bun.spawn([binary, ...args], { + cwd: projectDirectory, + env: environment, + stdout: "pipe", + stderr: "pipe", + }) + const timeout = setTimeout(() => processHandle.kill(), 60_000) + const [exitCode, stdout, stderr] = await Promise.all([ + processHandle.exited, + new Response(processHandle.stdout).text(), + new Response(processHandle.stderr).text(), + ]) + clearTimeout(timeout) + return { exitCode, stdout, stderr } + } + + const { exitCode, stdout, stderr } = await request([ + "api", + "--standalone", + "get", + "/api/plugin", + ]) + + const logDirectory = join(home, ".local", "share", "opencode", "log") + let logs = "" + try { + const files = await readdir(logDirectory) + logs = (await Promise.all(files.map(file => readFile(join(logDirectory, file), "utf8")))).join("\n") + } catch { + // The assertion below still reports stdout and stderr when no log was written. + } + + expect(exitCode, `${stderr}\n${logs}`).toBe(0) + expect(stdout, `${stderr}\n${logs}`).toContain("opencode-command-hooks.v2") + }, 90_000) +}) diff --git a/tests/execution.shell.test.ts b/tests/execution.shell.test.ts index a0b50b3..cc9b0d3 100644 --- a/tests/execution.shell.test.ts +++ b/tests/execution.shell.test.ts @@ -1,4 +1,7 @@ import { describe, it, expect } from "bun:test" +import { mkdtemp, rm } from "fs/promises" +import { tmpdir } from "os" +import { join } from "path" import { executeCommand, executeCommands } from "../src/execution/shell.js" describe("Shell command execution", () => { @@ -64,6 +67,19 @@ describe("Shell command execution", () => { expect(result.success).toBe(true) expect(result.stdout).toContain("hello") }) + + it("executes in an explicit working directory", async () => { + const directory = await mkdtemp(join(tmpdir(), "opencode-hooks-cwd-")) + + try { + const result = await executeCommand("pwd", { cwd: directory }) + + expect(result.success).toBe(true) + expect(result.stdout?.trim()).toBe(directory) + } finally { + await rm(directory, { recursive: true, force: true }) + } + }) }) describe("executeCommands", () => { diff --git a/tests/executor.host.test.ts b/tests/executor.host.test.ts new file mode 100644 index 0000000..818316c --- /dev/null +++ b/tests/executor.host.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "bun:test" +import { executeHooks, type HookHost } from "../src/executor.js" +import type { ToolHook } from "../src/types/hooks.js" + +describe("host-neutral hook execution", () => { + it("delegates injection and toast delivery to the host", async () => { + const injections: Array<{ sessionId: string; message: string; hookId: string }> = [] + const toasts: Array<{ title?: string; message: string }> = [] + const host: HookHost = { + cwd: process.cwd(), + inject: async (sessionId, message, hookId) => { + injections.push({ sessionId, message, hookId }) + }, + toast: async (toast) => { + toasts.push(toast) + }, + } + const hook: ToolHook = { + id: "host-port", + when: { phase: "after", tool: "bash" }, + inject: "hook {id} for {tool}", + toast: { title: "Hook", message: "finished {id}" }, + } + + await executeHooks( + [hook], + { sessionId: "session-1", agent: "build", tool: "bash" }, + host, + ) + + expect(injections).toEqual([ + { sessionId: "session-1", message: "hook host-port for bash", hookId: "host-port" }, + ]) + expect(toasts).toEqual([ + { title: "Hook", message: "finished host-port", variant: "info", duration: undefined }, + ]) + }) +}) diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts new file mode 100644 index 0000000..e8d58f0 --- /dev/null +++ b/tests/v2.package.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "bun:test" +import { $ } from "bun" + +describe("V2 package artifact", () => { + it("builds and packs the isolated beta package with exact host dependencies", async () => { + await $`npm run build:v2`.quiet() + const output = await $`npm pack --dry-run --json --ignore-scripts`.cwd("packages/v2").text() + const [pack] = JSON.parse(output) as Array<{ + id: string + files: Array<{ path: string }> + }> + const manifest = await Bun.file("packages/v2/package.json").json() as { + dependencies: Record + } + + expect(pack.id).toStartWith("opencode-command-hooks-v2@0.1.0-beta.") + expect(pack.files.map(file => file.path)).toContain("dist/index.js") + expect(manifest.dependencies["@opencode-ai/plugin"]).toBe("0.0.0-next-15800") + }) +}) diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts new file mode 100644 index 0000000..5708ce8 --- /dev/null +++ b/tests/v2.plugin.test.ts @@ -0,0 +1,193 @@ +import { afterEach, describe, expect, it, spyOn } from "bun:test" +import { mkdir, mkdtemp, rm, writeFile } from "fs/promises" +import { tmpdir } from "os" +import { join } from "path" +import { createV2Plugin, type V2Context } from "../src/v2/plugin.js" + +type ToolCallback = (event: Record) => Promise | void + +const temporaryDirectories: string[] = [] + +const createProject = async (config: Record): Promise => { + const directory = await mkdtemp(join(tmpdir(), "opencode-hooks-v2-")) + temporaryDirectories.push(directory) + await mkdir(join(directory, ".opencode"), { recursive: true }) + await writeFile( + join(directory, ".opencode", "command-hooks.jsonc"), + JSON.stringify(config), + ) + return directory +} + +const createContext = ( + directory: string, + events: Record[] = [], +) => { + const callbacks = new Map() + const disposed: string[] = [] + const syntheticCalls: Array> = [] + const context: V2Context = { + tool: { + hook: async (name, callback) => { + callbacks.set(name, callback as ToolCallback) + return { dispose: async () => { disposed.push(name) } } + }, + }, + event: { + subscribe: () => (async function* () { + for (const event of events) yield event + })(), + }, + session: { + get: async ({ sessionID }) => ({ + id: sessionID, + agent: "build", + location: { directory }, + }), + synthetic: async (input) => { + syntheticCalls.push(input) + }, + }, + } + + return { context, callbacks, disposed, syntheticCalls } +} + +afterEach(async () => { + await Promise.all( + temporaryDirectories.splice(0).map(directory => rm(directory, { recursive: true, force: true })), + ) +}) + +describe("OpenCode V2 plugin", () => { + it("registers both tool phases and uses after-event input without a V1 cache", async () => { + const directory = await createProject({ + tool: [ + { + id: "v2-after", + when: { phase: "after", tool: "bash", toolArgs: { command: "pwd" } }, + run: "pwd", + inject: "cwd={stdout}", + }, + ], + }) + const { context, callbacks, disposed, syntheticCalls } = createContext(directory) + const plugin = createV2Plugin() + + const cleanup = await plugin.setup(context) + + expect(plugin.id).toBe("opencode-command-hooks.v2") + expect([...callbacks.keys()]).toEqual(["execute.before", "execute.after"]) + + await callbacks.get("execute.after")?.({ + tool: "bash", + sessionID: "session-1", + callID: "call-1", + agent: "build", + input: { command: "pwd" }, + result: {}, + }) + + expect(syntheticCalls).toHaveLength(1) + expect(syntheticCalls[0]).toMatchObject({ + sessionID: "session-1", + text: `cwd=${directory}\n`, + description: "opencode-command-hooks", + metadata: { hookId: "v2-after" }, + resume: false, + }) + + await cleanup?.() + expect(disposed).toEqual(["execute.before", "execute.after"]) + }) + + it("maps created and idle events once and keeps callback failures non-blocking", async () => { + const directory = await createProject({ + session: [ + { id: "start", when: { event: "session.start" }, inject: "started" }, + { id: "idle", when: { event: "session.idle" }, inject: "idle" }, + ], + }) + const events = [ + { + id: "event-created", + type: "session.created", + location: { directory }, + data: { sessionID: "session-2" }, + }, + { + id: "event-idle", + type: "session.idle", + location: { directory }, + data: { sessionID: "session-2" }, + }, + { + id: "event-idle", + type: "session.idle", + location: { directory }, + data: { sessionID: "session-2" }, + }, + ] + const { context, syntheticCalls } = createContext(directory, events) + const cleanup = await createV2Plugin().setup(context) + + await new Promise(resolve => setTimeout(resolve, 20)) + + expect(syntheticCalls.map(call => call.text)).toEqual(["started", "idle"]) + expect(syntheticCalls.every(call => call.resume === false)).toBe(true) + await cleanup?.() + + const failing = createContext(directory) + failing.context.session.get = async () => { + throw new Error("session lookup failed") + } + const errorSpy = spyOn(console, "error").mockImplementation(() => {}) + const failingCleanup = await createV2Plugin().setup(failing.context) + + await expect( + failing.callbacks.get("execute.before")?.({ + tool: "bash", + sessionID: "missing", + callID: "call-2", + agent: "build", + input: {}, + }), + ).resolves.toBeUndefined() + expect(errorSpy).toHaveBeenCalled() + + errorSpy.mockRestore() + await failingCleanup?.() + }) + + it("reports V2 toast degradation once without blocking injection", async () => { + const directory = await createProject({ + tool: [ + { + id: "toast-gap", + when: { phase: "before", tool: "bash" }, + inject: "still injected", + toast: { message: "not available" }, + }, + ], + }) + const { context, callbacks, syntheticCalls } = createContext(directory) + const warningSpy = spyOn(console, "warn").mockImplementation(() => {}) + const cleanup = await createV2Plugin().setup(context) + const event = { + tool: "bash", + sessionID: "session-3", + callID: "call-3", + agent: "build", + input: {}, + } + + await callbacks.get("execute.before")?.(event) + await callbacks.get("execute.before")?.(event) + + expect(syntheticCalls).toHaveLength(2) + expect(warningSpy).toHaveBeenCalledTimes(1) + + warningSpy.mockRestore() + await cleanup?.() + }) +}) diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..90df575 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.ts"], + "exclude": ["src/v2.ts", "src/v2/**", "node_modules", "dist"] +} diff --git a/tsconfig.json b/tsconfig.json index 1666494..9e2d338 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,10 @@ "module": "ESNext", "lib": ["ES2020"], "moduleResolution": "bundler", + "baseUrl": ".", + "paths": { + "@opencode-ai/plugin/v2": ["node_modules/@opencode-ai/plugin-v2/dist/v2/promise/index.d.ts"] + }, "declaration": true, "declarationMap": true, "sourceMap": true, From a1a1510530ed08537fe8b8fbb73d6d4f4ca6ba8c Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 20 Jul 2026 00:20:13 -0700 Subject: [PATCH 02/32] fix: verify V2 against isolated host --- .github/workflows/ci.yml | 3 + OPENCODE_V2_IMPACT_REPORT.md | 12 ++-- README.md | 4 +- bun.lock | 14 ++--- package.json | 2 +- packages/v2/README.md | 2 +- packages/v2/package.json | 2 +- src/v2/plugin.ts | 17 ++++- tests/e2e.v2.test.ts | 117 ++++++++++++++++++++++++++--------- tests/v2.package.test.ts | 2 +- tests/v2.plugin.test.ts | 30 +++++++++ 11 files changed, 154 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c417e19..a16d477 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,3 +47,6 @@ jobs: - name: Verify V2 package run: npm pack --dry-run working-directory: packages/v2 + + - name: Test packed package in OpenCode 2 + run: npm run test:v2:e2e diff --git a/OPENCODE_V2_IMPACT_REPORT.md b/OPENCODE_V2_IMPACT_REPORT.md index 4bf20b6..7af9bb8 100644 --- a/OPENCODE_V2_IMPACT_REPORT.md +++ b/OPENCODE_V2_IMPACT_REPORT.md @@ -1,7 +1,7 @@ # OpenCode 2 Impact and Migration Report Status: Implemented beta prototype -Last updated: 2026-07-17 +Last updated: 2026-07-20 Doc Class: report Doc Type: research Report Type: engineering migration @@ -39,15 +39,15 @@ The opt-in beta adapter is now implemented in this repository: - `src/v2/plugin.ts` owns the V2 Promise adapter and keeps callback failures non-blocking. - `packages/v2` produces the separate `opencode-command-hooks-v2` package without changing the V1 package identity or release tag. -- The package pins `@opencode-ai/plugin@0.0.0-next-15800` exactly. +- The package pins `@opencode-ai/plugin@0.0.0-next-15853` exactly. - Tool hooks use direct V2 event input, and `subagent`/`agent` are normalized to the existing `task`/`subagent_type` config vocabulary. - Session lookup supplies the authoritative project directory and agent for config discovery and command execution. - Injection uses `ctx.session.synthetic()` with `resume: false`; toast requests produce one explicit unsupported diagnostic. -- The event consumer handles `session.created` and compatibility `session.idle`, deduplicates event IDs, and is closed during plugin cleanup. +- The event consumer maps compatibility and durable execution events to `session.start`/`session.idle`, deduplicates event IDs and session starts, and is closed during plugin cleanup. - Unit, V1 regression, packed-artifact, and gated pinned-host E2E tests cover the adapter. - A dedicated V2 release workflow requires the pinned host E2E to pass before npm publication. -The pinned `0.0.0-next-15800` host currently returns an empty plugin list in the standalone E2E even for native config and local discovery. Its logs show the known config-directory watcher failure and no plugin-load attempt. The reproducible test remains gated behind `OPENCODE2_E2E=1`; publishing remains blocked until that upstream host behavior is fixed or a newer exact build passes. +The first one-shot standalone E2E queried the plugin list before asynchronous plugin activation completed. The corrected test starts an isolated persistent server, polls the location-scoped plugin endpoint until setup finishes, and passes against `0.0.0-next-15853`. It uses temporary HOME/XDG directories and a test-only server password, so it does not connect to or modify the normal OpenCode service. ## Research Question @@ -603,7 +603,7 @@ Rollback triggers: - OpenCode 2 is changing daily; this report is a point-in-time analysis for 2026-07-17. - Some upstream beta documentation already differs from the current `v2` branch source. -- A V2 adapter, packed package test, and pinned-host E2E smoke test are now present. The host smoke currently reproduces the upstream plugin-loading blocker described above. +- A V2 adapter, packed package test, and isolated pinned-host E2E loading test are now present and passing. - No claim here should override a later stable OpenCode 2 migration guide or package contract. ## Follow-ups @@ -613,5 +613,5 @@ Rollback triggers: - [x] Add packed V2 artifact verification while preserving the V1 regression suite. - [x] Use `opencode-command-hooks-v2` as the isolated beta package name. - [ ] Confirm npm ownership before publishing any prerelease. -- [ ] Enable the pinned host E2E gate after upstream plugin loading succeeds. +- [x] Enable the isolated pinned-host E2E loading gate. - [ ] Expand to a dual-host runtime matrix when the blocking host defects are resolved. diff --git a/README.md b/README.md index dd8cfbe..460ce46 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ Add to your `opencode.json`: ## OpenCode 2 Beta -OpenCode 2 uses a different plugin API, so the V1 package above does not load there. The opt-in beta artifact is built separately and pins the exact OpenCode 2 plugin contract it supports. It is not published while the pinned host loading test remains blocked upstream; after that gate passes, its config will be: +OpenCode 2 uses a different plugin API, so the V1 package above does not load there. The opt-in beta artifact is built separately and pins the exact OpenCode 2 plugin contract it supports. Once the beta package is published, its config is: ```jsonc { @@ -181,7 +181,7 @@ OpenCode 2 uses a different plugin API, so the V1 package above does not load th } ``` -Version `0.1.0-beta.0` targets `@opencode-ai/cli@0.0.0-next-15800`. Keep versions exact while the upstream API is beta, and verify the plugin ID with `opencode2 api get /api/plugin`. +Version `0.1.0-beta.0` targets `@opencode-ai/cli@0.0.0-next-15853`. Keep versions exact while the upstream API is beta, and verify the plugin ID with `opencode2 api get /api/plugin` after the host finishes activating plugins. The V2 adapter supports tool before/after hooks, `toolArgs` filters, agent frontmatter hooks, session start/idle hooks, explicit project-directory execution, and injection through synthetic context. Existing V1 config vocabulary remains valid: the V2 `subagent` tool and its `agent` argument are normalized to `task` and `subagent_type` for matching. diff --git a/bun.lock b/bun.lock index e06e7b6..7cd66e6 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-15800", + "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-15853", "@types/bun": "latest", "@types/js-yaml": "^4.0.5", "eslint": "^9.39.1", @@ -73,17 +73,17 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-next-15800", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-15800", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-beta.98", "google-auth-library": "10.5.0" } }, "sha512-UzbIVWqpH7KjS42VIpmeN9artdf6iQ/RGvYw0lraNlm4X7XnRn87nh/3mNdpqTrFDVzkbJyrxNvB4pIXBRc36Q=="], + "@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-next-15853", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-15853", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-beta.98", "google-auth-library": "10.5.0" } }, "sha512-JK1haXMl++KxwwbxlzNOZv+s4OjJc1Qftgg5d3SS6x0iAqlLtxyDtsepYvo7C1WN0BjRxCqZQI+6aSBGQ87TCQ=="], - "@opencode-ai/client": ["@opencode-ai/client@0.0.0-next-15800", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-next-15800", "@opencode-ai/schema": "0.0.0-next-15800" }, "peerDependencies": { "effect": "4.0.0-beta.98" }, "optionalPeers": ["effect"] }, "sha512-tFxRkQuWwfk/fdIECXkEdJUi2z09jWTFFbWBpgT233IWixjjhiKH2zfjLtpfkW8kTb4IPCs0vPWgzEvUWTT+Uw=="], + "@opencode-ai/client": ["@opencode-ai/client@0.0.0-next-15853", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-next-15853", "@opencode-ai/schema": "0.0.0-next-15853" }, "peerDependencies": { "effect": "4.0.0-beta.98" }, "optionalPeers": ["effect"] }, "sha512-jQp4QiMNZlPaqtmNwPRR2Esp4IMlavtMtnhr2WZyYfm41TgM+ziNC8lo9cvd+v1nu5vlGJLJNff+VW80w8zUoA=="], "@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.218", "", { "dependencies": { "@opencode-ai/sdk": "1.0.218", "zod": "4.1.8" } }, "sha512-7fsCx2SIu7TRNn3cdHiaIU7r1/XgnVTBufGnlezH+OZwHP+gILBjqtuMn+1L0e1npnl12CvS3FRNvg/3jr6yhA=="], - "@opencode-ai/plugin-v2": ["@opencode-ai/plugin@0.0.0-next-15800", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-next-15800", "@opencode-ai/client": "0.0.0-next-15800", "@opencode-ai/schema": "0.0.0-next-15800", "@opencode-ai/sdk": "0.0.0-next-15800", "@standard-schema/spec": "^1.1.0", "effect": "4.0.0-beta.98", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-YHt9uHjNke0UH8W2kVxvdaxLHA9/02ZTh9hldPhfh3D42QAAwEeJJIqKgpFc6Jf+7nmCNSjQEfzpEYzlrUQliA=="], + "@opencode-ai/plugin-v2": ["@opencode-ai/plugin@0.0.0-next-15853", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-next-15853", "@opencode-ai/client": "0.0.0-next-15853", "@opencode-ai/schema": "0.0.0-next-15853", "@opencode-ai/sdk": "0.0.0-next-15853", "@standard-schema/spec": "^1.1.0", "effect": "4.0.0-beta.98", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-J1e/qQPm7AYj8ftl0rIHKNHAtvE4fkG4Cc8jOjDKzUBTns9Hk8YwXonVC7EAHurnXPkEU5yZublWI6Owrp2sYg=="], - "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-next-15800", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-15800", "effect": "4.0.0-beta.98" } }, "sha512-epbGuT+R5ly1MWG/5wUo/i51j1PK2+RsG6rAiGAw3nWOSObyWw4Y2YaXRO4bvIGF5+0bJh9MAJD/jKgNn2f+7w=="], + "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-next-15853", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-15853", "effect": "4.0.0-beta.98" } }, "sha512-NRPuAIFAw6LgT5Z8wxvgYY52Yz14OFMwHJHt9AXYxm/2TOAcd+F3S9VWOoLS3GzO10tFqffJx4HJd/aDrQXVog=="], - "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-next-15800", "", { "dependencies": { "effect": "4.0.0-beta.98" } }, "sha512-QiF9CmntEVIthT3n0oAtdxzq/9C/efqHWlk3y4yiF9u2S1Znj/KmGtyezYylvAlEdUgZTXoMpu7sib2iXLCf5g=="], + "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-next-15853", "", { "dependencies": { "effect": "4.0.0-beta.98" } }, "sha512-sw5rJrNgdQL3TIK7kvZG5NCvixbhHNopz3/le3cR5zpM3pC1E9eYuqGFuakEuCX1tGEwyVVHCeE19ar7QH7VSA=="], "@opencode-ai/sdk": ["@opencode-ai/sdk@1.0.218", "", {}, "sha512-c6ss6UPAMskSVQUecuhNvPLFngyVh2Os9o0kpVjoqJJ16HXhzjVSk5axgh3ueQrfP5aZfg5o6l6srmjuCTPNnQ=="], @@ -421,7 +421,7 @@ "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - "@opencode-ai/plugin-v2/@opencode-ai/sdk": ["@opencode-ai/sdk@0.0.0-next-15800", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-MtdELCZl50RNM3W/etlU18FxgCVt+i17FE+QNAFidYmr0bQK/FXeIjtZ+wlZ867kEh0+aZoKur9VemA3ExyqDA=="], + "@opencode-ai/plugin-v2/@opencode-ai/sdk": ["@opencode-ai/sdk@0.0.0-next-15853", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-BkmSNsRGzQyNJkWU85McFABAQe81qfCQ2mwVEJGVcBqCsJx817lh2u+8ElEArWo5jnkCb+DALDJ6612iLjovrA=="], "@opencode-ai/plugin-v2/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], diff --git a/package.json b/package.json index a8cd352..cad7bd0 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", - "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-15800", + "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-15853", "@types/bun": "latest", "@types/js-yaml": "^4.0.5", "eslint": "^9.39.1", diff --git a/packages/v2/README.md b/packages/v2/README.md index f194bb4..8230ad0 100644 --- a/packages/v2/README.md +++ b/packages/v2/README.md @@ -1,6 +1,6 @@ # opencode-command-hooks-v2 -Beta package for OpenCode 2. It targets `@opencode-ai/cli@0.0.0-next-15800` and uses the same `.opencode/command-hooks.jsonc` and agent frontmatter configuration as the V1 package. +Beta package for OpenCode 2. It targets `@opencode-ai/cli@0.0.0-next-15853` and uses the same `.opencode/command-hooks.jsonc` and agent frontmatter configuration as the V1 package. ```jsonc { diff --git a/packages/v2/package.json b/packages/v2/package.json index 86e7c8c..b593155 100644 --- a/packages/v2/package.json +++ b/packages/v2/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/shanebishop1/opencode-command-hooks#opencode-2-beta", "dependencies": { - "@opencode-ai/plugin": "0.0.0-next-15800" + "@opencode-ai/plugin": "0.0.0-next-15853" }, "engines": { "bun": ">=1.0.0" diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 98ebda9..4c2d270 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -96,6 +96,7 @@ export const createV2Plugin = (): V2Plugin => ({ setup: async (ctx) => { const notifiedConfigErrors = new Set() const handledEvents = new Set() + const startedSessions = new Set() let warnedToastUnsupported = false let stopped = false @@ -182,7 +183,15 @@ export const createV2Plugin = (): V2Plugin => ({ } const handleSession = async (event: V2Event): Promise => { - if (event.type !== "session.created" && event.type !== "session.idle") return + const eventType = event.type === "session.created" || event.type === "session.execution.started" + ? "session.created" + : event.type === "session.idle" || + event.type === "session.execution.succeeded" || + event.type === "session.execution.failed" || + event.type === "session.execution.interrupted" + ? "session.idle" + : undefined + if (!eventType) return if (event.id && handledEvents.has(event.id)) return if (event.id) { handledEvents.add(event.id) @@ -194,6 +203,10 @@ export const createV2Plugin = (): V2Plugin => ({ const sessionID = normalizeString(event.data?.sessionID) if (!sessionID) return + if (eventType === "session.created") { + if (startedSessions.has(sessionID)) return + startedSessions.add(sessionID) + } try { const resolved = await sessionInfo(sessionID, { @@ -203,7 +216,7 @@ export const createV2Plugin = (): V2Plugin => ({ const { config, error } = await loadGlobalConfig(resolved.directory) notifyConfigError(error, resolved.directory) const hooks = filterSessionHooks(config.session ?? [], { - event: event.type, + event: eventType, agent: resolved.agent, }) await executeHooks( diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index 150b855..908f320 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -5,7 +5,7 @@ import { join, resolve } from "path" import { $ } from "bun" const enabled = process.env.OPENCODE2_E2E === "1" -const cliVersion = "0.0.0-next-15800" +const cliVersion = "0.0.0-next-15853" let projectDirectory = "" describe("OpenCode V2 package E2E", () => { @@ -60,41 +60,98 @@ describe("OpenCode V2 package E2E", () => { XDG_CACHE_HOME: join(home, ".cache"), OPENCODE_CONFIG: config, OPENCODE_LOG_LEVEL: "trace", + OPENCODE_PASSWORD: "v2-e2e-password", + OPENCODE_SERVER_PASSWORD: "v2-e2e-password", } - const request = async (args: string[]) => { - const processHandle = Bun.spawn([binary, ...args], { - cwd: projectDirectory, - env: environment, - stdout: "pipe", - stderr: "pipe", - }) - const timeout = setTimeout(() => processHandle.kill(), 60_000) - const [exitCode, stdout, stderr] = await Promise.all([ - processHandle.exited, - new Response(processHandle.stdout).text(), - new Response(processHandle.stderr).text(), + const server = Bun.spawn([ + binary, + "serve", + "--hostname", + "127.0.0.1", + "--port", + "0", + ], { + cwd: projectDirectory, + env: environment, + stdout: "pipe", + stderr: "pipe", + }) + let serverOutput = "" + let baseUrl = "" + const serverStdout = (async () => { + const reader = server.stdout.getReader() + const decoder = new TextDecoder() + while (true) { + const { done, value } = await reader.read() + if (done) break + serverOutput += decoder.decode(value, { stream: true }) + baseUrl ||= serverOutput.match(/server listening on (http:\/\/\S+)/)?.[1] ?? "" + } + serverOutput += decoder.decode() + })() + const serverStderr = new Response(server.stderr).text() + const stopServer = async (): Promise => { + if (server.exitCode === null) server.kill(9) + await Promise.race([ + server.exited.then(() => undefined), + new Promise(resolve => setTimeout(resolve, 5_000)), ]) - clearTimeout(timeout) - return { exitCode, stdout, stderr } } - - const { exitCode, stdout, stderr } = await request([ - "api", - "--standalone", - "get", - "/api/plugin", - ]) - + const headers = { + "x-opencode-directory": projectDirectory, + authorization: `Basic ${Buffer.from("opencode:v2-e2e-password").toString("base64")}`, + } const logDirectory = join(home, ".local", "share", "opencode", "log") - let logs = "" + const readLogs = async (): Promise => { + try { + const files = await readdir(logDirectory) + return (await Promise.all(files.map(file => readFile(join(logDirectory, file), "utf8")))).join("\n") + } catch { + return "" + } + } + let pluginResponse = "" + try { - const files = await readdir(logDirectory) - logs = (await Promise.all(files.map(file => readFile(join(logDirectory, file), "utf8")))).join("\n") - } catch { - // The assertion below still reports stdout and stderr when no log was written. + const serverDeadline = Date.now() + 20_000 + while (!baseUrl && server.exitCode === null && Date.now() < serverDeadline) { + await new Promise(resolve => setTimeout(resolve, 25)) + } + if (!baseUrl) { + await stopServer() + await serverStdout + throw new Error( + `V2 server did not become ready (exit=${server.exitCode})\nstdout:\n${serverOutput}\nstderr:\n${await serverStderr}`, + ) + } + + const pluginDeadline = Date.now() + 30_000 + while (Date.now() < pluginDeadline) { + try { + const response = await fetch(`${baseUrl}/api/plugin`, { headers }) + pluginResponse = await response.text() + if (response.ok && pluginResponse.includes("opencode-command-hooks.v2")) break + } catch { + // The server is still starting. + } + await new Promise(resolve => setTimeout(resolve, 100)) + } + + if (!pluginResponse.includes("opencode-command-hooks.v2")) { + await stopServer() + await serverStdout + throw new Error( + `V2 plugin did not load (server exit=${server.exitCode}): ${pluginResponse}\nstdout:\n${serverOutput}\nstderr:\n${await serverStderr}\n${await readLogs()}`, + ) + } + + } finally { + await stopServer() + await serverStdout } - expect(exitCode, `${stderr}\n${logs}`).toBe(0) - expect(stdout, `${stderr}\n${logs}`).toContain("opencode-command-hooks.v2") + const logs = await readLogs() + + expect(pluginResponse, logs).toContain("opencode-command-hooks.v2") }, 90_000) }) diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index e8d58f0..460d3d8 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -15,6 +15,6 @@ describe("V2 package artifact", () => { expect(pack.id).toStartWith("opencode-command-hooks-v2@0.1.0-beta.") expect(pack.files.map(file => file.path)).toContain("dist/index.js") - expect(manifest.dependencies["@opencode-ai/plugin"]).toBe("0.0.0-next-15800") + expect(manifest.dependencies["@opencode-ai/plugin"]).toBe("0.0.0-next-15853") }) }) diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index 5708ce8..8ca3505 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -159,6 +159,36 @@ describe("OpenCode V2 plugin", () => { await failingCleanup?.() }) + it("maps durable execution lifecycle events to start and idle hooks", async () => { + const directory = await createProject({ + session: [ + { id: "durable-start", when: { event: "session.start" }, inject: "durable started" }, + { id: "durable-idle", when: { event: "session.idle" }, inject: "durable idle" }, + ], + }) + const events = [ + { + id: "execution-started", + type: "session.execution.started", + location: { directory }, + data: { sessionID: "session-durable" }, + }, + { + id: "execution-succeeded", + type: "session.execution.succeeded", + location: { directory }, + data: { sessionID: "session-durable" }, + }, + ] + const { context, syntheticCalls } = createContext(directory, events) + const cleanup = await createV2Plugin().setup(context) + + await new Promise(resolve => setTimeout(resolve, 20)) + + expect(syntheticCalls.map(call => call.text)).toEqual(["durable started", "durable idle"]) + await cleanup?.() + }) + it("reports V2 toast degradation once without blocking injection", async () => { const directory = await createProject({ tool: [ From 6301f5ee4492eff14087418f8e882fadf3f5e1b5 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:07:47 -0700 Subject: [PATCH 03/32] ci: publish GitHub releases from tags --- .github/workflows/release-v2.yml | 13 +++++++++++++ .github/workflows/release.yml | 12 +++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-v2.yml b/.github/workflows/release-v2.yml index 45a4a5f..78ab029 100644 --- a/.github/workflows/release-v2.yml +++ b/.github/workflows/release-v2.yml @@ -56,6 +56,9 @@ jobs: publish: needs: verify runs-on: ubuntu-latest + permissions: + contents: write + id-token: write steps: - name: Checkout uses: actions/checkout@v4 @@ -77,3 +80,13 @@ jobs: - name: Publish V2 beta run: npm publish --access public --provenance --tag next working-directory: packages/v2 + + - name: Create GitHub prerelease + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then + echo "GitHub release already exists: $GITHUB_REF_NAME" + else + gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes --title "$GITHUB_REF_NAME" --prerelease --latest=false + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9624fd9..1ee9d70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,7 +67,7 @@ jobs: needs: verify runs-on: ubuntu-latest permissions: - contents: read + contents: write id-token: write steps: - name: Checkout @@ -89,3 +89,13 @@ jobs: - name: Publish to npm run: npm publish --access public --provenance + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then + echo "GitHub release already exists: $GITHUB_REF_NAME" + else + gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes --title "$GITHUB_REF_NAME" + fi From a167a950bb6d66644a9a5ed370d217cac7e7d651 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Sun, 26 Jul 2026 22:52:08 -0700 Subject: [PATCH 04/32] feat: scope session hooks to parent sessions --- README.md | 1 + src/executor.ts | 9 +++- src/index.ts | 17 ++++++ src/schemas.ts | 2 + src/types/hooks.ts | 7 ++- tests/session.scope.test.ts | 105 ++++++++++++++++++++++++++++++++++++ 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 tests/session.scope.test.ts diff --git a/README.md b/README.md index 460ce46..79613b8 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ You can set up tool hooks to only trigger on specific arguments via `when.toolAr ## Features - Tool hooks (`before`/`after`) and session hooks (`start`/`idle`) via simple JSON/YAML frontmatter config + - Session hooks target parent sessions by default; opt into child or all sessions with `"when": { "event": "session.idle", "sessionScope": "child" }` or `"sessionScope": "any"`. - Hooks are **non-blocking**: failures don’t crash the session/tool. - Commands run **sequentially**, even if earlier ones fail. - Inject bash output into context with `inject` and notify user with `toast` diff --git a/src/executor.ts b/src/executor.ts index da29efb..1791734 100644 --- a/src/executor.ts +++ b/src/executor.ts @@ -53,7 +53,11 @@ export const matches = (pattern: string | string[] | undefined, value: string | */ export const filterSessionHooks = ( hooks: SessionHook[], - criteria: { event: string; agent: string | undefined } + criteria: { + event: string + agent: string | undefined + sessionScope: "parent" | "child" | undefined + } ): SessionHook[] => { return hooks.filter((hook) => { // Normalize session.start to session.created @@ -71,6 +75,9 @@ export const filterSessionHooks = ( return false } + const hookScope = hook.when.sessionScope ?? "parent" + if (hookScope !== "any" && hookScope !== criteria.sessionScope) return false + return matches(hook.when.agent, criteria.agent) }) } diff --git a/src/index.ts b/src/index.ts index 3af0e30..7cd4210 100644 --- a/src/index.ts +++ b/src/index.ts @@ -122,6 +122,21 @@ const deleteToolArgs = (callId: string | undefined): void => { toolCallArgsCache.delete(callId) } +const getSessionScope = async ( + sessionId: string, + client: OpencodeClient, +): Promise<"parent" | "child" | undefined> => { + try { + const result = await client.session.get({ path: { id: sessionId } }) + if (!result.data) return undefined + return result.data.parentID === undefined ? "parent" : "child" + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + logger.debug(`Could not determine scope for session ${sessionId}: ${message}`) + return undefined + } +} + /** @@ -149,10 +164,12 @@ const handleSessionEvent = async ( const markdownConfig = { tool: [], session: [] } const { config: mergedConfig } = mergeConfigs(globalConfig, markdownConfig) + const sessionScope = await getSessionScope(sessionId, client) const matchedHooks = filterSessionHooks(mergedConfig.session || [], { event: eventType, agent, + sessionScope, }) logger.debug( diff --git a/src/schemas.ts b/src/schemas.ts index 65959b3..ac6ef24 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -27,6 +27,7 @@ const PhaseSchema = z.enum(["before", "after"]); * Note: "session.start" maps to "session.created" internally */ const SessionEventSchema = z.enum(["session.created", "session.idle", "session.end", "session.start"]); +const SessionScopeSchema = z.enum(["parent", "child", "any"]); // ============================================================================ // TOOL HOOK SCHEMAS @@ -97,6 +98,7 @@ export const ToolHookSchema = z.object({ const SessionHookWhenSchema = z.object({ event: SessionEventSchema, agent: StringOrArray.optional(), + sessionScope: SessionScopeSchema.default("parent"), }); /** diff --git a/src/types/hooks.ts b/src/types/hooks.ts index ee51200..5734faf 100644 --- a/src/types/hooks.ts +++ b/src/types/hooks.ts @@ -168,7 +168,7 @@ export interface SessionHook { * Matching conditions for session hooks * * All specified conditions must match for the hook to execute. - * Omitted fields default to matching all values (wildcard behavior). + * Omitted sessionScope defaults to matching parent sessions. */ export interface SessionHookWhen { /** @@ -182,6 +182,11 @@ export interface SessionHookWhen { * Omitted: defaults to "*" in global config, "this agent" in markdown. */ agent?: string | string[] + + /** + * Session hierarchy to match. Defaults to "parent"; use "any" to match all sessions. + */ + sessionScope?: "parent" | "child" | "any" } /** diff --git a/tests/session.scope.test.ts b/tests/session.scope.test.ts new file mode 100644 index 0000000..c9b428c --- /dev/null +++ b/tests/session.scope.test.ts @@ -0,0 +1,105 @@ +import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { filterSessionHooks } from "../src/executor"; +import { parseSessionHook } from "../src/schemas"; + +const ORIGINAL_CWD = process.cwd(); + +const sessionHook = (id: string, sessionScope?: "parent" | "child" | "any") => + parseSessionHook({ + id, + when: { event: "session.idle", ...(sessionScope ? { sessionScope } : {}) }, + inject: id, + })!; + +describe("session hook scope", () => { + it("defaults parsed session hooks to parent scope and accepts child and any", () => { + expect(sessionHook("default").when.sessionScope).toBe("parent"); + expect(sessionHook("child", "child").when.sessionScope).toBe("child"); + expect(sessionHook("any", "any").when.sessionScope).toBe("any"); + expect(parseSessionHook({ + id: "invalid", + when: { event: "session.idle", sessionScope: "all" }, + inject: "invalid", + })).toBeNull(); + }); + + it("matches parent, child, any, and unknown session identities deterministically", () => { + const hooks = [sessionHook("default"), sessionHook("child", "child"), sessionHook("any", "any")]; + + expect(filterSessionHooks(hooks, { + event: "session.idle", + agent: undefined, + sessionScope: "parent", + }).map((hook) => hook.id)).toEqual(["default", "any"]); + expect(filterSessionHooks(hooks, { + event: "session.idle", + agent: undefined, + sessionScope: "child", + }).map((hook) => hook.id)).toEqual(["child", "any"]); + expect(filterSessionHooks(hooks, { + event: "session.idle", + agent: undefined, + sessionScope: undefined, + }).map((hook) => hook.id)).toEqual(["any"]); + }); +}); + +describe("session lifecycle scope adapter", () => { + let testDir: string; + + beforeEach(() => { + testDir = mkdtempSync(join(tmpdir(), "opencode-hooks-session-scope-")); + mkdirSync(join(testDir, ".opencode"), { recursive: true }); + writeFileSync(join(testDir, ".opencode", "command-hooks.jsonc"), JSON.stringify({ + session: [ + { id: "default-parent", when: { event: "session.idle" }, inject: "default-parent" }, + { id: "child-only", when: { event: "session.idle", sessionScope: "child" }, inject: "child-only" }, + { id: "any-session", when: { event: "session.idle", sessionScope: "any" }, inject: "any-session" }, + ], + })); + process.chdir(testDir); + }); + + afterEach(() => { + process.chdir(ORIGINAL_CWD); + rmSync(testDir, { recursive: true, force: true }); + }); + + it("resolves session identity through client.session.get and only lets any hooks run when unknown", async () => { + const promptCalls: string[] = []; + const getCalls: string[] = []; + const client = { + session: { + get: async ({ path }: { path: { id: string } }) => { + getCalls.push(path.id); + if (path.id === "unknown") throw new Error("not found"); + return { data: path.id === "child" ? { parentID: "parent" } : {} }; + }, + promptAsync: async ({ body }: { body: { parts: Array<{ text: string }> } }) => { + promptCalls.push(body.parts[0].text); + return {}; + }, + }, + tui: { showToast: async () => ({}) }, + }; + + const { CommandHooksPlugin } = await import("../src/index.js"); + const plugin = await CommandHooksPlugin({ client } as never); + + for (const id of ["parent", "child", "unknown"]) { + await plugin.event?.({ + event: { type: "session.idle", properties: { sessionID: id } }, + } as never); + } + + expect(getCalls).toEqual(["parent", "child", "unknown"]); + expect(promptCalls).toEqual([ + "default-parent", "any-session", + "child-only", "any-session", + "any-session", + ]); + }); +}); From 5b2c64b9c2683053fba6a27b6445f5a651121554 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:00:29 -0700 Subject: [PATCH 05/32] fix: scope V2 session hooks --- src/v2/plugin.ts | 28 +++++++++++++++-- tests/v2.plugin.test.ts | 68 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 4c2d270..9172be6 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -16,6 +16,8 @@ type V2ToolEvent = { type V2Event = { id?: string type: string + parentID?: string + info?: { parentID?: string } data?: Record location?: { directory?: string; workspaceID?: string } } @@ -23,6 +25,7 @@ type V2Event = { type V2SessionInfo = { id: string agent?: string + parentID?: string location: { directory: string; workspaceID?: string } } @@ -60,6 +63,19 @@ const asRecord = (value: unknown): Record | undefined => ? value as Record : undefined +const hasOwn = (value: object, key: string): boolean => + Object.prototype.hasOwnProperty.call(value, key) + +const eventSessionScope = (event: V2Event): "child" | undefined => { + if (hasOwn(event, "parentID") || (event.info && hasOwn(event.info, "parentID"))) { + return "child" + } + if (!event.data) return undefined + if (hasOwn(event.data, "parentID")) return "child" + const info = asRecord(event.data.info) + return info && hasOwn(info, "parentID") ? "child" : undefined +} + const toolContext = (event: V2ToolEvent): { toolName: string toolArgs: Record | undefined @@ -130,19 +146,25 @@ export const createV2Plugin = (): V2Plugin => ({ const sessionInfo = async ( sessionID: string, - fallback?: { directory?: string; agent?: string }, - ): Promise<{ directory: string; agent: string | undefined }> => { + fallback?: { directory?: string; agent?: string; sessionScope?: "child" }, + ): Promise<{ + directory: string + agent: string | undefined + sessionScope: "parent" | "child" | undefined + }> => { try { const session = await ctx.session.get({ sessionID }) return { directory: session.location.directory, agent: normalizeString(session.agent) || normalizeString(fallback?.agent) || undefined, + sessionScope: session.parentID === undefined ? "parent" : "child", } } catch (error) { if (fallback?.directory) { return { directory: fallback.directory, agent: normalizeString(fallback.agent) || undefined, + sessionScope: fallback.sessionScope, } } throw error @@ -212,12 +234,14 @@ export const createV2Plugin = (): V2Plugin => ({ const resolved = await sessionInfo(sessionID, { directory: normalizeString(event.location?.directory) || undefined, agent: normalizeString(event.data?.agent) || undefined, + sessionScope: eventSessionScope(event), }) const { config, error } = await loadGlobalConfig(resolved.directory) notifyConfigError(error, resolved.directory) const hooks = filterSessionHooks(config.session ?? [], { event: eventType, agent: resolved.agent, + sessionScope: resolved.sessionScope, }) await executeHooks( hooks, diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index 8ca3505..8920af3 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -5,6 +5,14 @@ import { join } from "path" import { createV2Plugin, type V2Context } from "../src/v2/plugin.js" type ToolCallback = (event: Record) => Promise | void +type LifecycleEvent = { + id?: string + type: string + parentID?: string + info?: { parentID?: string } + data?: Record + location?: { directory?: string; workspaceID?: string } +} const temporaryDirectories: string[] = [] @@ -21,11 +29,15 @@ const createProject = async (config: Record): Promise = const createContext = ( directory: string, - events: Record[] = [], + events: LifecycleEvent[] = [], ) => { const callbacks = new Map() const disposed: string[] = [] const syntheticCalls: Array> = [] + let eventsDrained: () => void + const eventsComplete = new Promise(resolve => { + eventsDrained = resolve + }) const context: V2Context = { tool: { hook: async (name, callback) => { @@ -36,6 +48,7 @@ const createContext = ( event: { subscribe: () => (async function* () { for (const event of events) yield event + eventsDrained() })(), }, session: { @@ -50,7 +63,7 @@ const createContext = ( }, } - return { context, callbacks, disposed, syntheticCalls } + return { context, callbacks, disposed, syntheticCalls, eventsComplete } } afterEach(async () => { @@ -189,6 +202,57 @@ describe("OpenCode V2 plugin", () => { await cleanup?.() }) + it("filters lifecycle hooks by resolved and fallback session scope without guessing unknown sessions", async () => { + const directory = await createProject({ + session: [ + { id: "parent", when: { event: "session.idle" }, inject: "parent" }, + { id: "child", when: { event: "session.idle", sessionScope: "child" }, inject: "child" }, + { id: "any", when: { event: "session.idle", sessionScope: "any" }, inject: "any" }, + ], + }) + const events = [ + { id: "parent", type: "session.idle", location: { directory }, data: { sessionID: "parent" } }, + { id: "child", type: "session.idle", location: { directory }, data: { sessionID: "child" } }, + { + id: "fallback-parent-id", + type: "session.idle", + location: { directory }, + data: { sessionID: "fallback-parent-id", parentID: "parent" }, + }, + { + id: "fallback-info-parent-id", + type: "session.execution.succeeded", + location: { directory }, + data: { sessionID: "fallback-info-parent-id", info: { parentID: "parent" } }, + }, + { id: "unknown", type: "session.idle", location: { directory }, data: { sessionID: "unknown" } }, + ] + const { context, eventsComplete, syntheticCalls } = createContext(directory, events) + context.session.get = async ({ sessionID }) => { + if (sessionID.startsWith("fallback") || sessionID === "unknown") { + throw new Error("session lookup failed") + } + return { + id: sessionID, + agent: "build", + location: { directory }, + ...(sessionID === "child" ? { parentID: "parent" } : {}), + } + } + + const cleanup = await createV2Plugin().setup(context) + await eventsComplete + + expect(syntheticCalls.map(call => call.text)).toEqual([ + "parent", "any", + "child", "any", + "child", "any", + "child", "any", + "any", + ]) + await cleanup?.() + }) + it("reports V2 toast degradation once without blocking injection", async () => { const directory = await createProject({ tool: [ From 4fa438bde249a077b6616456f406ef64e3a33739 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:05:13 -0700 Subject: [PATCH 06/32] fix: keep unknown V2 session scope safe --- src/v2/plugin.ts | 15 +++++---------- tests/v2.plugin.test.ts | 7 +++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 9172be6..ccaa82b 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -63,17 +63,12 @@ const asRecord = (value: unknown): Record | undefined => ? value as Record : undefined -const hasOwn = (value: object, key: string): boolean => - Object.prototype.hasOwnProperty.call(value, key) - const eventSessionScope = (event: V2Event): "child" | undefined => { - if (hasOwn(event, "parentID") || (event.info && hasOwn(event.info, "parentID"))) { - return "child" - } - if (!event.data) return undefined - if (hasOwn(event.data, "parentID")) return "child" - const info = asRecord(event.data.info) - return info && hasOwn(info, "parentID") ? "child" : undefined + const info = asRecord(event.data?.info) + const parentID = normalizeString( + event.parentID ?? event.info?.parentID ?? event.data?.parentID ?? info?.parentID, + ) + return parentID ? "child" : undefined } const toolContext = (event: V2ToolEvent): { diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index 8920af3..1efb14a 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -225,6 +225,12 @@ describe("OpenCode V2 plugin", () => { location: { directory }, data: { sessionID: "fallback-info-parent-id", info: { parentID: "parent" } }, }, + { + id: "fallback-undefined-parent-id", + type: "session.idle", + location: { directory }, + data: { sessionID: "fallback-undefined-parent-id", parentID: undefined }, + }, { id: "unknown", type: "session.idle", location: { directory }, data: { sessionID: "unknown" } }, ] const { context, eventsComplete, syntheticCalls } = createContext(directory, events) @@ -249,6 +255,7 @@ describe("OpenCode V2 plugin", () => { "child", "any", "child", "any", "any", + "any", ]) await cleanup?.() }) From ddd589fef9be006cbfacc7b7dad03da234c9d30d Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:35:54 -0700 Subject: [PATCH 07/32] Revert "fix: keep unknown V2 session scope safe" This reverts commit 4fa438bde249a077b6616456f406ef64e3a33739. --- src/v2/plugin.ts | 15 ++++++++++----- tests/v2.plugin.test.ts | 7 ------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index ccaa82b..9172be6 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -63,12 +63,17 @@ const asRecord = (value: unknown): Record | undefined => ? value as Record : undefined +const hasOwn = (value: object, key: string): boolean => + Object.prototype.hasOwnProperty.call(value, key) + const eventSessionScope = (event: V2Event): "child" | undefined => { - const info = asRecord(event.data?.info) - const parentID = normalizeString( - event.parentID ?? event.info?.parentID ?? event.data?.parentID ?? info?.parentID, - ) - return parentID ? "child" : undefined + if (hasOwn(event, "parentID") || (event.info && hasOwn(event.info, "parentID"))) { + return "child" + } + if (!event.data) return undefined + if (hasOwn(event.data, "parentID")) return "child" + const info = asRecord(event.data.info) + return info && hasOwn(info, "parentID") ? "child" : undefined } const toolContext = (event: V2ToolEvent): { diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index 1efb14a..8920af3 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -225,12 +225,6 @@ describe("OpenCode V2 plugin", () => { location: { directory }, data: { sessionID: "fallback-info-parent-id", info: { parentID: "parent" } }, }, - { - id: "fallback-undefined-parent-id", - type: "session.idle", - location: { directory }, - data: { sessionID: "fallback-undefined-parent-id", parentID: undefined }, - }, { id: "unknown", type: "session.idle", location: { directory }, data: { sessionID: "unknown" } }, ] const { context, eventsComplete, syntheticCalls } = createContext(directory, events) @@ -255,7 +249,6 @@ describe("OpenCode V2 plugin", () => { "child", "any", "child", "any", "any", - "any", ]) await cleanup?.() }) From 63f3aa1760948b309c4a9cab14ea925036133a2f Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:35:54 -0700 Subject: [PATCH 08/32] Revert "fix: scope V2 session hooks" This reverts commit 5b2c64b9c2683053fba6a27b6445f5a651121554. --- src/v2/plugin.ts | 28 ++--------------- tests/v2.plugin.test.ts | 68 ++--------------------------------------- 2 files changed, 4 insertions(+), 92 deletions(-) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 9172be6..4c2d270 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -16,8 +16,6 @@ type V2ToolEvent = { type V2Event = { id?: string type: string - parentID?: string - info?: { parentID?: string } data?: Record location?: { directory?: string; workspaceID?: string } } @@ -25,7 +23,6 @@ type V2Event = { type V2SessionInfo = { id: string agent?: string - parentID?: string location: { directory: string; workspaceID?: string } } @@ -63,19 +60,6 @@ const asRecord = (value: unknown): Record | undefined => ? value as Record : undefined -const hasOwn = (value: object, key: string): boolean => - Object.prototype.hasOwnProperty.call(value, key) - -const eventSessionScope = (event: V2Event): "child" | undefined => { - if (hasOwn(event, "parentID") || (event.info && hasOwn(event.info, "parentID"))) { - return "child" - } - if (!event.data) return undefined - if (hasOwn(event.data, "parentID")) return "child" - const info = asRecord(event.data.info) - return info && hasOwn(info, "parentID") ? "child" : undefined -} - const toolContext = (event: V2ToolEvent): { toolName: string toolArgs: Record | undefined @@ -146,25 +130,19 @@ export const createV2Plugin = (): V2Plugin => ({ const sessionInfo = async ( sessionID: string, - fallback?: { directory?: string; agent?: string; sessionScope?: "child" }, - ): Promise<{ - directory: string - agent: string | undefined - sessionScope: "parent" | "child" | undefined - }> => { + fallback?: { directory?: string; agent?: string }, + ): Promise<{ directory: string; agent: string | undefined }> => { try { const session = await ctx.session.get({ sessionID }) return { directory: session.location.directory, agent: normalizeString(session.agent) || normalizeString(fallback?.agent) || undefined, - sessionScope: session.parentID === undefined ? "parent" : "child", } } catch (error) { if (fallback?.directory) { return { directory: fallback.directory, agent: normalizeString(fallback.agent) || undefined, - sessionScope: fallback.sessionScope, } } throw error @@ -234,14 +212,12 @@ export const createV2Plugin = (): V2Plugin => ({ const resolved = await sessionInfo(sessionID, { directory: normalizeString(event.location?.directory) || undefined, agent: normalizeString(event.data?.agent) || undefined, - sessionScope: eventSessionScope(event), }) const { config, error } = await loadGlobalConfig(resolved.directory) notifyConfigError(error, resolved.directory) const hooks = filterSessionHooks(config.session ?? [], { event: eventType, agent: resolved.agent, - sessionScope: resolved.sessionScope, }) await executeHooks( hooks, diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index 8920af3..8ca3505 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -5,14 +5,6 @@ import { join } from "path" import { createV2Plugin, type V2Context } from "../src/v2/plugin.js" type ToolCallback = (event: Record) => Promise | void -type LifecycleEvent = { - id?: string - type: string - parentID?: string - info?: { parentID?: string } - data?: Record - location?: { directory?: string; workspaceID?: string } -} const temporaryDirectories: string[] = [] @@ -29,15 +21,11 @@ const createProject = async (config: Record): Promise = const createContext = ( directory: string, - events: LifecycleEvent[] = [], + events: Record[] = [], ) => { const callbacks = new Map() const disposed: string[] = [] const syntheticCalls: Array> = [] - let eventsDrained: () => void - const eventsComplete = new Promise(resolve => { - eventsDrained = resolve - }) const context: V2Context = { tool: { hook: async (name, callback) => { @@ -48,7 +36,6 @@ const createContext = ( event: { subscribe: () => (async function* () { for (const event of events) yield event - eventsDrained() })(), }, session: { @@ -63,7 +50,7 @@ const createContext = ( }, } - return { context, callbacks, disposed, syntheticCalls, eventsComplete } + return { context, callbacks, disposed, syntheticCalls } } afterEach(async () => { @@ -202,57 +189,6 @@ describe("OpenCode V2 plugin", () => { await cleanup?.() }) - it("filters lifecycle hooks by resolved and fallback session scope without guessing unknown sessions", async () => { - const directory = await createProject({ - session: [ - { id: "parent", when: { event: "session.idle" }, inject: "parent" }, - { id: "child", when: { event: "session.idle", sessionScope: "child" }, inject: "child" }, - { id: "any", when: { event: "session.idle", sessionScope: "any" }, inject: "any" }, - ], - }) - const events = [ - { id: "parent", type: "session.idle", location: { directory }, data: { sessionID: "parent" } }, - { id: "child", type: "session.idle", location: { directory }, data: { sessionID: "child" } }, - { - id: "fallback-parent-id", - type: "session.idle", - location: { directory }, - data: { sessionID: "fallback-parent-id", parentID: "parent" }, - }, - { - id: "fallback-info-parent-id", - type: "session.execution.succeeded", - location: { directory }, - data: { sessionID: "fallback-info-parent-id", info: { parentID: "parent" } }, - }, - { id: "unknown", type: "session.idle", location: { directory }, data: { sessionID: "unknown" } }, - ] - const { context, eventsComplete, syntheticCalls } = createContext(directory, events) - context.session.get = async ({ sessionID }) => { - if (sessionID.startsWith("fallback") || sessionID === "unknown") { - throw new Error("session lookup failed") - } - return { - id: sessionID, - agent: "build", - location: { directory }, - ...(sessionID === "child" ? { parentID: "parent" } : {}), - } - } - - const cleanup = await createV2Plugin().setup(context) - await eventsComplete - - expect(syntheticCalls.map(call => call.text)).toEqual([ - "parent", "any", - "child", "any", - "child", "any", - "child", "any", - "any", - ]) - await cleanup?.() - }) - it("reports V2 toast degradation once without blocking injection", async () => { const directory = await createProject({ tool: [ From 9467255a5a03f86eb276244fd54ddb73fcca6c10 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:35:54 -0700 Subject: [PATCH 09/32] Revert "feat: scope session hooks to parent sessions" This reverts commit a167a950bb6d66644a9a5ed370d217cac7e7d651. --- README.md | 1 - src/executor.ts | 9 +--- src/index.ts | 17 ------ src/schemas.ts | 2 - src/types/hooks.ts | 7 +-- tests/session.scope.test.ts | 105 ------------------------------------ 6 files changed, 2 insertions(+), 139 deletions(-) delete mode 100644 tests/session.scope.test.ts diff --git a/README.md b/README.md index 79613b8..460ce46 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,6 @@ You can set up tool hooks to only trigger on specific arguments via `when.toolAr ## Features - Tool hooks (`before`/`after`) and session hooks (`start`/`idle`) via simple JSON/YAML frontmatter config - - Session hooks target parent sessions by default; opt into child or all sessions with `"when": { "event": "session.idle", "sessionScope": "child" }` or `"sessionScope": "any"`. - Hooks are **non-blocking**: failures don’t crash the session/tool. - Commands run **sequentially**, even if earlier ones fail. - Inject bash output into context with `inject` and notify user with `toast` diff --git a/src/executor.ts b/src/executor.ts index 1791734..da29efb 100644 --- a/src/executor.ts +++ b/src/executor.ts @@ -53,11 +53,7 @@ export const matches = (pattern: string | string[] | undefined, value: string | */ export const filterSessionHooks = ( hooks: SessionHook[], - criteria: { - event: string - agent: string | undefined - sessionScope: "parent" | "child" | undefined - } + criteria: { event: string; agent: string | undefined } ): SessionHook[] => { return hooks.filter((hook) => { // Normalize session.start to session.created @@ -75,9 +71,6 @@ export const filterSessionHooks = ( return false } - const hookScope = hook.when.sessionScope ?? "parent" - if (hookScope !== "any" && hookScope !== criteria.sessionScope) return false - return matches(hook.when.agent, criteria.agent) }) } diff --git a/src/index.ts b/src/index.ts index 7cd4210..3af0e30 100644 --- a/src/index.ts +++ b/src/index.ts @@ -122,21 +122,6 @@ const deleteToolArgs = (callId: string | undefined): void => { toolCallArgsCache.delete(callId) } -const getSessionScope = async ( - sessionId: string, - client: OpencodeClient, -): Promise<"parent" | "child" | undefined> => { - try { - const result = await client.session.get({ path: { id: sessionId } }) - if (!result.data) return undefined - return result.data.parentID === undefined ? "parent" : "child" - } catch (error) { - const message = error instanceof Error ? error.message : String(error) - logger.debug(`Could not determine scope for session ${sessionId}: ${message}`) - return undefined - } -} - /** @@ -164,12 +149,10 @@ const handleSessionEvent = async ( const markdownConfig = { tool: [], session: [] } const { config: mergedConfig } = mergeConfigs(globalConfig, markdownConfig) - const sessionScope = await getSessionScope(sessionId, client) const matchedHooks = filterSessionHooks(mergedConfig.session || [], { event: eventType, agent, - sessionScope, }) logger.debug( diff --git a/src/schemas.ts b/src/schemas.ts index ac6ef24..65959b3 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -27,7 +27,6 @@ const PhaseSchema = z.enum(["before", "after"]); * Note: "session.start" maps to "session.created" internally */ const SessionEventSchema = z.enum(["session.created", "session.idle", "session.end", "session.start"]); -const SessionScopeSchema = z.enum(["parent", "child", "any"]); // ============================================================================ // TOOL HOOK SCHEMAS @@ -98,7 +97,6 @@ export const ToolHookSchema = z.object({ const SessionHookWhenSchema = z.object({ event: SessionEventSchema, agent: StringOrArray.optional(), - sessionScope: SessionScopeSchema.default("parent"), }); /** diff --git a/src/types/hooks.ts b/src/types/hooks.ts index 5734faf..ee51200 100644 --- a/src/types/hooks.ts +++ b/src/types/hooks.ts @@ -168,7 +168,7 @@ export interface SessionHook { * Matching conditions for session hooks * * All specified conditions must match for the hook to execute. - * Omitted sessionScope defaults to matching parent sessions. + * Omitted fields default to matching all values (wildcard behavior). */ export interface SessionHookWhen { /** @@ -182,11 +182,6 @@ export interface SessionHookWhen { * Omitted: defaults to "*" in global config, "this agent" in markdown. */ agent?: string | string[] - - /** - * Session hierarchy to match. Defaults to "parent"; use "any" to match all sessions. - */ - sessionScope?: "parent" | "child" | "any" } /** diff --git a/tests/session.scope.test.ts b/tests/session.scope.test.ts deleted file mode 100644 index c9b428c..0000000 --- a/tests/session.scope.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test"; -import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs"; -import { tmpdir } from "os"; -import { join } from "path"; -import { filterSessionHooks } from "../src/executor"; -import { parseSessionHook } from "../src/schemas"; - -const ORIGINAL_CWD = process.cwd(); - -const sessionHook = (id: string, sessionScope?: "parent" | "child" | "any") => - parseSessionHook({ - id, - when: { event: "session.idle", ...(sessionScope ? { sessionScope } : {}) }, - inject: id, - })!; - -describe("session hook scope", () => { - it("defaults parsed session hooks to parent scope and accepts child and any", () => { - expect(sessionHook("default").when.sessionScope).toBe("parent"); - expect(sessionHook("child", "child").when.sessionScope).toBe("child"); - expect(sessionHook("any", "any").when.sessionScope).toBe("any"); - expect(parseSessionHook({ - id: "invalid", - when: { event: "session.idle", sessionScope: "all" }, - inject: "invalid", - })).toBeNull(); - }); - - it("matches parent, child, any, and unknown session identities deterministically", () => { - const hooks = [sessionHook("default"), sessionHook("child", "child"), sessionHook("any", "any")]; - - expect(filterSessionHooks(hooks, { - event: "session.idle", - agent: undefined, - sessionScope: "parent", - }).map((hook) => hook.id)).toEqual(["default", "any"]); - expect(filterSessionHooks(hooks, { - event: "session.idle", - agent: undefined, - sessionScope: "child", - }).map((hook) => hook.id)).toEqual(["child", "any"]); - expect(filterSessionHooks(hooks, { - event: "session.idle", - agent: undefined, - sessionScope: undefined, - }).map((hook) => hook.id)).toEqual(["any"]); - }); -}); - -describe("session lifecycle scope adapter", () => { - let testDir: string; - - beforeEach(() => { - testDir = mkdtempSync(join(tmpdir(), "opencode-hooks-session-scope-")); - mkdirSync(join(testDir, ".opencode"), { recursive: true }); - writeFileSync(join(testDir, ".opencode", "command-hooks.jsonc"), JSON.stringify({ - session: [ - { id: "default-parent", when: { event: "session.idle" }, inject: "default-parent" }, - { id: "child-only", when: { event: "session.idle", sessionScope: "child" }, inject: "child-only" }, - { id: "any-session", when: { event: "session.idle", sessionScope: "any" }, inject: "any-session" }, - ], - })); - process.chdir(testDir); - }); - - afterEach(() => { - process.chdir(ORIGINAL_CWD); - rmSync(testDir, { recursive: true, force: true }); - }); - - it("resolves session identity through client.session.get and only lets any hooks run when unknown", async () => { - const promptCalls: string[] = []; - const getCalls: string[] = []; - const client = { - session: { - get: async ({ path }: { path: { id: string } }) => { - getCalls.push(path.id); - if (path.id === "unknown") throw new Error("not found"); - return { data: path.id === "child" ? { parentID: "parent" } : {} }; - }, - promptAsync: async ({ body }: { body: { parts: Array<{ text: string }> } }) => { - promptCalls.push(body.parts[0].text); - return {}; - }, - }, - tui: { showToast: async () => ({}) }, - }; - - const { CommandHooksPlugin } = await import("../src/index.js"); - const plugin = await CommandHooksPlugin({ client } as never); - - for (const id of ["parent", "child", "unknown"]) { - await plugin.event?.({ - event: { type: "session.idle", properties: { sessionID: id } }, - } as never); - } - - expect(getCalls).toEqual(["parent", "child", "unknown"]); - expect(promptCalls).toEqual([ - "default-parent", "any-session", - "child-only", "any-session", - "any-session", - ]); - }); -}); From e73ad2f5e138028341f3f01653a3c3f92d7e5847 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:40:21 -0700 Subject: [PATCH 10/32] fix: suppress idle hooks during subagent waits --- README.md | 8 ++- src/executor.ts | 7 ++- src/index.ts | 44 +++++++++++---- src/schemas.ts | 1 + src/subagent-tracker.ts | 41 ++++++++++++++ src/types/hooks.ts | 3 + tests/session.subagent-wait.test.ts | 87 +++++++++++++++++++++++++++++ 7 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 src/subagent-tracker.ts create mode 100644 tests/session.subagent-wait.test.ts diff --git a/README.md b/README.md index 460ce46..841094a 100644 --- a/README.md +++ b/README.md @@ -391,13 +391,17 @@ Tool-arg matching is exact. This example runs only when the tool arg `path` equa }, { "id": "session-idle", - "when": { "event": "session.idle" }, - "run": ["echo 'Session idle'"], + "when": { "event": "session.idle", "excludeSubagentWait": true }, + "run": ["notify-user.sh 'Waiting for input'"], }, ], } ``` +`excludeSubagentWait` is opt-in. When true, that `session.idle` hook waits until all +active `task` subagent calls for the session have completed. Other idle hooks keep +their existing behavior and still run while the parent is waiting on a subagent. + --- ## Template Placeholders diff --git a/src/executor.ts b/src/executor.ts index da29efb..f9739e6 100644 --- a/src/executor.ts +++ b/src/executor.ts @@ -53,7 +53,7 @@ export const matches = (pattern: string | string[] | undefined, value: string | */ export const filterSessionHooks = ( hooks: SessionHook[], - criteria: { event: string; agent: string | undefined } + criteria: { event: string; agent: string | undefined; hasActiveSubagents?: boolean } ): SessionHook[] => { return hooks.filter((hook) => { // Normalize session.start to session.created @@ -63,6 +63,11 @@ export const filterSessionHooks = ( } if (normalizedEvent !== criteria.event) return false + if ( + normalizedEvent === "session.idle" && + hook.when.excludeSubagentWait && + criteria.hasActiveSubagents + ) return false if (hook.when.agent && !criteria.agent) { logger.debug( diff --git a/src/index.ts b/src/index.ts index 3af0e30..8d2bb4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { normalizeString } from "./utils.js" import { loadGlobalConfig } from "./config/global.js" import { loadAgentConfig } from "./config/agent.js" import { mergeConfigs } from "./config/merge.js" +import { createActiveSubagentTracker } from "./subagent-tracker.js" const notifyConfigError = async ( configError: string | null, @@ -133,6 +134,7 @@ const handleSessionEvent = async ( agent: string | undefined, client: OpencodeClient, host: HookHost, + hasActiveSubagents: boolean, ): Promise => { if (!sessionId) { logger.debug(`${eventType} event missing session ID`) @@ -153,6 +155,7 @@ const handleSessionEvent = async ( const matchedHooks = filterSessionHooks(mergedConfig.session || [], { event: eventType, agent, + hasActiveSubagents, }) logger.debug( @@ -270,6 +273,7 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { }) }, } + const activeSubagents = createActiveSubagentTracker() try { logger.info("Initializing OpenCode Command Hooks plugin...") @@ -312,7 +316,14 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { const sessionId = info?.id ? normalizeString(info.id) : undefined const agent = normalizeString(event.properties?.agent) - await handleSessionEvent("session.created", sessionId, agent, typedClient, host) + await handleSessionEvent( + "session.created", + sessionId, + agent, + typedClient, + host, + sessionId ? activeSubagents.hasActive(sessionId) : false, + ) } // Handle session.idle event @@ -322,7 +333,14 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { const sessionId = normalizeString(event.properties?.sessionID) const agent = normalizeString(event.properties?.agent) - await handleSessionEvent("session.idle", sessionId, agent, typedClient, host) + await handleSessionEvent( + "session.idle", + sessionId, + agent, + typedClient, + host, + sessionId ? activeSubagents.hasActive(sessionId) : false, + ) } // Backward-compat fallback for older OpenCode event streams. @@ -340,14 +358,16 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { event.properties?.callID ?? event.properties?.callId ) - if (!sessionId || !toolName) { + if (!sessionId || !toolName) { logger.debug( "tool.result event missing sessionID or tool name" ) - return - } + return + } - if (wasAfterHookProcessed(callId)) { + if (toolName === "task") activeSubagents.end(sessionId, callId) + + if (wasAfterHookProcessed(callId)) { logger.debug(`Skipping duplicate after-hook execution for callID: ${callId}`) deleteToolArgs(callId) return @@ -379,6 +399,8 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { `Tool args: ${JSON.stringify(output.args)}` ) + if (input.tool === "task") activeSubagents.begin(input.sessionID, input.callID) + await handleToolExecutionHook("before", input, output.args, typedClient, host) }, @@ -394,13 +416,15 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { `Received tool.execute.after for tool: ${input.tool}` ) - if (!toolOutput) { + if (!toolOutput) { logger.debug( `tool.execute.after for ${input.tool} has no output payload; running hooks with cached args only` - ) - } + ) + } + + if (input.tool === "task") activeSubagents.end(input.sessionID, input.callID) - const storedToolArgs = getToolArgs(input.callID) + const storedToolArgs = getToolArgs(input.callID) await handleToolExecutionHook("after", input, storedToolArgs, typedClient, host) }, } diff --git a/src/schemas.ts b/src/schemas.ts index 65959b3..82542bc 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -97,6 +97,7 @@ export const ToolHookSchema = z.object({ const SessionHookWhenSchema = z.object({ event: SessionEventSchema, agent: StringOrArray.optional(), + excludeSubagentWait: z.boolean().optional(), }); /** diff --git a/src/subagent-tracker.ts b/src/subagent-tracker.ts new file mode 100644 index 0000000..34bd952 --- /dev/null +++ b/src/subagent-tracker.ts @@ -0,0 +1,41 @@ +export type ActiveSubagentTracker = { + begin: (sessionId: string, callId?: string) => void + end: (sessionId: string, callId?: string) => void + hasActive: (sessionId: string) => boolean +} + +type ActiveCalls = { + ids: Set + anonymous: number +} + +export const createActiveSubagentTracker = (): ActiveSubagentTracker => { + const sessions = new Map() + + const state = (sessionId: string): ActiveCalls => { + const existing = sessions.get(sessionId) + if (existing) return existing + const created = { ids: new Set(), anonymous: 0 } + sessions.set(sessionId, created) + return created + } + + return { + begin: (sessionId, callId) => { + const active = state(sessionId) + if (callId) active.ids.add(callId) + else active.anonymous += 1 + }, + end: (sessionId, callId) => { + const active = sessions.get(sessionId) + if (!active) return + if (callId) active.ids.delete(callId) + else active.anonymous = Math.max(0, active.anonymous - 1) + if (active.ids.size === 0 && active.anonymous === 0) sessions.delete(sessionId) + }, + hasActive: sessionId => { + const active = sessions.get(sessionId) + return active !== undefined && (active.ids.size > 0 || active.anonymous > 0) + }, + } +} diff --git a/src/types/hooks.ts b/src/types/hooks.ts index ee51200..2050a2c 100644 --- a/src/types/hooks.ts +++ b/src/types/hooks.ts @@ -182,6 +182,9 @@ export interface SessionHookWhen { * Omitted: defaults to "*" in global config, "this agent" in markdown. */ agent?: string | string[] + + /** Skip this idle hook while the session has active subagent tool calls. */ + excludeSubagentWait?: boolean } /** diff --git a/tests/session.subagent-wait.test.ts b/tests/session.subagent-wait.test.ts new file mode 100644 index 0000000..1f4835e --- /dev/null +++ b/tests/session.subagent-wait.test.ts @@ -0,0 +1,87 @@ +import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs" +import { tmpdir } from "os" +import { join } from "path" +import { parseSessionHook } from "../src/schemas" + +const originalCwd = process.cwd() + +describe("session idle subagent waits", () => { + let directory: string + + beforeEach(() => { + directory = mkdtempSync(join(tmpdir(), "opencode-hooks-subagent-wait-")) + mkdirSync(join(directory, ".opencode"), { recursive: true }) + writeFileSync( + join(directory, ".opencode", "command-hooks.jsonc"), + JSON.stringify({ + session: [ + { id: "always", when: { event: "session.idle" }, inject: "always" }, + { + id: "after-subagents", + when: { event: "session.idle", excludeSubagentWait: true }, + inject: "after-subagents", + }, + ], + }), + ) + process.chdir(directory) + }) + + afterEach(() => { + process.chdir(originalCwd) + rmSync(directory, { recursive: true, force: true }) + }) + + it("accepts excludeSubagentWait as an opt-in session hook condition", () => { + const hook = parseSessionHook({ + id: "notify", + when: { event: "session.idle", excludeSubagentWait: true }, + inject: "notify", + }) + + expect(hook?.when.excludeSubagentWait).toBe(true) + }) + + it("suppresses only opted-in idle hooks until every task call finishes", async () => { + const injected: string[] = [] + const client = { + session: { + promptAsync: async ({ body }: { body: { parts: Array<{ text: string }> } }) => { + injected.push(body.parts[0].text) + return {} + }, + }, + tui: { showToast: async () => ({}) }, + } + const { CommandHooksPlugin } = await import("../src/index.js") + const plugin = await CommandHooksPlugin({ client } as never) + const idle = () => plugin.event?.({ + event: { type: "session.idle", properties: { sessionID: "parent" } }, + } as never) + + await plugin["tool.execute.before"]?.( + { tool: "task", sessionID: "parent", callID: "task-1" }, + { args: { subagent_type: "worker" } }, + ) + await plugin["tool.execute.before"]?.( + { tool: "task", sessionID: "parent", callID: "task-2" }, + { args: { subagent_type: "worker" } }, + ) + await idle() + + await plugin["tool.execute.after"]?.( + { tool: "task", sessionID: "parent", callID: "task-1" }, + undefined as never, + ) + await idle() + + await plugin["tool.execute.after"]?.( + { tool: "task", sessionID: "parent", callID: "task-2" }, + undefined as never, + ) + await idle() + + expect(injected).toEqual(["always", "always", "always", "after-subagents"]) + }) +}) From 1ef955769ed7f7a31dbdc93d58fe6ab4b8865be4 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:43:22 -0700 Subject: [PATCH 11/32] fix: suppress V2 idle hooks during subagent waits --- packages/v2/README.md | 2 ++ src/v2/plugin.ts | 10 ++++++- tests/v2.plugin.test.ts | 65 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/v2/README.md b/packages/v2/README.md index 8230ad0..ed8bca9 100644 --- a/packages/v2/README.md +++ b/packages/v2/README.md @@ -10,6 +10,8 @@ Beta package for OpenCode 2. It targets `@opencode-ai/cli@0.0.0-next-15853` and Tool before/after hooks, argument filters, agent hooks, session start/idle hooks, shell execution, and synthetic context injection are supported. Commands run in the session's project directory. +Set `"excludeSubagentWait": true` on a `session.idle` hook to suppress that hook while the session has active V2 `subagent` tool calls. The option is disabled by default. + OpenCode 2 server plugins cannot currently show TUI toasts. A toast setting produces one diagnostic while command execution and injection continue. Keep the V1 package and configuration available for rollback while OpenCode 2 remains beta. Verify loading with `opencode2 api get /api/plugin`. diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 4c2d270..8fe24bc 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -4,6 +4,7 @@ import { loadAgentConfig } from "../config/agent.js" import { mergeConfigs } from "../config/merge.js" import { executeHooks, filterSessionHooks, filterToolHooks, type HookHost } from "../executor.js" import { normalizeString } from "../utils.js" +import { createActiveSubagentTracker } from "../subagent-tracker.js" type V2ToolEvent = { tool: string @@ -97,6 +98,7 @@ export const createV2Plugin = (): V2Plugin => ({ const notifiedConfigErrors = new Set() const handledEvents = new Set() const startedSessions = new Set() + const activeSubagents = createActiveSubagentTracker() let warnedToastUnsupported = false let stopped = false @@ -150,9 +152,14 @@ export const createV2Plugin = (): V2Plugin => ({ } const handleTool = async (phase: "before" | "after", event: V2ToolEvent): Promise => { + const normalized = toolContext(event) + if (normalized.toolName === "task") { + if (phase === "before") activeSubagents.begin(event.sessionID, event.callID) + else activeSubagents.end(event.sessionID, event.callID) + } + try { const resolved = await sessionInfo(event.sessionID, { agent: event.agent }) - const normalized = toolContext(event) const { config: globalConfig, error } = await loadGlobalConfig(resolved.directory) notifyConfigError(error, resolved.directory) @@ -218,6 +225,7 @@ export const createV2Plugin = (): V2Plugin => ({ const hooks = filterSessionHooks(config.session ?? [], { event: eventType, agent: resolved.agent, + hasActiveSubagents: activeSubagents.hasActive(sessionID), }) await executeHooks( hooks, diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index 8ca3505..ba625c5 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -189,6 +189,71 @@ describe("OpenCode V2 plugin", () => { await cleanup?.() }) + it("suppresses opted-in idle hooks while a V2 subagent tool is active", async () => { + const directory = await createProject({ + session: [ + { id: "always", when: { event: "session.idle" }, inject: "always" }, + { + id: "after-subagents", + when: { event: "session.idle", excludeSubagentWait: true }, + inject: "after-subagents", + }, + ], + }) + const { context, callbacks, syntheticCalls } = createContext(directory) + let releaseFirst!: () => void + let releaseSecond!: () => void + let waitingForSecond!: () => void + let eventsFinished!: () => void + const first = new Promise(resolve => { releaseFirst = resolve }) + const second = new Promise(resolve => { releaseSecond = resolve }) + const firstProcessed = new Promise(resolve => { waitingForSecond = resolve }) + const complete = new Promise(resolve => { eventsFinished = resolve }) + context.event.subscribe = () => (async function* () { + await first + yield { + id: "idle-active", + type: "session.idle", + location: { directory }, + data: { sessionID: "parent" }, + } + waitingForSecond() + await second + yield { + id: "idle-complete", + type: "session.idle", + location: { directory }, + data: { sessionID: "parent" }, + } + eventsFinished() + })() + + const cleanup = await createV2Plugin().setup(context) + const subagent = { + tool: "subagent", + sessionID: "parent", + callID: "subagent-1", + agent: "build", + input: { agent: "worker" }, + } + await callbacks.get("execute.before")?.(subagent) + releaseFirst() + await firstProcessed + + expect(syntheticCalls.map(call => call.text)).toEqual(["always"]) + + await callbacks.get("execute.after")?.(subagent) + releaseSecond() + await complete + + expect(syntheticCalls.map(call => call.text)).toEqual([ + "always", + "always", + "after-subagents", + ]) + await cleanup?.() + }) + it("reports V2 toast degradation once without blocking injection", async () => { const directory = await createProject({ tool: [ From f8c9ef971f68919edb94f6e9be4436f83fae0ea0 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:47:02 -0700 Subject: [PATCH 12/32] fix: clear subagent state for deleted sessions --- src/index.ts | 10 ++++++++-- src/subagent-tracker.ts | 4 ++++ tests/session.subagent-wait.test.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8d2bb4b..1e7619a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -324,9 +324,15 @@ export const CommandHooksPlugin: Plugin = async ({ client }) => { host, sessionId ? activeSubagents.hasActive(sessionId) : false, ) - } + } + + if (event.type === "session.deleted") { + const info = event.properties?.info as { id?: string } | undefined + const sessionId = info?.id ? normalizeString(info.id) : undefined + if (sessionId) activeSubagents.clear(sessionId) + } - // Handle session.idle event + // Handle session.idle event if (event.type === "session.idle") { logger.debug("Received session.idle event") diff --git a/src/subagent-tracker.ts b/src/subagent-tracker.ts index 34bd952..6400239 100644 --- a/src/subagent-tracker.ts +++ b/src/subagent-tracker.ts @@ -2,6 +2,7 @@ export type ActiveSubagentTracker = { begin: (sessionId: string, callId?: string) => void end: (sessionId: string, callId?: string) => void hasActive: (sessionId: string) => boolean + clear: (sessionId: string) => void } type ActiveCalls = { @@ -37,5 +38,8 @@ export const createActiveSubagentTracker = (): ActiveSubagentTracker => { const active = sessions.get(sessionId) return active !== undefined && (active.ids.size > 0 || active.anonymous > 0) }, + clear: sessionId => { + sessions.delete(sessionId) + }, } } diff --git a/tests/session.subagent-wait.test.ts b/tests/session.subagent-wait.test.ts index 1f4835e..3bd34e7 100644 --- a/tests/session.subagent-wait.test.ts +++ b/tests/session.subagent-wait.test.ts @@ -84,4 +84,32 @@ describe("session idle subagent waits", () => { expect(injected).toEqual(["always", "always", "always", "after-subagents"]) }) + + it("clears active subagent state when the session is deleted", async () => { + const injected: string[] = [] + const client = { + session: { + promptAsync: async ({ body }: { body: { parts: Array<{ text: string }> } }) => { + injected.push(body.parts[0].text) + return {} + }, + }, + tui: { showToast: async () => ({}) }, + } + const { CommandHooksPlugin } = await import("../src/index.js") + const plugin = await CommandHooksPlugin({ client } as never) + + await plugin["tool.execute.before"]?.( + { tool: "task", sessionID: "deleted", callID: "task-deleted" }, + { args: { subagent_type: "worker" } }, + ) + await plugin.event?.({ + event: { type: "session.deleted", properties: { info: { id: "deleted" } } }, + } as never) + await plugin.event?.({ + event: { type: "session.idle", properties: { sessionID: "deleted" } }, + } as never) + + expect(injected).toEqual(["always", "after-subagents"]) + }) }) From 1e707abfe88bd0352bd4c2eb3d1f1204f2ed7725 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:48:46 -0700 Subject: [PATCH 13/32] fix: clear V2 subagent state on session deletion --- src/v2/plugin.ts | 6 ++++++ tests/v2.plugin.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 8fe24bc..912aaf5 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -190,6 +190,12 @@ export const createV2Plugin = (): V2Plugin => ({ } const handleSession = async (event: V2Event): Promise => { + if (event.type === "session.deleted") { + const sessionID = normalizeString(event.data?.sessionID) + if (sessionID) activeSubagents.clear(sessionID) + return + } + const eventType = event.type === "session.created" || event.type === "session.execution.started" ? "session.created" : event.type === "session.idle" || diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index ba625c5..6fc3c83 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -232,7 +232,6 @@ describe("OpenCode V2 plugin", () => { const subagent = { tool: "subagent", sessionID: "parent", - callID: "subagent-1", agent: "build", input: { agent: "worker" }, } @@ -254,6 +253,47 @@ describe("OpenCode V2 plugin", () => { await cleanup?.() }) + it("clears active V2 subagent state when the session is deleted", async () => { + const directory = await createProject({ + session: [ + { + id: "after-subagents", + when: { event: "session.idle", excludeSubagentWait: true }, + inject: "after-subagents", + }, + ], + }) + const { context, callbacks, syntheticCalls } = createContext(directory) + let releaseEvents!: () => void + let eventsFinished!: () => void + const release = new Promise(resolve => { releaseEvents = resolve }) + const complete = new Promise(resolve => { eventsFinished = resolve }) + context.event.subscribe = () => (async function* () { + await release + yield { id: "deleted", type: "session.deleted", data: { sessionID: "parent" } } + yield { + id: "idle-after-delete", + type: "session.idle", + location: { directory }, + data: { sessionID: "parent" }, + } + eventsFinished() + })() + + const cleanup = await createV2Plugin().setup(context) + await callbacks.get("execute.before")?.({ + tool: "subagent", + sessionID: "parent", + agent: "build", + input: { agent: "worker" }, + }) + releaseEvents() + await complete + + expect(syntheticCalls.map(call => call.text)).toEqual(["after-subagents"]) + await cleanup?.() + }) + it("reports V2 toast degradation once without blocking injection", async () => { const directory = await createProject({ tool: [ From 0c54769e82176f1b1a207aec416ad998709e11ea Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:47:00 -0400 Subject: [PATCH 14/32] test: exercise current OpenCode hosts --- .github/workflows/v1-e2e.yml | 2 +- tests/e2e.v2.test.ts | 165 ++++++++++++++++++++++++++++++----- 2 files changed, 146 insertions(+), 21 deletions(-) diff --git a/.github/workflows/v1-e2e.yml b/.github/workflows/v1-e2e.yml index d91580a..2050b04 100644 --- a/.github/workflows/v1-e2e.yml +++ b/.github/workflows/v1-e2e.yml @@ -23,7 +23,7 @@ jobs: run: bun install --frozen-lockfile - name: Install OpenCode V1 - run: bun install --global opencode-ai@1.18.3 + run: bun install --global opencode-ai@latest - name: Run V1 headless E2E tests run: npm run test:v1:e2e diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index 7acfebe..618deb5 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -1,12 +1,66 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test" -import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "fs/promises" +import { access, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "fs/promises" import { tmpdir } from "os" import { join, resolve } from "path" import { $ } from "bun" const enabled = process.env.OPENCODE2_E2E === "1" -const cliVersion = "0.0.0-beta-18684" +const cliVersion = "beta" let projectDirectory = "" +let binary = "" +let homeDirectory = "" +let environment: Record = {} + +const runCommand = async (args: string[], timeoutMs = 120_000) => { + const process = Bun.spawn([binary, ...args], { + cwd: projectDirectory, + env: environment, + stdout: "pipe", + stderr: "pipe", + }) + const timeout = setTimeout(() => process.kill(9), timeoutMs) + try { + const [exitCode, stdout, stderr] = await Promise.all([ + process.exited, + new Response(process.stdout).text(), + new Response(process.stderr).text(), + ]) + return { exitCode, stdout, stderr } + } finally { + clearTimeout(timeout) + } +} + +const selectFreeModel = (output: string): string => { + const models = output.split("\n").map(line => line.trim()) + const model = ["opencode/mimo-v2.5-free", "opencode/ling-3.0-flash-fin-free"] + .find(candidate => models.includes(candidate)) + ?? models.find(candidate => candidate.startsWith("opencode/") && candidate.endsWith("-free")) + if (!model) throw new Error(`OpenCode 2 did not advertise a credential-free model:\n${output}`) + return model +} + +const missingFiles = async (paths: string[]): Promise => { + const checked = await Promise.all(paths.map(async path => { + try { + await access(path) + return undefined + } catch { + return path + } + })) + return checked.filter((path): path is string => path !== undefined) +} + +const waitForFiles = async (paths: string[], timeoutMs = 10_000): Promise => { + const deadline = Date.now() + timeoutMs + let missing = await missingFiles(paths) + while (missing.length > 0 && Date.now() < deadline) { + await new Promise(resolve => setTimeout(resolve, 100)) + missing = await missingFiles(paths) + } + return missing +} describe("OpenCode V2 package E2E", () => { beforeAll(async () => { @@ -20,7 +74,7 @@ describe("OpenCode V2 package E2E", () => { join(projectDirectory, "package.json"), JSON.stringify({ private: true, type: "module" }), ) - await $`npm install ${join(projectDirectory, "opencode-command-hooks-v2-0.1.0-beta.0.tgz")} @opencode-ai/cli@${cliVersion}` + await $`npm install ${join(projectDirectory, "opencode-command-hooks-v2-0.1.0-beta.0.tgz")} ${`@opencode-ai/cli@${cliVersion}`}` .cwd(projectDirectory) .quiet() await mkdir(join(projectDirectory, ".opencode"), { recursive: true }) @@ -34,6 +88,21 @@ describe("OpenCode V2 package E2E", () => { join(projectDirectory, ".opencode", "command-hooks.jsonc"), JSON.stringify({ tool: [], session: [] }), ) + binary = resolve(projectDirectory, "node_modules", ".bin", "opencode2") + homeDirectory = join(projectDirectory, "home") + await mkdir(homeDirectory, { recursive: true }) + environment = { + ...process.env, + PWD: projectDirectory, + HOME: homeDirectory, + XDG_CONFIG_HOME: join(homeDirectory, ".config"), + XDG_DATA_HOME: join(homeDirectory, ".local", "share"), + XDG_CACHE_HOME: join(homeDirectory, ".cache"), + OPENCODE_CONFIG: join(projectDirectory, "opencode.jsonc"), + OPENCODE_LOG_LEVEL: "trace", + OPENCODE_PASSWORD: "v2-e2e-password", + OPENCODE_SERVER_PASSWORD: "v2-e2e-password", + } }, 120_000) afterAll(async () => { @@ -42,27 +111,12 @@ describe("OpenCode V2 package E2E", () => { } }) - it("loads the packed plugin in the pinned OpenCode 2 host", async () => { + it("loads the packed plugin in the current OpenCode 2 beta host", async () => { if (!enabled) { console.log("Skipping V2 E2E: set OPENCODE2_E2E=1 to run") return } - const binary = resolve(projectDirectory, "node_modules", ".bin", "opencode2") - const config = join(projectDirectory, "opencode.jsonc") - const home = join(projectDirectory, "home") - await mkdir(home, { recursive: true }) - const environment = { - ...process.env, - HOME: home, - XDG_CONFIG_HOME: join(home, ".config"), - XDG_DATA_HOME: join(home, ".local", "share"), - XDG_CACHE_HOME: join(home, ".cache"), - OPENCODE_CONFIG: config, - OPENCODE_LOG_LEVEL: "trace", - OPENCODE_PASSWORD: "v2-e2e-password", - OPENCODE_SERVER_PASSWORD: "v2-e2e-password", - } const server = Bun.spawn([ binary, "serve", @@ -101,7 +155,7 @@ describe("OpenCode V2 package E2E", () => { "x-opencode-directory": projectDirectory, authorization: `Basic ${Buffer.from("opencode:v2-e2e-password").toString("base64")}`, } - const logDirectory = join(home, ".local", "share", "opencode", "log") + const logDirectory = join(homeDirectory, ".local", "share", "opencode", "log") const readLogs = async (): Promise => { try { const files = await readdir(logDirectory) @@ -154,4 +208,75 @@ describe("OpenCode V2 package E2E", () => { expect(pluginResponse, logs).toContain("opencode-command-hooks.v2") }, 90_000) + + it("executes tool and session hooks in the current OpenCode 2 beta host", async () => { + if (!enabled) return + + const evidence = { + before: join(projectDirectory, "tool-before.txt"), + after: join(projectDirectory, "tool-after.txt"), + started: join(projectDirectory, "session-started.txt"), + idle: join(projectDirectory, "session-idle.txt"), + model: join(projectDirectory, "model-tool.txt"), + } + await writeFile( + join(projectDirectory, ".opencode", "command-hooks.jsonc"), + JSON.stringify({ + tool: [ + { + id: "v2-real-before", + when: { phase: "before", tool: "*" }, + run: "printf before > tool-before.txt", + }, + { + id: "v2-real-after", + when: { phase: "after", tool: "*" }, + run: "printf after > tool-after.txt", + inject: "V2_AFTER_HOOK_EXECUTED", + }, + ], + session: [ + { + id: "v2-real-start", + when: { event: "session.start" }, + run: "printf started > session-started.txt", + }, + { + id: "v2-real-idle", + when: { event: "session.idle" }, + run: "printf idle > session-idle.txt", + }, + ], + }), + ) + + const models = await runCommand(["models", "--standalone"]) + expect(models.exitCode, models.stderr).toBe(0) + const model = selectFreeModel(models.stdout) + const result = await runCommand([ + "run", + "--standalone", + "--auto", + "--model", + model, + "Use the shell tool to run exactly: printf model-tool > model-tool.txt. Then reply DONE.", + ]) + + expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(0) + const evidencePaths = Object.values(evidence) + const missing = await waitForFiles(evidencePaths) + const logDirectory = join(homeDirectory, ".local", "share", "opencode", "log") + const logs = await readdir(logDirectory) + .then(files => Promise.all(files.map(file => readFile(join(logDirectory, file), "utf8")))) + .then(files => files.join("\n")) + .catch(() => "") + expect(missing, `${result.stdout}\n${result.stderr}\n${logs}`).toEqual([]) + expect(logs).not.toContain("Failed to handle tool.execute.after") + expect(logs).not.toContain("Failed to inject message") + expect(await readFile(evidence.before, "utf8")).toBe("before") + expect(await readFile(evidence.after, "utf8")).toBe("after") + expect(await readFile(evidence.started, "utf8")).toBe("started") + expect(await readFile(evidence.idle, "utf8")).toBe("idle") + expect(await readFile(evidence.model, "utf8")).toBe("model-tool") + }, 180_000) }) From a6ab4ce7e86588e18d4ee54d6f623f71ac65b83b Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:35:02 -0400 Subject: [PATCH 15/32] feat: support V1 and V2 from one package --- .github/workflows/ci.yml | 8 +-- .github/workflows/release-v2.yml | 92 -------------------------------- .github/workflows/release.yml | 3 ++ OPENCODE_V2_IMPACT_REPORT.md | 59 ++++++++++---------- README.md | 10 ++-- package.json | 20 ++++--- packages/v2/LICENSE | 21 -------- packages/v2/README.md | 17 ------ packages/v2/package.json | 40 -------------- src/server.ts | 9 ++++ src/v2/plugin.ts | 4 +- tests/e2e.v1-headless.test.ts | 20 ++++++- tests/e2e.v2.test.ts | 13 +++-- tests/v2.package.test.ts | 31 +++++++---- tsconfig.build.json | 2 +- 15 files changed, 111 insertions(+), 238 deletions(-) delete mode 100644 .github/workflows/release-v2.yml delete mode 100644 packages/v2/LICENSE delete mode 100644 packages/v2/README.md delete mode 100644 packages/v2/package.json create mode 100644 src/server.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a16d477..429954b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,15 +38,11 @@ jobs: - name: Test run: npm test - - name: Build V1 and V2 + - name: Build dual-host package run: npm run build:all - - name: Verify V1 package + - name: Verify package run: npm pack --dry-run - - name: Verify V2 package - run: npm pack --dry-run - working-directory: packages/v2 - - name: Test packed package in OpenCode 2 run: npm run test:v2:e2e diff --git a/.github/workflows/release-v2.yml b/.github/workflows/release-v2.yml deleted file mode 100644 index 78ab029..0000000 --- a/.github/workflows/release-v2.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: release-v2 - -on: - push: - tags: - - "v2-*" - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - -permissions: - contents: read - id-token: write - -jobs: - verify: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: "24" - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: "1.3.9" - - - name: Validate tag and package version - run: | - node -e ' - const fs = require("node:fs"); - const tag = process.env.GITHUB_REF_NAME || ""; - const version = JSON.parse(fs.readFileSync("packages/v2/package.json", "utf8")).version; - if (tag !== `v2-${version}`) { - throw new Error(`tag/version mismatch: tag=${tag} package=${version}`); - } - ' - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Verify repository - run: npm run lint && npm run typecheck && npm test - - - name: Build and pack V2 - run: npm --prefix ../.. run build:v2 && npm pack --dry-run - working-directory: packages/v2 - - - name: Test packed package in OpenCode 2 - run: npm run test:v2:e2e - - publish: - needs: verify - runs-on: ubuntu-latest - permissions: - contents: write - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: "24" - registry-url: "https://registry.npmjs.org" - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: "1.3.9" - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Publish V2 beta - run: npm publish --access public --provenance --tag next - working-directory: packages/v2 - - - name: Create GitHub prerelease - env: - GH_TOKEN: ${{ github.token }} - run: | - if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then - echo "GitHub release already exists: $GITHUB_REF_NAME" - else - gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes --title "$GITHUB_REF_NAME" --prerelease --latest=false - fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1ee9d70..4b0e846 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,6 +63,9 @@ jobs: - name: Pack dry-run run: npm pack --dry-run + - name: Test packed package in OpenCode 2 + run: npm run test:v2:e2e + publish-npm: needs: verify runs-on: ubuntu-latest diff --git a/OPENCODE_V2_IMPACT_REPORT.md b/OPENCODE_V2_IMPACT_REPORT.md index 862ce51..996b603 100644 --- a/OPENCODE_V2_IMPACT_REPORT.md +++ b/OPENCODE_V2_IMPACT_REPORT.md @@ -1,20 +1,20 @@ # OpenCode 2 Impact and Migration Report -Status: Implemented beta prototype -Last updated: 2026-07-20 +Status: Implemented dual-host beta adapter +Last updated: 2026-08-30 Doc Class: report Doc Type: research Report Type: engineering migration -Decision Status: accepted for opt-in beta +Decision Status: accepted for one-package dual-host distribution Authority: advisory Canonical Source: `OPENCODE_V2_IMPACT_REPORT.md` Report topic: OpenCode 2 plugin and client migration ## Summary -OpenCode 2 is currently a beta distributed separately from stable OpenCode 1. The upstream project explicitly warns that its APIs, configuration, data, and plugin APIs may still change. It also explicitly states that OpenCode 1 plugins do not work in OpenCode 2. +OpenCode 2 is currently a beta distributed separately from stable OpenCode 1. The upstream project warns that its APIs, configuration, data, and plugin APIs may still change. V1 and V2 plugin modules use incompatible runtime contracts, but current OpenCode hosts can select different modules from one npm package. -`opencode-command-hooks` will not load in OpenCode 2 in its current form. The package exports an OpenCode 1 plugin function that returns a hooks object. OpenCode 2 requires a default plugin descriptor with a unique `id` and `setup` or `effect` function, and hooks are registered imperatively through the new context. +`opencode-command-hooks` now exports a V2 descriptor with a unique `id` and `setup` function from its package root. Its `./server` export provides the V1 `{ id, server }` descriptor. OpenCode V1 1.18.23 or newer selects `./server`, while OpenCode V2 selects the root. The current OpenCode 2 beta has enough API surface to prototype most core behavior: @@ -23,7 +23,7 @@ The current OpenCode 2 beta has enough API surface to prototype most core behavi - `ctx.session.synthetic()` appears to be the intended replacement for injecting context without creating a normal user prompt. - Existing v1-shaped config and agent Markdown locations are intended to be translated in memory. -However, a production migration should not begin yet. Important contracts remain unstable or incomplete: +Important V2 contracts remain unstable or incomplete: - The beta guide and current `v2` branch disagree on at least one hook name: the guide uses `ctx.session.hook("request")`, while source currently defines `ctx.session.hook("context")`. - The guide and source also differ on some tool context field names. @@ -31,21 +31,21 @@ However, a production migration should not begin yet. Important contracts remain - Plugin reload isolation and config-directory reload have open defects. - The package must determine the correct location/workspace rather than use `process.cwd()` in the long-lived v2 service. -The recommended release strategy is to preserve `opencode-command-hooks` as the v1 line and publish any beta port under a separate package name, such as `opencode-command-hooks-v2`, using exact prerelease versions. Do not move the existing package's npm `latest` tag to a v2-only artifact while a large v1 population remains. +The release strategy is one package with host-specific entrypoints. Existing users retain `"plugin": ["opencode-command-hooks"]`; V2 migrates that V1 config field internally. OpenCode V1 versions older than 1.18.23 are outside the supported range because they do not select `./server`. ## Implementation Addendum The opt-in beta adapter is now implemented in this repository: - `src/v2/plugin.ts` owns the V2 Promise adapter and keeps callback failures non-blocking. -- `packages/v2` produces the separate `opencode-command-hooks-v2` package without changing the V1 package identity or release tag. -- The package pins `@opencode-ai/plugin@0.0.0-beta-18684` exactly. +- `src/v2.ts` builds to the package root and `src/server.ts` builds to the V1 `./server` export. +- Development pins the V2 plugin contract to `@opencode-ai/plugin@0.0.0-beta-18684` through an npm alias. - Tool hooks use direct V2 event input, and `subagent`/`agent` are normalized to the existing `task`/`subagent_type` config vocabulary. - Session lookup supplies the authoritative project directory and agent for config discovery and command execution. - Injection uses `ctx.session.synthetic()` with `resume: false`; toast requests produce one explicit unsupported diagnostic. - The event consumer maps compatibility and durable execution events to `session.start`/`session.idle`, deduplicates event IDs and session starts, and is closed during plugin cleanup. - Unit, V1 regression, packed-artifact, and gated pinned-host E2E tests cover the adapter. -- A dedicated V2 release workflow requires the pinned host E2E to pass before npm publication. +- The normal release workflow requires the current V2 host E2E to pass before publishing the shared package. The first one-shot standalone E2E queried the plugin list before asynchronous plugin activation completed. The corrected test starts an isolated persistent server, polls the location-scoped plugin endpoint until setup finishes, and passes against `0.0.0-beta-18684`. It uses temporary HOME/XDG directories and a test-only server password, so it does not connect to or modify the normal OpenCode service. @@ -275,12 +275,12 @@ V2 intends to translate v1 config in memory. Existing users can keep: } ``` -Native v2 configuration uses: +Native v2 configuration may use: ```jsonc { "plugins": [ - "opencode-command-hooks-v2@" + "opencode-command-hooks" ] } ``` @@ -325,34 +325,29 @@ Confidence: high. ### Decision -Keep the existing npm package as the v1 product line. Publish the v2 beta port under a separate package name and exact versions. - -Suggested naming: - -- V1: `opencode-command-hooks` -- V2 beta: `opencode-command-hooks-v2` +Publish one `opencode-command-hooks` package with a V2 root export and a V1 `./server` export. Why this is safest: -- Existing bare v1 specs continue resolving to a v1-compatible artifact. -- Moving npm `latest` cannot accidentally break all unpinned v1 users. -- V2 users opt in explicitly and can roll back by removing one package and restoring the other. -- Each package can use independent dependencies, entrypoints, tests, and release cadence while the v2 API churns. -- Support communication is unambiguous. +- Existing configuration keeps the same package name through a host upgrade. +- Current V1 hosts select `./server` without loading the incompatible V2 descriptor. +- V2 hosts load the package root using the documented V2 descriptor contract. +- Both adapters share config, matching, execution, and template behavior. +- One packed artifact can be tested against both hosts before publication. ### Alternatives considered | Option | Benefit | Risk | Recommendation | | --- | --- | --- | --- | -| Separate v2 package | Strongest isolation and rollback | Temporary package-name fragmentation | Recommended during beta and transition | -| Same package, v2 on `next`/`v2` dist-tag | Preserves one package name | A future `latest` change can break v1 users; mutable tags complicate reproducibility | Acceptable only with exact versions and strict release discipline | +| Separate v2 package | Strong isolation | Package-name fragmentation and config changes | Rejected because host-specific exports are supported | +| Same package, v2 on `next`/`v2` dist-tag | Preserves one package name | Hosts still resolve one package version and users must pin | Not needed | | Same package, v2 major on `latest` | Conventional semver | OpenCode auto-installs bare specs; many users do not pin plugin versions | Do not do while v1 remains common | -| One dual-runtime root export | One artifact | Hosts do not select a major-specific export; loader contracts differ; older v1 loaders may reject descriptors | Do not use as the primary strategy | +| One package with `.` and `./server` | One artifact and unchanged config | Requires OpenCode V1 1.18.23 or newer | Selected | ### Version policy -- Keep npm `latest` for `opencode-command-hooks` on the supported v1 line. -- Publish v2 beta releases only as exact versions of the separate package. +- Publish both adapters together under the existing `opencode-command-hooks` version. +- Keep the V2 development dependency pinned to the exact supported beta build. - Pin `@opencode-ai/plugin` and other v2 packages to the exact matching beta build. - Do not depend on `next`, `beta`, or `latest` ranges inside a production plugin release. - Add host compatibility metadata when upstream confirms enforcement for the target host, but treat it as a guardrail rather than the compatibility mechanism. @@ -483,13 +478,13 @@ Exit criteria: - All expected degradations are documented and surfaced to users. - Open v2 reload defects are fixed upstream or mitigated and tested. -### Gate 5: Publish an opt-in v2 beta package +### Gate 5: Publish the dual-host package Actions: -- Publish `opencode-command-hooks-v2@` without changing the v1 package's `latest` tag. +- Publish `opencode-command-hooks` only after the packed artifact passes both host suites. - State the exact supported OpenCode 2 beta build. -- Provide side-by-side install, verification, and rollback steps. +- Document the OpenCode V1 1.18.23 minimum and unchanged package configuration. - Require users to verify active plugin IDs with `opencode2 api get /api/plugin`. - Collect issue reports with host version, plugin version, event type, and server logs. @@ -611,7 +606,7 @@ Rollback triggers: - [x] Recheck the beta guide, exact published package types, and `v2` source before implementation. - [ ] Obtain upstream clarification on session hook naming, location access, toast/logging, and synthetic delivery. - [x] Add packed V2 artifact verification while preserving the V1 regression suite. -- [x] Use `opencode-command-hooks-v2` as the isolated beta package name. +- [x] Replace the isolated beta package with V2 `.` and V1 `./server` exports. - [ ] Confirm npm ownership before publishing any prerelease. - [x] Enable the isolated pinned-host E2E loading gate. - [ ] Expand to a dual-host runtime matrix when the blocking host defects are resolved. diff --git a/README.md b/README.md index 1fe7ff7..88f4197 100644 --- a/README.md +++ b/README.md @@ -171,23 +171,25 @@ Add to your `opencode.json`: } ``` +The dual-host package requires OpenCode V1 1.18.23 or newer. Older V1 releases do not recognize the package's `./server` entrypoint. + ## OpenCode 2 Beta -OpenCode 2 uses a different plugin API, so the V1 package above does not load there. The opt-in beta artifact is built separately and pins the exact OpenCode 2 plugin contract it supports. Once the beta package is published, its config is: +The same `opencode-command-hooks` package supports OpenCode V1 and V2. OpenCode V1 1.18.23 or newer automatically loads the package's `./server` export, while OpenCode V2 loads the package root. Existing V1 configuration therefore remains valid when upgrading hosts: ```jsonc { - "plugins": ["opencode-command-hooks-v2@0.1.0-beta.0"] + "plugin": ["opencode-command-hooks"] } ``` -Version `0.1.0-beta.0` targets `@opencode-ai/cli@0.0.0-beta-18684`. Keep versions exact while the upstream API is beta, and verify the plugin ID with `opencode2 api get /api/plugin` after the host finishes activating plugins. +The V2 adapter currently targets `@opencode-ai/cli@0.0.0-beta-18684`. OpenCode 2 migrates the singular V1 `plugin` setting to its `plugins` representation when loading the config. Verify the plugin ID with `opencode2 api get /api/plugin` after the host finishes activating plugins. The V2 adapter supports tool before/after hooks, `toolArgs` filters, agent frontmatter hooks, session start/idle hooks, explicit project-directory execution, and injection through synthetic context. Existing V1 config vocabulary remains valid: the V2 `subagent` tool and its `agent` argument are normalized to `task` and `subagent_type` for matching. V2 server plugins cannot currently show TUI toasts. When a matched hook requests one, the adapter emits one diagnostic and continues command execution and injection. Synthetic injections use `resume: false` so an idle hook does not create an unsolicited model turn. -Run the pinned host smoke test with `npm run test:v2:e2e`. Keep the V1 package available for rollback while OpenCode 2 remains beta. +Run the current beta host smoke test with `npm run test:v2:e2e`. ## Configuration diff --git a/package.json b/package.json index ce6b547..756ecd2 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,18 @@ { "name": "opencode-command-hooks", - "version": "0.6.1", + "version": "0.7.0", "description": "OpenCode plugin for declaratively attaching shell commands to agent/tool/slash-command lifecycle events", "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "main": "dist/v2.js", + "types": "dist/v2.d.ts", "exports": { ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" + "types": "./dist/v2.d.ts", + "import": "./dist/v2.js" + }, + "./server": { + "types": "./dist/server.d.ts", + "import": "./dist/server.js" } }, "files": [ @@ -17,8 +21,7 @@ ], "scripts": { "build": "rm -rf dist && tsc -p tsconfig.build.json", - "build:v2": "bun build src/v2.ts --outfile packages/v2/dist/index.js --target=bun", - "build:all": "npm run build && npm run build:v2", + "build:all": "npm run build", "test": "bun test", "test:v2": "bun test tests/v2.plugin.test.ts tests/v2.package.test.ts", "test:v2:e2e": "OPENCODE2_E2E=1 bun test tests/e2e.v2.test.ts", @@ -69,6 +72,7 @@ "typescript-eslint": "^8.47.0" }, "engines": { - "bun": ">=1.0.0" + "bun": ">=1.0.0", + "opencode": ">=1.18.23" } } diff --git a/packages/v2/LICENSE b/packages/v2/LICENSE deleted file mode 100644 index 18b4ee6..0000000 --- a/packages/v2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Shane Bishop - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/v2/README.md b/packages/v2/README.md deleted file mode 100644 index e0a21fd..0000000 --- a/packages/v2/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# opencode-command-hooks-v2 - -Beta package for OpenCode 2. It targets `@opencode-ai/cli@0.0.0-beta-18684` and uses the same `.opencode/command-hooks.jsonc` and agent frontmatter configuration as the V1 package. - -```jsonc -{ - "plugins": ["opencode-command-hooks-v2@0.1.0-beta.0"] -} -``` - -Tool before/after hooks, argument filters, agent hooks, session start/idle hooks, shell execution, and synthetic context injection are supported. Commands run in the session's project directory. - -Set `"excludeSubagentWait": true` on a `session.idle` hook to suppress that hook while the session has active V2 `subagent` tool calls. The option is disabled by default. - -OpenCode 2 server plugins cannot currently show TUI toasts. A toast setting produces one diagnostic while command execution and injection continue. - -Keep the V1 package and configuration available for rollback while OpenCode 2 remains beta. Verify loading with `opencode2 api get /api/plugin`. diff --git a/packages/v2/package.json b/packages/v2/package.json deleted file mode 100644 index bc7bf71..0000000 --- a/packages/v2/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "opencode-command-hooks-v2", - "version": "0.1.0-beta.0", - "description": "OpenCode 2 beta adapter for declarative command hooks", - "type": "module", - "main": "dist/index.js", - "exports": "./dist/index.js", - "files": [ - "dist", - "LICENSE", - "README.md" - ], - "scripts": { - "prepack": "npm --prefix ../.. run build:v2" - }, - "keywords": [ - "opencode", - "opencode2", - "plugin", - "hooks", - "shell" - ], - "author": "Shane Bishop <71288697+shanebishop1@users.noreply.github.com>", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/shanebishop1/opencode-command-hooks.git", - "directory": "packages/v2" - }, - "bugs": { - "url": "https://github.com/shanebishop1/opencode-command-hooks/issues" - }, - "homepage": "https://github.com/shanebishop1/opencode-command-hooks#opencode-2-beta", - "dependencies": { - "@opencode-ai/plugin": "0.0.0-beta-18684" - }, - "engines": { - "bun": ">=1.0.0" - } -} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..625d7ce --- /dev/null +++ b/src/server.ts @@ -0,0 +1,9 @@ +import type { PluginModule } from "@opencode-ai/plugin" +import { CommandHooksPlugin } from "./index.js" + +const plugin = { + id: "opencode-command-hooks", + server: CommandHooksPlugin, +} satisfies PluginModule + +export default plugin diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index bf1634e..707a611 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -96,7 +96,7 @@ const toolContext = (event: V2ToolEvent): { const diagnostic = (message: string, error?: unknown): void => { const detail = error instanceof Error ? error.message : error === undefined ? "" : String(error) - console.error(`[opencode-command-hooks-v2] ${message}${detail ? `: ${detail}` : ""}`) + console.error(`[opencode-command-hooks] ${message}${detail ? `: ${detail}` : ""}`) } export const createV2Plugin = (): V2Plugin => ({ @@ -132,7 +132,7 @@ export const createV2Plugin = (): V2Plugin => ({ if (warnedToastUnsupported) return warnedToastUnsupported = true console.warn( - "[opencode-command-hooks-v2] Toast notifications are not available to V2 server plugins; hook execution and injection will continue.", + "[opencode-command-hooks] Toast notifications are not available to V2 server plugins; hook execution and injection will continue.", ) }, }) diff --git a/tests/e2e.v1-headless.test.ts b/tests/e2e.v1-headless.test.ts index 2a46b60..3dbc2af 100644 --- a/tests/e2e.v1-headless.test.ts +++ b/tests/e2e.v1-headless.test.ts @@ -2,9 +2,11 @@ import { describe, it, expect, beforeAll, afterAll } from "bun:test" import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, statSync, unlinkSync, rmSync } from "fs" import { join } from "path" import { tmpdir } from "os" +import { $ } from "bun" const REPOSITORY_ROOT = process.cwd() let TEST_SANDBOX_DIR = "" +let TEST_PLUGIN_DIR = "" let TEST_CONFIG_DIR = "" let TEST_OPENCODE_SUBDIR = "" let TEST_OPENCODE_CONFIG = "" @@ -221,7 +223,8 @@ function writeTestOpencodeConfig(): void { mkdirSync(TEST_CONFIG_DIR, { recursive: true }) } const pluginConfig = { - plugin: [join(REPOSITORY_ROOT, "dist", "index.js")], + // OpenCode V1 must select the package's ./server export instead of its V2 root. + plugin: [TEST_PLUGIN_DIR], } writeFileSync(TEST_OPENCODE_CONFIG, JSON.stringify(pluginConfig, null, 2)) } @@ -303,6 +306,19 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { beforeAll(async () => { createTestSandbox() + const archive = (await $`npm pack --ignore-scripts --pack-destination ${TEST_SANDBOX_DIR}` + .cwd(REPOSITORY_ROOT) + .text()) + .trim() + .split("\n") + .at(-1) + if (!archive) throw new Error("npm pack did not return an archive name") + writeFileSync(join(TEST_SANDBOX_DIR, "package.json"), JSON.stringify({ private: true })) + await $`npm install --ignore-scripts ${join(TEST_SANDBOX_DIR, archive)}` + .cwd(TEST_SANDBOX_DIR) + .quiet() + TEST_PLUGIN_DIR = join(TEST_SANDBOX_DIR, "node_modules", "opencode-command-hooks") + // Verify the executable works in the same isolated environment as the tests. await runOpenCodeCommand(["--version"]) if (!E2E_MODEL) { @@ -312,7 +328,7 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { // Enable the plugin in the test opencode config writeTestOpencodeConfig() - }) + }, 120_000) afterAll(() => { if (TEST_SANDBOX_DIR) { diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index 618deb5..f3c8a6d 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -68,20 +68,25 @@ describe("OpenCode V2 package E2E", () => { projectDirectory = await mkdtemp(join(tmpdir(), "opencode-hooks-v2-e2e-")) await $`git init -q`.cwd(projectDirectory) - await $`npm run build:v2`.quiet() - await $`npm pack --ignore-scripts --pack-destination ${projectDirectory}`.cwd("packages/v2").quiet() + await $`npm run build`.quiet() + const archive = (await $`npm pack --ignore-scripts --pack-destination ${projectDirectory}`.text()) + .trim() + .split("\n") + .at(-1) + if (!archive) throw new Error("npm pack did not return an archive name") await writeFile( join(projectDirectory, "package.json"), JSON.stringify({ private: true, type: "module" }), ) - await $`npm install ${join(projectDirectory, "opencode-command-hooks-v2-0.1.0-beta.0.tgz")} ${`@opencode-ai/cli@${cliVersion}`}` + await $`npm install ${join(projectDirectory, archive)} ${`@opencode-ai/cli@${cliVersion}`}` .cwd(projectDirectory) .quiet() await mkdir(join(projectDirectory, ".opencode"), { recursive: true }) await writeFile( join(projectDirectory, "opencode.jsonc"), JSON.stringify({ - plugins: [join(projectDirectory, "node_modules", "opencode-command-hooks-v2", "dist", "index.js")], + // Keep the V1 config shape to verify OpenCode 2 migrates it while loading the package root. + plugin: [join(projectDirectory, "node_modules", "opencode-command-hooks")], }), ) await writeFile( diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index c16dba4..aac1126 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -1,20 +1,33 @@ import { describe, expect, it } from "bun:test" import { $ } from "bun" -describe("V2 package artifact", () => { - it("builds and packs the isolated beta package with exact host dependencies", async () => { - await $`npm run build:v2`.quiet() - const output = await $`npm pack --dry-run --json --ignore-scripts`.cwd("packages/v2").text() +describe("dual-host package artifact", () => { + it("exports V2 at the root and V1 through the server entrypoint", async () => { + await $`npm run build`.quiet() + const output = await $`npm pack --dry-run --json --ignore-scripts`.text() const [pack] = JSON.parse(output) as Array<{ id: string files: Array<{ path: string }> }> - const manifest = await Bun.file("packages/v2/package.json").json() as { - dependencies: Record + const manifest = await Bun.file("package.json").json() as { + name: string + exports: Record + engines: Record } - expect(pack.id).toStartWith("opencode-command-hooks-v2@0.1.0-beta.") - expect(pack.files.map(file => file.path)).toContain("dist/index.js") - expect(manifest.dependencies["@opencode-ai/plugin"]).toBe("0.0.0-beta-18684") + expect(pack.id).toStartWith("opencode-command-hooks@") + expect(pack.files.map(file => file.path)).toContain("dist/v2.js") + expect(pack.files.map(file => file.path)).toContain("dist/server.js") + expect(manifest.name).toBe("opencode-command-hooks") + expect(manifest.exports["."]).toBeDefined() + expect(manifest.exports["./server"]).toBeDefined() + expect(manifest.engines.opencode).toBe(">=1.18.23") + + const v2 = await import("opencode-command-hooks") + const v1 = await import("opencode-command-hooks/server") + expect(v2.default.id).toBe("opencode-command-hooks.v2") + expect(typeof v2.default.setup).toBe("function") + expect(v1.default.id).toBe("opencode-command-hooks") + expect(typeof v1.default.server).toBe("function") }) }) diff --git a/tsconfig.build.json b/tsconfig.build.json index 90df575..0919244 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", "include": ["src/**/*.ts"], - "exclude": ["src/v2.ts", "src/v2/**", "node_modules", "dist"] + "exclude": ["node_modules", "dist"] } From bb5f0660ccef93d586dffab6372a6edd54a7872c Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:52:38 -0400 Subject: [PATCH 16/32] fix: support current V2 local package loading --- index.js | 1 + package.json | 1 + tests/e2e.v2.test.ts | 2 +- tests/v2.package.test.ts | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..c912f23 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +export { default } from "./dist/v2.js" diff --git a/package.json b/package.json index 756ecd2..09940e7 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "files": [ "dist", + "index.js", "types" ], "scripts": { diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index f3c8a6d..1fa8a2f 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -85,7 +85,7 @@ describe("OpenCode V2 package E2E", () => { await writeFile( join(projectDirectory, "opencode.jsonc"), JSON.stringify({ - // Keep the V1 config shape to verify OpenCode 2 migrates it while loading the package root. + // Keep the V1 config shape while loading the installed package directory. plugin: [join(projectDirectory, "node_modules", "opencode-command-hooks")], }), ) diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index aac1126..86837ec 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -18,6 +18,7 @@ describe("dual-host package artifact", () => { expect(pack.id).toStartWith("opencode-command-hooks@") expect(pack.files.map(file => file.path)).toContain("dist/v2.js") expect(pack.files.map(file => file.path)).toContain("dist/server.js") + expect(pack.files.map(file => file.path)).toContain("index.js") expect(manifest.name).toBe("opencode-command-hooks") expect(manifest.exports["."]).toBeDefined() expect(manifest.exports["./server"]).toBeDefined() From 441b0521f47375071c4272a184622e5f9ba0968b Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:39:32 +0000 Subject: [PATCH 17/32] fix: target stable OpenCode 2 plugin API --- .github/workflows/v1-e2e.yml | 2 +- bun.lock | 550 +++++++++++++++++++++++++++++++++-- package.json | 10 +- src/v2.ts | 4 +- src/v2/plugin.ts | 6 +- tests/v2.package.test.ts | 2 +- tests/v2.plugin.test.ts | 30 ++ 7 files changed, 575 insertions(+), 29 deletions(-) diff --git a/.github/workflows/v1-e2e.yml b/.github/workflows/v1-e2e.yml index 2050b04..f488d5f 100644 --- a/.github/workflows/v1-e2e.yml +++ b/.github/workflows/v1-e2e.yml @@ -23,7 +23,7 @@ jobs: run: bun install --frozen-lockfile - name: Install OpenCode V1 - run: bun install --global opencode-ai@latest + run: bun install --global opencode-ai@1.18.29 - name: Run V1 headless E2E tests run: npm run test:v1:e2e diff --git a/bun.lock b/bun.lock index d2892c5..de5b914 100644 --- a/bun.lock +++ b/bun.lock @@ -5,15 +5,15 @@ "": { "name": "opencode-command-hooks", "dependencies": { - "@opencode-ai/plugin": "^1.18.25", - "@opencode-ai/sdk": "^1.18.25", + "@opencode-ai/plugin": "^1.18.29", + "@opencode-ai/sdk": "^1.18.29", + "@opencode/plugin": "2.0.12", "js-yaml": "^4.1.0", "jsonc-parser": "^3.3.1", "zod": "^3.22.4", }, "devDependencies": { "@eslint/js": "^9.39.1", - "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-beta-18684", "@types/bun": "latest", "@types/js-yaml": "^4.0.5", "eslint": "^9.39.1", @@ -29,10 +29,58 @@ "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], + "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "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" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], + + "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], + + "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], + "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1057.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/fetch-http-handler": "^5.4.5", "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg=="], + + "@aws-sdk/core": ["@aws-sdk/core@3.978.0", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@aws-sdk/xml-builder": "^3.972.40", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.33.3", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA=="], + + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.70", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg=="], + + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.71", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ=="], + + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.73", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg=="], + + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.16", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/credential-provider-env": "^3.972.71", "@aws-sdk/credential-provider-http": "^3.972.73", "@aws-sdk/credential-provider-login": "^3.972.78", "@aws-sdk/credential-provider-process": "^3.972.71", "@aws-sdk/credential-provider-sso": "^3.973.15", "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw=="], + + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.78", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA=="], + + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.83", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.71", "@aws-sdk/credential-provider-http": "^3.972.73", "@aws-sdk/credential-provider-ini": "^3.973.16", "@aws-sdk/credential-provider-process": "^3.972.71", "@aws-sdk/credential-provider-sso": "^3.973.15", "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ=="], + + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.71", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA=="], + + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.15", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/token-providers": "3.1129.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.77", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ=="], + + "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1057.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1057.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-login": "^3.972.45", "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw=="], + + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.45", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/signature-v4-multi-region": "^3.996.46", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ=="], + + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.46", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1129.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA=="], + "@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="], + "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.10", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g=="], + + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.40", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA=="], + + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], + + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.112", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.0-rc.112" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ=="], + + "@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="], + + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -51,6 +99,8 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -61,6 +111,10 @@ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], @@ -73,28 +127,134 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-beta-18684", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-beta-18684", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-gZTPELkm9aSv0cxWt9gGSdN9fecO9/GlplzUT4knrBD70vxQGnOYx7LGDUPZokW8Fzn7gjD824Qh9AIVjyb07g=="], + "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], + + "@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="], + + "@npmcli/config": ["@npmcli/config@10.8.1", "", { "dependencies": { "@npmcli/map-workspaces": "^5.0.0", "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "ini": "^6.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "walk-up-path": "^4.0.0" } }, "sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ=="], + + "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], + + "@npmcli/git": ["@npmcli/git@7.0.2", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", "semver": "^7.3.5", "which": "^6.0.0" } }, "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="], + + "@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@4.0.0", "", { "dependencies": { "npm-bundled": "^5.0.0", "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="], + + "@npmcli/map-workspaces": ["@npmcli/map-workspaces@5.0.3", "", { "dependencies": { "@npmcli/name-from-folder": "^4.0.0", "@npmcli/package-json": "^7.0.0", "glob": "^13.0.0", "minimatch": "^10.0.3" } }, "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw=="], - "@opencode-ai/client": ["@opencode-ai/client@0.0.0-beta-18684", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-beta-18684", "@opencode-ai/schema": "0.0.0-beta-18684" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-Qjq07sTId3gZcqqMsPg+gtqU3PXneOWje+BSzBXepimAfPNqL0aIpH+em2Y4KL3lxEuJK3vdfyx8UXI8gzGRlQ=="], + "@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@9.0.3", "", { "dependencies": { "cacache": "^20.0.0", "json-parse-even-better-errors": "^5.0.0", "pacote": "^21.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5" } }, "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg=="], - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.25", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.25", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-Kb34zFqYosFNiMd1IuYiZGjX17z+18Srm7tHZMCz+uMVRTYNkEw1FTrfAK2FLbggwYdgzifGwKMNF1slLT8eLw=="], + "@npmcli/name-from-folder": ["@npmcli/name-from-folder@4.0.0", "", {}, "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg=="], - "@opencode-ai/plugin-v2": ["@opencode-ai/plugin@0.0.0-beta-18684", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-beta-18684", "@opencode-ai/client": "0.0.0-beta-18684", "@opencode-ai/protocol": "0.0.0-beta-18684", "@opencode-ai/schema": "0.0.0-beta-18684", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode-ai/theme": "0.0.0-beta-18684", "@opentui/core": ">=0.5.9", "@opentui/solid": ">=0.5.9", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode-ai/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-OOgIRF563MsMROHrxXuJtdG5729PE1DhN4ASRW6OuH0KNjEbIkCrnOJmBI5by+JSbwTbHrsgKXhk18ee9MpRUg=="], + "@npmcli/node-gyp": ["@npmcli/node-gyp@5.0.0", "", {}, "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ=="], - "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-beta-18684", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-beta-18684", "effect": "4.0.0-rc.112" } }, "sha512-BXeauVOxxcam0zgOxJcpkmoQQTiqq3L6l3i8qF/Wsa3iPf7cA1SCM6+3r6iJ/9akckEEi9ZL85M3b8NhOcU85A=="], + "@npmcli/package-json": ["@npmcli/package-json@7.0.5", "", { "dependencies": { "@npmcli/git": "^7.0.0", "glob": "^13.0.0", "hosted-git-info": "^9.0.0", "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", "spdx-expression-parse": "^4.0.0" } }, "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ=="], - "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-beta-18684", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-aoQEG+KBSTAq7IaNVpAfXKNSvcBaRwpjPgPL0DuKoj39g013/NoR/rmbWtjxKkkczBqbWp/91zLGvXW+bIOotQ=="], + "@npmcli/promise-spawn": ["@npmcli/promise-spawn@9.0.1", "", { "dependencies": { "which": "^6.0.0" } }, "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.25", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-GwgwhW+vE8FWSDw730SjzqNhsWXB0uJjbFOiqFkmM+USFuG13HuTlGe6SR2ixt+WXxoD6FV1hILWqsXyqej9hQ=="], + "@npmcli/query": ["@npmcli/query@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ=="], + + "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], + + "@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="], + + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.31", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-Rdc1bPK06PByaGyGd0kf7JUZ4pTkexz2OOUNlqZWLpHOiMEZ+/rFGjt46ypZ3QwA697gNdWwwwQbHbKG5NMwGA=="], + + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.31", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Raouthf8Lhe9edjvYeeSK7SgvdoU6bBjH9qV3f70dHoa6h+z0X2TMz/e22/wKp/StlFUZ4kIRpYYxFnY8/k01w=="], + + "@opencode/ai": ["@opencode/ai@2.0.12", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.0.12", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-3ODFv7a3mKHZ92uUgJDKUM8wY2akNwpvC6YDck0U2gwkc6zfXwNXXpWAEjhdlHPujmQub6dNb0FS5St4oKpSNQ=="], + + "@opencode/client": ["@opencode/client@2.0.12", "", { "dependencies": { "@opencode/protocol": "2.0.12", "@opencode/schema": "2.0.12" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-WxZLWIv0Sx9NwoCDmkEqKPnn+NOpk3Kt9FWh5gMgrl1mDbCI+6EQtFOsgrhqJSrFZXMzFqgLMAiTTIYD7wZWpA=="], + + "@opencode/plugin": ["@opencode/plugin@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.0.12", "@opencode/client": "2.0.12", "@opencode/protocol": "2.0.12", "@opencode/schema": "2.0.12", "@opencode/util": "2.0.12", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode/theme": "2.0.12", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-eUO61AEruyxK6wrL82XhjRuZ5zvoWpLdhzAWbFgoLe3/8IgwrY/4SUKGEou5WgXtzh0zWNDQQqMpaVMe66n8cQ=="], + + "@opencode/protocol": ["@opencode/protocol@2.0.12", "", { "dependencies": { "@opencode/schema": "2.0.12", "effect": "4.0.0-rc.112" } }, "sha512-YYp9ElZMW8UG7cV9kSd9j3svPTEt0sTP7G4tXeQ2icbCIwFki28x+fCiuF8wBGcpYMEcLg1GrF3aEqxPAZOpWA=="], + + "@opencode/schema": ["@opencode/schema@2.0.12", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-T3RJb371gH813pdaWpAvXAgZ3GSHpPcHSkP8nRbPsSKYOlfmNId2s8Js9aLCJHeVfIKmW3+I9poa3Bx+aiiROQ=="], + + "@opencode/util": ["@opencode/util@2.0.12", "", { "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", "@effect/platform-node": "4.0.0-rc.112", "@effect/platform-node-shared": "4.0.0-rc.112", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "cross-spawn": "7.0.6", "effect": "4.0.0-rc.112", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "pacote": "21.5.1" } }, "sha512-LgecrvN4dhsbdgG7Lt6+Cp/+0+VswyV8RJsE/mjjJ6Ns39EwFVTnZYMHT4bkR+vRL/w3IF0VCFg8Ohnfwkn3lw=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], + + "@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="], + + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="], + + "@redis/bloom": ["@redis/bloom@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag=="], + + "@redis/client": ["@redis/client@6.2.1", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0", "@opentelemetry/api": ">=1 <2" }, "optionalPeers": ["@node-rs/xxhash", "@opentelemetry/api"] }, "sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q=="], + + "@redis/json": ["@redis/json@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ=="], + + "@redis/search": ["@redis/search@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ=="], + + "@redis/time-series": ["@redis/time-series@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw=="], + + "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], + + "@sigstore/core": ["@sigstore/core@3.2.1", "", {}, "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.2", "", {}, "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw=="], + + "@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="], + + "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], + + "@sigstore/verify": ["@sigstore/verify@3.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], + "@smithy/core": ["@smithy/core@3.29.5", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.8.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA=="], + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.1", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw=="], + + "@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="], + "@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="], "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.4.10", "", { "dependencies": { "@smithy/core": "^3.29.5", "tslib": "^2.6.2" } }, "sha512-DcPJ8L79nxi/6QA29F9m+OcIR9/KIXN80i5YMQEVy3fZUUgsE9d5HRj5WVKt9id9XucnnxlSpChg8qNkRApPPg=="], @@ -105,6 +265,10 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -115,6 +279,8 @@ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.51.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/type-utils": "8.51.0", "@typescript-eslint/utils": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.51.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A=="], @@ -135,6 +301,8 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -157,24 +325,44 @@ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "bin-links": ["bin-links@6.0.2", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w=="], + + "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], + "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -187,10 +375,12 @@ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + "effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], @@ -209,6 +399,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], @@ -237,11 +429,13 @@ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + "gaxios": ["gaxios@7.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg=="], "gcp-metadata": ["gcp-metadata@8.1.3", "", { "dependencies": { "gaxios": "7.1.3", "google-logging-utils": "1.1.3", "json-bigint": "^1.0.0" } }, "sha512-ziTrzUhhpL9Zk5k0HHzgP/KIpWDJT0VMBC/ynt/QIBvTW+UUcSivQRl6VlwTf/EilDxtSWklHoRsKy1c4k+59w=="], - "glob": ["glob@10.5.0", "", { "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" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -251,20 +445,34 @@ "google-logging-utils": ["google-logging-utils@1.1.4", "", {}, "sha512-LXxE6ND+JHhDPYtPFt3oeWrjxFPrnRZCZ4At6lpbi1ZDSAN7bmGET9s53vvARbxT93p+xpeDUbc/nCR4C+7vcw=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "ip-address": ["ip-address@10.7.2", "", {}, "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -283,14 +491,24 @@ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + + "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], + + "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], @@ -305,12 +523,34 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], + + "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], + + "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "msgpackr": ["msgpackr@2.1.0", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ=="], @@ -321,42 +561,86 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], + + "npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="], + + "npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], + + "npm-packlist": ["npm-packlist@10.0.4", "", { "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" } }, "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng=="], + + "npm-pick-manifest": ["npm-pick-manifest@11.0.3", "", { "dependencies": { "npm-install-checks": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ=="], + + "npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "pacote": ["pacote@21.5.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "postcss-selector-parser": ["postcss-selector-parser@7.1.6", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="], + + "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], + + "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], + + "protobufjs": ["protobufjs@7.6.6", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], + "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], + + "redis": ["redis@6.2.1", "", { "dependencies": { "@redis/bloom": "6.2.1", "@redis/client": "6.2.1", "@redis/json": "6.2.1", "@redis/search": "6.2.1", "@redis/time-series": "6.2.1" } }, "sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -365,6 +649,22 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "sigstore": ["sigstore@4.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.1", "@sigstore/tuf": "^4.0.2", "@sigstore/verify": "^3.1.1" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.24", "", {}, "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ=="], + + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -377,26 +677,40 @@ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="], + "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="], + "ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.51.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.51.0", "@typescript-eslint/parser": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA=="], + "undici": ["undici@8.10.2", "", {}, "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "uuid": ["uuid@14.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ=="], + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], + + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -407,23 +721,165 @@ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="], + + "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@aws-crypto/sha256-browser/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + + "@aws-crypto/sha256-js/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/client-cognito-identity/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/client-cognito-identity/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/core/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/core/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/core/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-cognito-identity/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-cognito-identity/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-env/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-env/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-env/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-http/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-http/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-http/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-ini/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-ini/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-ini/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-login/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-login/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-node/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-node/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-node/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-process/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-process/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-process/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-sso/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-sso/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/credential-provider-web-identity/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/credential-provider-web-identity/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/credential-provider-web-identity/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/nested-clients/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/nested-clients/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/nested-clients/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/signature-v4-multi-region/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/signature-v4-multi-region/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/token-providers/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@aws-sdk/token-providers/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/xml-builder/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@effect/opentelemetry/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@effect/platform-node/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@effect/platform-node-shared/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - "@opencode-ai/plugin/effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="], + "@npmcli/arborist/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "@npmcli/git/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "@npmcli/git/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "@npmcli/map-workspaces/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@npmcli/promise-spawn/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - "@opencode-ai/plugin-v2/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + "@opencode/ai/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/plugin/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + + "@opencode/protocol/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/schema/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/util/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], + + "@opencode/util/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "@smithy/credential-provider-imds/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@smithy/fetch-http-handler/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@smithy/fetch-http-handler/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@smithy/node-http-handler/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@smithy/node-http-handler/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@smithy/signature-v4/@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], + + "@smithy/signature-v4/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@tufjs/models/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -433,7 +889,19 @@ "gcp-metadata/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], - "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "ignore-walk/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-gyp/undici": ["undici@6.28.1", "", {}, "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA=="], + + "node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "rimraf/glob": ["glob@10.5.0", "", { "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" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -447,16 +915,62 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@aws-crypto/sha256-browser/@aws-sdk/types/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/sha256-js/@aws-sdk/types/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@npmcli/arborist/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "@npmcli/git/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "@npmcli/map-workspaces/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "@npmcli/promise-spawn/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "@opencode/util/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "@tufjs/models/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "glob/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "ignore-walk/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "node-gyp/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@npmcli/arborist/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "@opencode/util/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "@tufjs/models/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "ignore-walk/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], } } diff --git a/package.json b/package.json index 09940e7..8174ae3 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "test:v2:e2e": "OPENCODE2_E2E=1 bun test tests/e2e.v2.test.ts", "lint": "eslint src tests --ext .ts", "typecheck": "tsc --noEmit", - "ci:check": "npm run lint && npm run typecheck && (command -v bun >/dev/null 2>&1 && bun test || echo \"bun not found; skipping tests\")", + "ci:check": "npm run lint && npm run typecheck && npm test", "typecheck:bun": "bun --bun tsc --noEmit", "typecheck:bun-build": "bun build src/index.ts --target=bun", "typecheck:bun-run": "bun run src/index.ts", @@ -55,15 +55,15 @@ }, "homepage": "https://github.com/shanebishop1/opencode-command-hooks#readme", "dependencies": { - "@opencode-ai/plugin": "^1.18.25", - "@opencode-ai/sdk": "^1.18.25", + "@opencode-ai/plugin": "^1.18.29", + "@opencode-ai/sdk": "^1.18.29", + "@opencode/plugin": "2.0.12", "js-yaml": "^4.1.0", "jsonc-parser": "^3.3.1", "zod": "^3.22.4" }, "devDependencies": { "@eslint/js": "^9.39.1", - "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-beta-18684", "@types/bun": "latest", "@types/js-yaml": "^4.0.5", "eslint": "^9.39.1", @@ -74,6 +74,6 @@ }, "engines": { "bun": ">=1.0.0", - "opencode": ">=1.18.23" + "opencode": ">=1.18.29" } } diff --git a/src/v2.ts b/src/v2.ts index f5d1396..0b52411 100644 --- a/src/v2.ts +++ b/src/v2.ts @@ -1,4 +1,4 @@ -import type { Plugin } from "@opencode-ai/plugin-v2" +import { Plugin } from "@opencode/plugin" import { createV2Plugin } from "./v2/plugin.js" -export default createV2Plugin() satisfies Plugin.Plugin +export default Plugin.define(createV2Plugin()) diff --git a/src/v2/plugin.ts b/src/v2/plugin.ts index 707a611..b3c6604 100644 --- a/src/v2/plugin.ts +++ b/src/v2/plugin.ts @@ -37,7 +37,7 @@ export interface V2Context { ) => Promise<{ dispose: () => Promise }> } event: { - subscribe: () => AsyncIterable + subscribe: (options?: { signal?: AbortSignal }) => AsyncIterable } session: { get: (input: { sessionID: string }) => Promise @@ -108,6 +108,7 @@ export const createV2Plugin = (): V2Plugin => ({ const activeSubagents = createActiveSubagentTracker() let warnedToastUnsupported = false let stopped = false + const controller = new AbortController() const notifyConfigError = (error: string | null, directory: string): void => { if (!error) return @@ -270,7 +271,7 @@ export const createV2Plugin = (): V2Plugin => ({ let iterator: AsyncIterator | undefined const eventTask = (async () => { try { - iterator = ctx.event.subscribe()[Symbol.asyncIterator]() + iterator = ctx.event.subscribe({ signal: controller.signal })[Symbol.asyncIterator]() while (!stopped) { const next = await iterator.next() if (next.done) break @@ -283,6 +284,7 @@ export const createV2Plugin = (): V2Plugin => ({ return async () => { stopped = true + controller.abort() const disposed = Promise.allSettled(registrations.map(registration => registration.dispose())) await iterator?.return?.() await eventTask diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index 86837ec..c91e113 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -22,7 +22,7 @@ describe("dual-host package artifact", () => { expect(manifest.name).toBe("opencode-command-hooks") expect(manifest.exports["."]).toBeDefined() expect(manifest.exports["./server"]).toBeDefined() - expect(manifest.engines.opencode).toBe(">=1.18.23") + expect(manifest.engines.opencode).toBe(">=1.18.29") const v2 = await import("opencode-command-hooks") const v1 = await import("opencode-command-hooks/server") diff --git a/tests/v2.plugin.test.ts b/tests/v2.plugin.test.ts index aa533cb..38bc20e 100644 --- a/tests/v2.plugin.test.ts +++ b/tests/v2.plugin.test.ts @@ -63,6 +63,36 @@ afterEach(async () => { }) describe("OpenCode V2 plugin", () => { + it("aborts a pending event subscription during cleanup", async () => { + const directory = await createProject({}) + const { context, disposed } = createContext(directory) + let subscriptionSignal: AbortSignal | undefined + let closed = false + context.event.subscribe = ({ signal } = {}) => (async function* () { + subscriptionSignal = signal + try { + await new Promise((_resolve, reject) => { + signal?.addEventListener("abort", () => reject(new Error("aborted")), { once: true }) + }) + yield { type: "session.idle", data: { sessionID: "unexpected" } } + } finally { + closed = true + } + })() + const errorSpy = spyOn(console, "error").mockImplementation(() => {}) + try { + const cleanup = await createV2Plugin().setup(context) + expect(subscriptionSignal?.aborted).toBe(false) + await cleanup?.() + expect(subscriptionSignal?.aborted).toBe(true) + expect(closed).toBe(true) + expect(disposed).toEqual(["execute.before", "execute.after"]) + expect(errorSpy).not.toHaveBeenCalled() + } finally { + errorSpy.mockRestore() + } + }, 1_000) + it("registers both tool phases and uses after-event input without a V1 cache", async () => { const directory = await createProject({ tool: [ From 0d7ed80b2fa5b37f21b38f5ec8ae63af1744a775 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:39:54 +0000 Subject: [PATCH 18/32] test: cover V2 with deterministic and live models --- .github/workflows/ci.yml | 27 +- .github/workflows/release.yml | 5 +- package.json | 1 + tests/e2e.v2.test.ts | 802 +++++++++++++++++++------- tests/fixtures/agents/e2e-worker.md | 15 + tests/fixtures/local-openai-server.ts | 141 +++++ 6 files changed, 767 insertions(+), 224 deletions(-) create mode 100644 tests/fixtures/agents/e2e-worker.md create mode 100644 tests/fixtures/local-openai-server.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 028b095..8ccd903 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: Build and Test on: pull_request: + workflow_dispatch: push: branches: - main @@ -47,5 +48,29 @@ jobs: - name: Verify package run: npm pack --dry-run - - name: Test packed package in OpenCode 2 + - name: Test packed package in OpenCode 2 with a deterministic provider run: npm run test:v2:e2e + + v2-live: + name: V2 live-model E2E + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Test packed package with a hosted free model + run: npm run test:v2:e2e:live diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b0e846..a260ef1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,9 +63,12 @@ jobs: - name: Pack dry-run run: npm pack --dry-run - - name: Test packed package in OpenCode 2 + - name: Test packed package in OpenCode 2 with a deterministic provider run: npm run test:v2:e2e + - name: Test packed package in OpenCode 2 with a hosted free model + run: npm run test:v2:e2e:live + publish-npm: needs: verify runs-on: ubuntu-latest diff --git a/package.json b/package.json index 8174ae3..2d4bc95 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "test": "bun test", "test:v2": "bun test tests/v2.plugin.test.ts tests/v2.package.test.ts", "test:v2:e2e": "OPENCODE2_E2E=1 bun test tests/e2e.v2.test.ts", + "test:v2:e2e:live": "OPENCODE2_E2E=1 OPENCODE2_E2E_LIVE=1 bun test tests/e2e.v2.test.ts", "lint": "eslint src tests --ext .ts", "typecheck": "tsc --noEmit", "ci:check": "npm run lint && npm run typecheck && npm test", diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index 1fa8a2f..b62b813 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -1,287 +1,645 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test" -import { access, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "fs/promises" +import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises" import { tmpdir } from "os" import { join, resolve } from "path" import { $ } from "bun" const enabled = process.env.OPENCODE2_E2E === "1" -const cliVersion = "beta" +const live = process.env.OPENCODE2_E2E_LIVE === "1" +const cliVersion = process.env.OPENCODE2_CLI_VERSION ?? "2.0.12" +const requestedLiveModel = process.env.OPENCODE2_E2E_MODEL?.trim() ?? "" +const repositoryRoot = process.cwd() +const workerFixture = resolve(repositoryRoot, "tests/fixtures/agents/e2e-worker.md") +const providerFixture = resolve(repositoryRoot, "tests/fixtures/local-openai-server.ts") + let projectDirectory = "" +let configPath = "" +let hooksPath = "" let binary = "" let homeDirectory = "" -let environment: Record = {} +let environment: Record = {} +let hostProcess: Bun.ReadableSubprocess | undefined +let providerProcess: Bun.ReadableSubprocess | undefined +let hostUrl = "" +let providerUrl = "" +let hostOutput = "" +let hostError = "" +let providerOutput = "" +let providerError = "" +let liveModel = "" + +type CommandResult = { exitCode: number; stdout: string; stderr: string } +type ProviderLog = { + body: { messages?: unknown[]; tools?: unknown[] } + decision?: { mode: string; tool?: string; schema?: Record } +} +type SessionSummary = { id: string; parentID?: string; directory?: string } +type HostedModel = { + id?: unknown + modelID?: unknown + providerID?: unknown + name?: unknown + enabled?: unknown + status?: unknown + capabilities?: { tools?: unknown } + cost?: unknown +} + +const sleep = (milliseconds: number) => new Promise(resolve => setTimeout(resolve, milliseconds)) + +const consumeOutput = async ( + stream: ReadableStream, + append: (chunk: string) => void, +): Promise => { + const reader = stream.getReader() + const decoder = new TextDecoder() + while (true) { + const { done, value } = await reader.read() + if (done) break + append(decoder.decode(value, { stream: true })) + } + append(decoder.decode()) +} + +const waitFor = async (read: () => Promise, matches: (value: T) => boolean, timeoutMs = 15_000): Promise => { + const deadline = Date.now() + timeoutMs + let value = await read() + while (!matches(value) && Date.now() < deadline) { + await sleep(100) + value = await read() + } + return value +} + +const missingFiles = async (paths: string[]): Promise => { + const result = await Promise.all(paths.map(async path => { + try { + await access(path) + return undefined + } catch { + return path + } + })) + return result.filter((path): path is string => path !== undefined) +} + +const waitForFiles = (paths: string[], timeoutMs = 15_000) => + waitFor(() => missingFiles(paths), missing => missing.length === 0, timeoutMs) -const runCommand = async (args: string[], timeoutMs = 120_000) => { - const process = Bun.spawn([binary, ...args], { +const runCommand = async (args: string[], timeoutMs = 90_000): Promise => { + const child = Bun.spawn([binary, ...args], { cwd: projectDirectory, env: environment, stdout: "pipe", stderr: "pipe", }) - const timeout = setTimeout(() => process.kill(9), timeoutMs) + let timedOut = false + const timeout = setTimeout(() => { + timedOut = true + if (child.exitCode === null) child.kill(9) + }, timeoutMs) try { const [exitCode, stdout, stderr] = await Promise.all([ - process.exited, - new Response(process.stdout).text(), - new Response(process.stderr).text(), + child.exited, + new Response(child.stdout).text(), + new Response(child.stderr).text(), ]) - return { exitCode, stdout, stderr } + return { exitCode: timedOut ? -1 : exitCode, stdout, stderr } } finally { clearTimeout(timeout) } } -const selectFreeModel = (output: string): string => { - const models = output.split("\n").map(line => line.trim()) - const model = ["opencode/mimo-v2.5-free", "opencode/ling-3.0-flash-fin-free"] - .find(candidate => models.includes(candidate)) - ?? models.find(candidate => candidate.startsWith("opencode/") && candidate.endsWith("-free")) - if (!model) throw new Error(`OpenCode 2 did not advertise a credential-free model:\n${output}`) - return model +const stopProcess = async (child: Bun.ReadableSubprocess | undefined): Promise => { + if (!child) return + if (child.exitCode === null) child.kill(9) + await Promise.race([ + child.exited.then(() => undefined), + sleep(5_000), + ]) } -const missingFiles = async (paths: string[]): Promise => { - const checked = await Promise.all(paths.map(async path => { +const readProviderLog = async (): Promise => { + try { + const content = await readFile(join(projectDirectory, "provider.jsonl"), "utf8") + return content + .split("\n") + .filter(Boolean) + .map(line => JSON.parse(line) as ProviderLog) + } catch { + return [] + } +} + +const readLineCount = async (path: string): Promise => { + try { + return (await readFile(path, "utf8")).split(/\r?\n/).filter(Boolean).length + } catch { + return 0 + } +} + +const writeHooks = async (config: object): Promise => { + await writeFile(hooksPath, JSON.stringify(config, null, 2)) +} + +const writeHostConfig = async (plugins: string[], includeLocalProvider = true): Promise => { + const config: Record = { + "$schema": "https://opencode.ai/config.json", + plugins, + } + if (includeLocalProvider) { + config.model = "local/deterministic" + config.providers = { + local: { + name: "Deterministic local provider", + package: "@opencode/ai/providers/openai-compatible", + settings: { baseURL: `${providerUrl}/v1` }, + models: { + deterministic: { + modelID: "deterministic", + capabilities: { tools: true, input: ["text"], output: ["text"] }, + limit: { context: 32_768, output: 4_096 }, + }, + }, + }, + } + } + await writeFile(configPath, JSON.stringify(config, null, 2)) +} + +const pluginIsLoaded = async (): Promise => { + try { + const response = await fetch(`${hostUrl}/api/plugin`, { headers: apiHeaders(), signal: AbortSignal.timeout(2_000) }) + if (!response.ok) return undefined + return (await response.text()).includes("opencode-command-hooks.v2") + } catch { + return undefined + } +} + +const apiHeaders = (): Record => ({ + "x-opencode-directory": projectDirectory, + authorization: `Basic ${Buffer.from("opencode:v2-e2e-password").toString("base64")}`, +}) + +const modelReference = (model: HostedModel): string => { + if (typeof model.id === "string" && model.id.includes("/")) return model.id + if (typeof model.providerID === "string" && typeof model.modelID === "string") { + return `${model.providerID}/${model.modelID}` + } + return typeof model.id === "string" ? model.id : "" +} + +const isFreeOpenCodeModel = (model: HostedModel): boolean => { + const reference = modelReference(model) + const provider = typeof model.providerID === "string" ? model.providerID : reference.split("/", 1)[0] + const modelID = typeof model.modelID === "string" ? model.modelID : reference.split("/").at(-1) ?? "" + const hasNoUsageCost = Array.isArray(model.cost) && model.cost.every(cost => { + if (typeof cost !== "object" || cost === null) return false + const value = cost as { input?: unknown; output?: unknown; cache?: { read?: unknown; write?: unknown } } + return value.input === 0 + && value.output === 0 + && typeof value.cache === "object" + && value.cache !== null + && value.cache.read === 0 + && value.cache.write === 0 + }) + return provider === "opencode" + && reference.startsWith("opencode/") + && modelID.endsWith("-free") + && model.enabled !== false + && model.status !== "deprecated" + && hasNoUsageCost + && model.capabilities?.tools === true +} + +const readHostedModels = async (): Promise<{ models: HostedModel[]; detail: string }> => { + const url = new URL(`${hostUrl}/api/model`) + url.searchParams.set("location[directory]", projectDirectory) + try { + const response = await fetch(url, { headers: apiHeaders(), signal: AbortSignal.timeout(3_000) }) + const body = await response.text() + if (!response.ok) return { models: [], detail: `HTTP ${response.status}: ${body}` } + let payload: unknown try { - await access(path) - return undefined + payload = JSON.parse(body) } catch { - return path + return { models: [], detail: `invalid JSON: ${body}` } + } + if (typeof payload !== "object" || payload === null || !Array.isArray((payload as { data?: unknown }).data)) { + return { models: [], detail: `unexpected /api/model payload: ${body}` } + } + return { models: (payload as { data: unknown[] }).data.filter(model => typeof model === "object" && model !== null) as HostedModel[], detail: body } + } catch (error) { + return { models: [], detail: String(error) } + } +} + +const discoverLiveModel = async (): Promise => { + if (requestedLiveModel && (!requestedLiveModel.startsWith("opencode/") || !requestedLiveModel.endsWith("-free"))) { + throw new Error(`OPENCODE2_E2E_MODEL must be an opencode/*-free model; refusing paid or non-OpenCode override: ${requestedLiveModel}`) + } + + const preferred = ["opencode/mimo-v2.5-free", "opencode/north-mini-code-free"] + const deadline = Date.now() + 60_000 + let lastDetail = "no catalog response" + while (Date.now() < deadline) { + const catalog = await readHostedModels() + lastDetail = catalog.detail + const models = catalog.models + const selected = requestedLiveModel + ? models.find(model => isFreeOpenCodeModel(model) && modelReference(model) === requestedLiveModel) + : preferred.map(reference => models.find(model => isFreeOpenCodeModel(model) && modelReference(model) === reference)).find(Boolean) + ?? models.find(isFreeOpenCodeModel) + if (selected) return modelReference(selected) + await sleep(500) + } + + throw new Error( + `OpenCode V2 did not advertise a credential-free, tool-capable opencode/*-free model from /api/model` + + `${requestedLiveModel ? ` matching ${requestedLiveModel}` : ""}. Last response: ${lastDetail}`, + ) +} + +const listSessions = async (): Promise => { + try { + const query = new URLSearchParams({ directory: projectDirectory, limit: "100", order: "asc" }) + const response = await fetch(`${hostUrl}/api/session?${query}`, { + headers: apiHeaders(), + signal: AbortSignal.timeout(2_000), + }) + if (!response.ok) return [] + const payload = await response.json() as { data?: unknown } + if (!Array.isArray(payload.data)) return [] + return payload.data.map(session => { + const value = typeof session === "object" && session !== null ? session as Record : {} + const location = typeof value.location === "object" && value.location !== null + ? value.location as Record + : {} + return { + id: String(value.id ?? ""), + parentID: typeof value.parentID === "string" ? value.parentID : undefined, + directory: typeof location.directory === "string" ? location.directory : undefined, + } + }).filter(session => session.id !== "") + } catch { + return [] + } +} + +const sessionEvidence = async (sessionID: string): Promise => { + const urls = [ + `${hostUrl}/api/session/${encodeURIComponent(sessionID)}/inbox`, + `${hostUrl}/api/session/${encodeURIComponent(sessionID)}/message`, + ] + const responses = await Promise.all(urls.map(async url => { + try { + const response = await fetch(url, { headers: apiHeaders(), signal: AbortSignal.timeout(2_000) }) + return response.ok ? response.text() : "" + } catch { + return "" } })) - return checked.filter((path): path is string => path !== undefined) + return responses.join("\n") } -const waitForFiles = async (paths: string[], timeoutMs = 10_000): Promise => { - const deadline = Date.now() + timeoutMs - let missing = await missingFiles(paths) - while (missing.length > 0 && Date.now() < deadline) { - await new Promise(resolve => setTimeout(resolve, 100)) - missing = await missingFiles(paths) +const sessionMessageEvidence = async (sessionID: string): Promise => { + try { + const response = await fetch(`${hostUrl}/api/session/${encodeURIComponent(sessionID)}/message`, { + headers: apiHeaders(), + signal: AbortSignal.timeout(2_000), + }) + return response.ok ? response.text() : "" + } catch { + return "" } - return missing } -describe("OpenCode V2 package E2E", () => { - beforeAll(async () => { - if (!enabled) return +const diagnostics = (): string => + `host stdout:\n${hostOutput}\nhost stderr:\n${hostError}\nprovider stdout:\n${providerOutput}\nprovider stderr:\n${providerError}` + +const startFixture = async (): Promise<{ process: Bun.ReadableSubprocess; url: string }> => { + const child = Bun.spawn([process.execPath, providerFixture, "0", join(projectDirectory, "provider.jsonl")], { + cwd: projectDirectory, + env: environment, + stdout: "pipe", + stderr: "pipe", + }) + const readOutput = consumeOutput(child.stdout, chunk => { providerOutput += chunk }) + const errorOutput = consumeOutput(child.stderr, chunk => { providerError += chunk }) + void readOutput + void errorOutput + const url = await waitFor( + async () => providerOutput.match(/READY (http:\/\/\S+)/)?.[1] ?? "", + value => value !== "", + 10_000, + ) + if (!url) { + await stopProcess(child) + await errorOutput + throw new Error(`Local provider did not become ready.\n${diagnostics()}`) + } + return { process: child, url } +} +const startHost = async (): Promise<{ process: Bun.ReadableSubprocess; url: string }> => { + const child = Bun.spawn([binary, "serve", "--hostname", "127.0.0.1", "--port", "0"], { + cwd: projectDirectory, + env: environment, + stdout: "pipe", + stderr: "pipe", + }) + const readOutput = consumeOutput(child.stdout, chunk => { hostOutput += chunk }) + void readOutput + const errorOutput = consumeOutput(child.stderr, chunk => { hostError += chunk }) + void errorOutput + const url = await waitFor( + async () => hostOutput.match(/server listening on (http:\/\/\S+)/)?.[1] ?? "", + value => value !== "", + 20_000, + ) + if (!url) { + await stopProcess(child) + await errorOutput + throw new Error(`OpenCode server did not become ready.\n${diagnostics()}`) + } + return { process: child, url } +} + +describe.skipIf(!enabled)("OpenCode V2 deterministic and opt-in hosted/free-model real-host E2E", () => { + beforeAll(async () => { projectDirectory = await mkdtemp(join(tmpdir(), "opencode-hooks-v2-e2e-")) - await $`git init -q`.cwd(projectDirectory) - await $`npm run build`.quiet() - const archive = (await $`npm pack --ignore-scripts --pack-destination ${projectDirectory}`.text()) - .trim() - .split("\n") - .at(-1) - if (!archive) throw new Error("npm pack did not return an archive name") - await writeFile( - join(projectDirectory, "package.json"), - JSON.stringify({ private: true, type: "module" }), - ) - await $`npm install ${join(projectDirectory, archive)} ${`@opencode-ai/cli@${cliVersion}`}` - .cwd(projectDirectory) - .quiet() - await mkdir(join(projectDirectory, ".opencode"), { recursive: true }) - await writeFile( - join(projectDirectory, "opencode.jsonc"), - JSON.stringify({ - // Keep the V1 config shape while loading the installed package directory. - plugin: [join(projectDirectory, "node_modules", "opencode-command-hooks")], - }), - ) - await writeFile( - join(projectDirectory, ".opencode", "command-hooks.jsonc"), - JSON.stringify({ tool: [], session: [] }), - ) - binary = resolve(projectDirectory, "node_modules", ".bin", "opencode2") + configPath = join(projectDirectory, "opencode.jsonc") + hooksPath = join(projectDirectory, ".opencode", "command-hooks.jsonc") homeDirectory = join(projectDirectory, "home") + const temporaryDirectory = join(projectDirectory, "tmp") + await mkdir(join(projectDirectory, ".opencode", "agents"), { recursive: true }) await mkdir(homeDirectory, { recursive: true }) - environment = { - ...process.env, + await mkdir(temporaryDirectory, { recursive: true }) + + const inheritedKeys = ["PATH", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "LC_CTYPE", "TERM", "COLORTERM", "TZ"] + const inherited = inheritedKeys + .map(key => [key, process.env[key]] as const) + .filter((entry): entry is readonly [string, string] => entry[1] !== undefined) + environment = Object.fromEntries(inherited) as Record + Object.assign(environment, { PWD: projectDirectory, HOME: homeDirectory, + TMPDIR: temporaryDirectory, + TMP: temporaryDirectory, + TEMP: temporaryDirectory, XDG_CONFIG_HOME: join(homeDirectory, ".config"), XDG_DATA_HOME: join(homeDirectory, ".local", "share"), XDG_CACHE_HOME: join(homeDirectory, ".cache"), - OPENCODE_CONFIG: join(projectDirectory, "opencode.jsonc"), - OPENCODE_LOG_LEVEL: "trace", + OPENCODE_CONFIG: configPath, + OPENCODE_LOG_LEVEL: "error", OPENCODE_PASSWORD: "v2-e2e-password", OPENCODE_SERVER_PASSWORD: "v2-e2e-password", + CI: "1", + }) + + if (!live) { + const fixture = await startFixture() + providerProcess = fixture.process + providerUrl = fixture.url } - }, 120_000) - afterAll(async () => { - if (projectDirectory) { - await rm(projectDirectory, { recursive: true, force: true }) + const archive = (await $`npm run build && npm pack --ignore-scripts --pack-destination ${projectDirectory}` + .cwd(repositoryRoot) + .text()) + .trim() + .split("\n") + .at(-1) + if (!archive) throw new Error("npm pack did not return an archive name") + + await writeFile(join(projectDirectory, "package.json"), JSON.stringify({ private: true, type: "module" })) + await $`npm install --no-audit --no-fund ${join(projectDirectory, archive)} ${`@opencode/cli@${cliVersion}`}` + .cwd(projectDirectory) + .quiet() + binary = resolve(projectDirectory, "node_modules", ".bin", "opencode") + + await writeFile(join(projectDirectory, ".opencode", "command-hooks.jsonc"), JSON.stringify({ tool: [], session: [] })) + await writeFile(join(projectDirectory, ".opencode", "agents", "e2e-worker.md"), await readFile(workerFixture, "utf8")) + await writeHostConfig([join(projectDirectory, "node_modules", "opencode-command-hooks")], !live) + + const host = await startHost() + hostProcess = host.process + hostUrl = host.url + const loaded = await waitFor(pluginIsLoaded, value => value === true, 20_000) + if (loaded !== true) throw new Error(`Packed V2 plugin was not loaded.\n${diagnostics()}`) + if (live) { + liveModel = await discoverLiveModel() + console.log(`[V2 live] host=${hostUrl} cli=${cliVersion} model=${liveModel}`) } + }, 180_000) + + afterAll(async () => { + await stopProcess(hostProcess) + await stopProcess(providerProcess) + if (projectDirectory) await rm(projectDirectory, { recursive: true, force: true }) }) - it("loads the packed plugin in the current OpenCode 2 beta host", async () => { - if (!enabled) { - console.log("Skipping V2 E2E: set OPENCODE2_E2E=1 to run") - return - } + it.skipIf(live)("executes real shell hooks and proves injection reached the provider context", async () => { + const shellFile = join(projectDirectory, "model-shell.txt") + const beforeFile = join(projectDirectory, "tool-before.txt") + const afterFile = join(projectDirectory, "tool-after.txt") + const startFile = join(projectDirectory, "session-start.txt") + const idleFile = join(projectDirectory, "session-idle.txt") + const injectedMarker = "V2_REAL_AFTER_INJECTION:V2_REAL_AFTER_OUTPUT" - const server = Bun.spawn([ - binary, - "serve", - "--hostname", - "127.0.0.1", - "--port", - "0", - ], { - cwd: projectDirectory, - env: environment, - stdout: "pipe", - stderr: "pipe", + await writeHooks({ + tool: [ + { id: "real-before", when: { phase: "before", tool: "*" }, run: "printf before > tool-before.txt" }, + { + id: "real-after", + when: { phase: "after", tool: "*" }, + run: "printf after > tool-after.txt; printf V2_REAL_AFTER_OUTPUT", + inject: "V2_REAL_AFTER_INJECTION:{stdout}", + }, + ], + session: [ + { id: "real-start", when: { event: "session.start" }, run: "printf started > session-start.txt" }, + { id: "real-idle", when: { event: "session.idle" }, run: "printf idle > session-idle.txt" }, + ], }) - let serverOutput = "" - let baseUrl = "" - const serverStdout = (async () => { - const reader = server.stdout.getReader() - const decoder = new TextDecoder() - while (true) { - const { done, value } = await reader.read() - if (done) break - serverOutput += decoder.decode(value, { stream: true }) - baseUrl ||= serverOutput.match(/server listening on (http:\/\/\S+)/)?.[1] ?? "" - } - serverOutput += decoder.decode() - })() - const serverStderr = new Response(server.stderr).text() - const stopServer = async (): Promise => { - if (server.exitCode === null) server.kill(9) - await Promise.race([ - server.exited.then(() => undefined), - new Promise(resolve => setTimeout(resolve, 5_000)), + + const result = await runCommand([ + "run", "--server", hostUrl, "--model", "local/deterministic", "--auto", + `V2_E2E_SHELL V2_E2E_SHELL_FILE=${shellFile} END. Use the shell tool to execute the requested command, then reply DONE.`, + ]) + expect(result.exitCode, `${result.stdout}\n${result.stderr}\n${diagnostics()}`).toBe(0) + + const evidence = [shellFile, beforeFile, afterFile, startFile, idleFile] + const missing = await waitForFiles(evidence) + expect(missing, `${result.stdout}\n${result.stderr}\n${diagnostics()}`).toEqual([]) + expect(await readFile(beforeFile, "utf8")).toBe("before") + expect(await readFile(afterFile, "utf8")).toBe("after") + expect(await readFile(startFile, "utf8")).toBe("started") + expect(await readFile(idleFile, "utf8")).toBe("idle") + expect(await readFile(shellFile, "utf8")).toBe("V2_MODEL_SHELL") + + const requests = await waitFor(readProviderLog, logs => logs.some(log => JSON.stringify(log).includes(injectedMarker))) + const shellRequest = requests.find(log => log.decision?.mode === "shell" && log.decision.tool) + expect(shellRequest, JSON.stringify(requests)).toBeDefined() + const shellSchema = shellRequest?.decision?.schema + expect(shellSchema && typeof shellSchema === "object", JSON.stringify(requests.map(log => log.decision))).toBe(true) + expect(Object.prototype.hasOwnProperty.call((shellSchema as Record).properties, "command"), JSON.stringify(shellRequest?.decision)).toBe(true) + expect(requests.some(log => JSON.stringify(log).includes(injectedMarker))).toBe(true) + }, 120_000) + + it.skipIf(live)("runs a real subagent, its frontmatter hooks, and root/child idle rules", async () => { + const childBeforeFile = join(projectDirectory, "child-before.txt") + const childAfterFile = join(projectDirectory, "child-after.txt") + const markerSuffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}` + const rootIdleMarker = `V2_ROOT_IDLE_${markerSuffix}` + const allIdleMarker = `V2_ALL_IDLE_${markerSuffix}` + + await writeHooks({ + session: [ + { id: "root-idle", when: { event: "session.idle" }, inject: rootIdleMarker }, + { id: "all-idle", when: { event: "session.idle", rootSessionOnly: false }, inject: allIdleMarker }, + ], + }) + + const sessionsBefore = await listSessions() + const sessionIdsBefore = new Set(sessionsBefore.map(session => session.id)) + const result = await runCommand([ + "run", "--server", hostUrl, "--model", "local/deterministic", "--auto", + "V2_E2E_SUBAGENT_ROOT: invoke the e2e-worker subagent and then reply PARENT_DONE.", + ]) + expect(result.exitCode, `${result.stdout}\n${result.stderr}\n${diagnostics()}`).toBe(0) + + expect(await waitForFiles([childBeforeFile, childAfterFile])).toEqual([]) + const sessionsAfter = await waitFor( + listSessions, + sessions => sessions.some(session => session.parentID && !sessionIdsBefore.has(session.id)), + 20_000, + ) + const childSession = sessionsAfter.find(session => session.parentID && !sessionIdsBefore.has(session.id)) + expect(childSession, JSON.stringify(sessionsAfter)).toMatchObject({ directory: projectDirectory }) + expect(childSession?.parentID).toBeTruthy() + const rootSessionID = childSession?.parentID + if (!rootSessionID) throw new Error(`Child session did not include parent ID: ${JSON.stringify(childSession)}`) + const idleEvidence = await waitFor( + async () => ({ + root: await sessionEvidence(rootSessionID), + child: await sessionEvidence(childSession.id), + }), + evidence => evidence.root.includes(rootIdleMarker) + && evidence.root.includes(allIdleMarker) + && evidence.child.includes(allIdleMarker) + && !evidence.child.includes(rootIdleMarker), + 20_000, + ) + expect(idleEvidence.root, JSON.stringify(idleEvidence)).toContain(rootIdleMarker) + expect(idleEvidence.root, JSON.stringify(idleEvidence)).toContain(allIdleMarker) + expect(idleEvidence.child, JSON.stringify(idleEvidence)).toContain(allIdleMarker) + expect(idleEvidence.child, JSON.stringify(idleEvidence)).not.toContain(rootIdleMarker) + + const requests = await readProviderLog() + const subagentRequest = requests.find(log => log.decision?.mode === "subagent" && log.decision.tool) + expect(subagentRequest, JSON.stringify(requests)).toBeDefined() + const subagentSchema = subagentRequest?.decision?.schema + expect(subagentSchema && typeof subagentSchema === "object", JSON.stringify(requests.map(log => log.decision))).toBe(true) + expect(Object.prototype.hasOwnProperty.call((subagentSchema as Record).properties, "agent"), JSON.stringify(subagentRequest?.decision)).toBe(true) + expect(requests.some(log => JSON.stringify(log).includes("V2_CHILD_FRONTMATTER:V2_CHILD_AFTER_OUTPUT"))).toBe(true) + }, 120_000) + + it.skipIf(live)("cleans registrations on disable/reload and never duplicates a hook", async () => { + const hitFile = join(projectDirectory, "reload-hit.txt") + const firstModelFile = join(projectDirectory, "reload-first.txt") + const secondModelFile = join(projectDirectory, "reload-second.txt") + const thirdModelFile = join(projectDirectory, "reload-third.txt") + const idleFile = join(projectDirectory, "reload-idle.txt") + await writeHooks({ + tool: [{ id: "reload-after", when: { phase: "after", tool: "*" }, run: "printf 'hit\n' >> reload-hit.txt" }], + session: [{ id: "reload-idle", when: { event: "session.idle" }, run: "printf 'idle\n' >> reload-idle.txt" }], + }) + + const runReloadProbe = async (file: string) => { + const result = await runCommand([ + "run", "--server", hostUrl, "--model", "local/deterministic", "--auto", + `V2_E2E_RELOAD V2_E2E_SHELL V2_E2E_SHELL_FILE=${file} END. Use the shell tool, then reply DONE.`, ]) + expect(result.exitCode, `${result.stdout}\n${result.stderr}\n${diagnostics()}`).toBe(0) + expect(await waitForFiles([file]), `${file}\n${diagnostics()}`).toEqual([]) + expect(await readFile(file, "utf8")).toBe("V2_MODEL_SHELL") } - const headers = { - "x-opencode-directory": projectDirectory, - authorization: `Basic ${Buffer.from("opencode:v2-e2e-password").toString("base64")}`, - } - const logDirectory = join(homeDirectory, ".local", "share", "opencode", "log") - const readLogs = async (): Promise => { - try { - const files = await readdir(logDirectory) - return (await Promise.all(files.map(file => readFile(join(logDirectory, file), "utf8")))).join("\n") - } catch { - return "" - } - } - let pluginResponse = "" - try { - const serverDeadline = Date.now() + 20_000 - while (!baseUrl && server.exitCode === null && Date.now() < serverDeadline) { - await new Promise(resolve => setTimeout(resolve, 25)) - } - if (!baseUrl) { - await stopServer() - await serverStdout - throw new Error( - `V2 server did not become ready (exit=${server.exitCode})\nstdout:\n${serverOutput}\nstderr:\n${await serverStderr}`, - ) - } + await runReloadProbe(firstModelFile) + expect(await waitFor(() => readLineCount(hitFile), count => count === 1)).toBe(1) + expect(await waitFor(() => readLineCount(idleFile), count => count === 1)).toBe(1) - const pluginDeadline = Date.now() + 30_000 - while (Date.now() < pluginDeadline) { - try { - const response = await fetch(`${baseUrl}/api/plugin`, { headers }) - pluginResponse = await response.text() - if (response.ok && pluginResponse.includes("opencode-command-hooks.v2")) break - } catch { - // The server is still starting. - } - await new Promise(resolve => setTimeout(resolve, 100)) - } + await writeHostConfig([]) + expect(await waitFor(pluginIsLoaded, value => value === false, 20_000)).toBe(false) + await runReloadProbe(secondModelFile) + expect(await readLineCount(hitFile)).toBe(1) + expect(await readLineCount(idleFile)).toBe(1) - if (!pluginResponse.includes("opencode-command-hooks.v2")) { - await stopServer() - await serverStdout - throw new Error( - `V2 plugin did not load (server exit=${server.exitCode}): ${pluginResponse}\nstdout:\n${serverOutput}\nstderr:\n${await serverStderr}\n${await readLogs()}`, - ) - } + await writeHostConfig([join(projectDirectory, "node_modules", "opencode-command-hooks")]) + expect(await waitFor(pluginIsLoaded, value => value === true, 20_000)).toBe(true) + await runReloadProbe(thirdModelFile) + expect(await waitFor(() => readLineCount(hitFile), count => count === 2)).toBe(2) + expect(await waitFor(() => readLineCount(idleFile), count => count === 2)).toBe(2) + }, 150_000) - } finally { - await stopServer() - await serverStdout - } + it.skipIf(!live)("uses a hosted free model for shell hooks and persists synthetic injection evidence", async () => { + const markerSuffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}` + const shellFile = join(projectDirectory, "live-model-shell.txt") + const beforeFile = join(projectDirectory, "live-tool-before.txt") + const afterFile = join(projectDirectory, "live-tool-after.txt") + const startFile = join(projectDirectory, "live-session-start.txt") + const idleFile = join(projectDirectory, "live-session-idle.txt") + const shellContent = "V2_LIVE_MODEL_SHELL" + const hookStdout = `V2_LIVE_AFTER_OUTPUT_${markerSuffix}` + const injectedMarker = `V2_LIVE_AFTER_INJECTION_${markerSuffix}:${hookStdout}` + const prompt = `Use the shell tool to create ${shellFile} with exactly this content and no trailing newline: ${shellContent}. After the shell command succeeds, reply DONE.` - const logs = await readLogs() + await writeHooks({ + tool: [ + { id: "live-before-shell", when: { phase: "before", tool: "shell" }, run: "printf before > live-tool-before.txt" }, + { + id: "live-after-shell", + when: { phase: "after", tool: "shell" }, + run: `printf after > live-tool-after.txt; printf '%s' '${hookStdout}'`, + inject: `V2_LIVE_AFTER_INJECTION_${markerSuffix}:{stdout}`, + }, + ], + session: [ + { id: "live-session-start", when: { event: "session.start" }, run: "printf started > live-session-start.txt" }, + { id: "live-session-idle", when: { event: "session.idle" }, run: "printf idle > live-session-idle.txt" }, + ], + }) - expect(pluginResponse, logs).toContain("opencode-command-hooks.v2") - }, 90_000) + const sessionsBefore = await listSessions() + const sessionIdsBefore = new Set(sessionsBefore.map(session => session.id)) + expect(prompt).not.toContain(injectedMarker) + const result = await runCommand(["run", "--server", hostUrl, "--model", liveModel, "--auto", prompt], 150_000) + expect(result.exitCode, `${result.stdout}\n${result.stderr}\n${diagnostics()}`).toBe(0) - it("executes tool and session hooks in the current OpenCode 2 beta host", async () => { - if (!enabled) return + const evidence = [shellFile, beforeFile, afterFile, startFile, idleFile] + expect(await waitForFiles(evidence, 30_000), `${result.stdout}\n${result.stderr}\n${diagnostics()}`).toEqual([]) + expect(await readFile(beforeFile, "utf8")).toBe("before") + expect(await readFile(afterFile, "utf8")).toBe("after") + expect(await readFile(startFile, "utf8")).toBe("started") + expect(await readFile(idleFile, "utf8")).toBe("idle") + expect(await readFile(shellFile, "utf8")).toBe(shellContent) - const evidence = { - before: join(projectDirectory, "tool-before.txt"), - after: join(projectDirectory, "tool-after.txt"), - started: join(projectDirectory, "session-started.txt"), - idle: join(projectDirectory, "session-idle.txt"), - model: join(projectDirectory, "model-tool.txt"), - } - await writeFile( - join(projectDirectory, ".opencode", "command-hooks.jsonc"), - JSON.stringify({ - tool: [ - { - id: "v2-real-before", - when: { phase: "before", tool: "*" }, - run: "printf before > tool-before.txt", - }, - { - id: "v2-real-after", - when: { phase: "after", tool: "*" }, - run: "printf after > tool-after.txt", - inject: "V2_AFTER_HOOK_EXECUTED", - }, - ], - session: [ - { - id: "v2-real-start", - when: { event: "session.start" }, - run: "printf started > session-started.txt", - }, - { - id: "v2-real-idle", - when: { event: "session.idle" }, - run: "printf idle > session-idle.txt", - }, - ], - }), + const sessionsAfter = await waitFor( + listSessions, + sessions => sessions.some(session => !session.parentID && !sessionIdsBefore.has(session.id) && session.directory === projectDirectory), + 30_000, ) + const session = sessionsAfter.find(session => !session.parentID && !sessionIdsBefore.has(session.id) && session.directory === projectDirectory) + expect(session, JSON.stringify(sessionsAfter)).toBeDefined() + if (!session) throw new Error(`Hosted model session was not persisted: ${JSON.stringify(sessionsAfter)}`) - const models = await runCommand(["models", "--standalone"]) - expect(models.exitCode, models.stderr).toBe(0) - const model = selectFreeModel(models.stdout) - const result = await runCommand([ - "run", - "--standalone", - "--auto", - "--model", - model, - "Use the shell tool to run exactly: printf model-tool > model-tool.txt. Then reply DONE.", - ]) - - expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(0) - const evidencePaths = Object.values(evidence) - const missing = await waitForFiles(evidencePaths) - const logDirectory = join(homeDirectory, ".local", "share", "opencode", "log") - const logs = await readdir(logDirectory) - .then(files => Promise.all(files.map(file => readFile(join(logDirectory, file), "utf8")))) - .then(files => files.join("\n")) - .catch(() => "") - expect(missing, `${result.stdout}\n${result.stderr}\n${logs}`).toEqual([]) - expect(logs).not.toContain("Failed to handle tool.execute.after") - expect(logs).not.toContain("Failed to inject message") - expect(await readFile(evidence.before, "utf8")).toBe("before") - expect(await readFile(evidence.after, "utf8")).toBe("after") - expect(await readFile(evidence.started, "utf8")).toBe("started") - expect(await readFile(evidence.idle, "utf8")).toBe("idle") - expect(await readFile(evidence.model, "utf8")).toBe("model-tool") - }, 180_000) + const persistedMessages = await waitFor( + () => sessionMessageEvidence(session.id), + messages => messages.includes(injectedMarker), + 30_000, + ) + expect(persistedMessages, `${persistedMessages}\n${diagnostics()}`).toContain(injectedMarker) + }, 210_000) }) diff --git a/tests/fixtures/agents/e2e-worker.md b/tests/fixtures/agents/e2e-worker.md new file mode 100644 index 0000000..867a0a3 --- /dev/null +++ b/tests/fixtures/agents/e2e-worker.md @@ -0,0 +1,15 @@ +--- +description: Deterministic real-host E2E worker. +mode: subagent +model: local/deterministic +hooks: + before: + - run: "printf child-before > child-before.txt" + after: + - run: "printf child-after > child-after.txt; printf V2_CHILD_AFTER_OUTPUT" + inject: "V2_CHILD_FRONTMATTER:{stdout}" +--- + +# Deterministic E2E worker + +Reply exactly WORKER_DONE. diff --git a/tests/fixtures/local-openai-server.ts b/tests/fixtures/local-openai-server.ts new file mode 100644 index 0000000..e52e7c8 --- /dev/null +++ b/tests/fixtures/local-openai-server.ts @@ -0,0 +1,141 @@ +import { appendFile } from "fs/promises" + +const port = Number(process.argv[2] ?? "0") +const logPath = process.argv[3] +let callNumber = 0 +type JsonObject = Record + +const asObject = (value: unknown): JsonObject => + typeof value === "object" && value !== null && !Array.isArray(value) + ? value as JsonObject + : {} + +const json = (body: unknown, status = 200) => new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, +}) + +const textOfMessages = (messages: unknown[]): string => JSON.stringify(messages) + +const toolDetails = (tool: unknown): { name: string; schema: JsonObject } => { + const wrapper = asObject(tool) + const functionDetails = asObject(wrapper.function) + const definition = Object.keys(functionDetails).length > 0 ? functionDetails : wrapper + return { + name: String(definition.name ?? ""), + schema: asObject(definition.parameters ?? definition.input_schema), + } +} + +const chooseTool = (tools: unknown[], mode: "shell" | "subagent"): { name: string; schema: JsonObject } | undefined => { + const details = tools.map(toolDetails) + const expectedName = mode === "subagent" ? "subagent" : "shell" + return details.find(tool => tool.name === expectedName) +} + +const shellQuote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'` + +const shellArguments = (schema: JsonObject, target: string): Record => { + const properties = asObject(schema.properties) + const args: Record = {} + if ("command" in properties) args.command = `printf V2_MODEL_SHELL > ${shellQuote(target)}` + if ("description" in properties) args.description = "Write the deterministic E2E marker" + if ("timeout" in properties) args.timeout = 10_000 + return args +} + +const subagentArguments = (schema: JsonObject): Record => { + const properties = asObject(schema.properties) + const args: Record = {} + const childPrompt = "V2_E2E_SUBAGENT_CHILD reply exactly WORKER_DONE." + for (const key of Object.keys(properties)) { + if (key === "agent" || key === "subagent_type") args[key] = "e2e-worker" + else if (key === "prompt" || key === "task") args[key] = childPrompt + else if (key === "description") args[key] = "Run the deterministic E2E worker" + else if (key === "background") args[key] = false + } + return args +} + +const response = (body: Record, stream: boolean): Response => { + if (!stream) return json(body) + const encoder = new TextEncoder() + const streamBody = new ReadableStream({ + start(controller) { + const choices = (body.choices as Array>) ?? [] + const choice = asObject(choices[0]) + const message = asObject(choice.message) + const delta: Record = { role: "assistant" } + if (message.tool_calls) { + delta.tool_calls = (message.tool_calls as Array).map((toolCall, index) => ({ + ...toolCall, + index, + })) + } + if (typeof message.content === "string") delta.content = message.content + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ ...body, object: "chat.completion.chunk", choices: [{ index: 0, delta, finish_reason: null }] })}\n\n`)) + controller.enqueue(encoder.encode(`data: ${JSON.stringify({ ...body, object: "chat.completion.chunk", choices: [{ index: 0, delta: {}, finish_reason: choice.finish_reason }] })}\n\n`)) + controller.enqueue(encoder.encode("data: [DONE]\n\n")) + controller.close() + }, + }) + return new Response(streamBody, { headers: { "content-type": "text/event-stream" } }) +} + +const server = Bun.serve({ + hostname: "127.0.0.1", + port, + async fetch(request) { + const url = new URL(request.url) + if (url.pathname === "/v1/models") { + return json({ object: "list", data: [{ id: "deterministic", object: "model", owned_by: "e2e" }] }) + } + if (url.pathname !== "/v1/chat/completions" || request.method !== "POST") return new Response("not found", { status: 404 }) + + const body = await request.json() as { messages?: unknown[]; tools?: unknown[]; stream?: boolean } + const messages = body.messages ?? [] + const prompt = textOfMessages(messages) + const tools = body.tools ?? [] + const isChild = prompt.includes("V2_E2E_SUBAGENT_CHILD") + const hasToolMessage = messages.some(message => asObject(message).role === "tool") + const isSubagent = prompt.includes("V2_E2E_SUBAGENT_ROOT") && !hasToolMessage + const isShell = prompt.includes("V2_E2E_SHELL") && !hasToolMessage + const mode = isSubagent ? "subagent" : isShell ? "shell" : "text" + const selected = mode === "subagent" || mode === "shell" ? chooseTool(tools, mode) : undefined + const target = prompt.match(/V2_E2E_SHELL_FILE=([^\s"\\]+)/)?.[1] ?? "v2-e2e-model.txt" + const id = `e2e-call-${++callNumber}` + const toolCall = selected && mode !== "text" + ? { + id, + type: "function", + function: { + name: selected.name, + arguments: JSON.stringify(mode === "shell" ? shellArguments(selected.schema, target) : subagentArguments(selected.schema)), + }, + } + : undefined + const content = isChild ? "WORKER_DONE" : "DONE" + const completion = { + id: `e2e-completion-${callNumber}`, + object: "chat.completion", + created: Math.floor(Date.now() / 1000), + model: "deterministic", + choices: [{ + index: 0, + message: toolCall ? { role: "assistant", content: null, tool_calls: [toolCall] } : { role: "assistant", content }, + finish_reason: toolCall ? "tool_calls" : "stop", + }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + } + + if (logPath) { + await appendFile(logPath, `${JSON.stringify({ + body, + decision: { mode, tool: selected?.name, schema: selected?.schema }, + })}\n`) + } + return response(completion, body.stream === true) + }, +}) + +console.log(`READY http://127.0.0.1:${server.port}`) From 8b9811471af00964ab0af6f08f58832ede283a4d Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:40:00 +0000 Subject: [PATCH 19/32] docs: document stable V2 support and test modes --- OPENCODE_V2_IMPACT_REPORT.md | 11 ++++++++++- README.md | 26 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/OPENCODE_V2_IMPACT_REPORT.md b/OPENCODE_V2_IMPACT_REPORT.md index 996b603..8015f14 100644 --- a/OPENCODE_V2_IMPACT_REPORT.md +++ b/OPENCODE_V2_IMPACT_REPORT.md @@ -1,6 +1,15 @@ # OpenCode 2 Impact and Migration Report -Status: Implemented dual-host beta adapter +Status: Historical beta migration research; superseded by the stable V2 adapter + +Current implementation (2026-09-21): the adapter targets `@opencode/cli@2.0.12` +and `@opencode/plugin@2.0.12`, with V1 support starting at 1.18.29. Real-host V2 +tests now cover injection, subagents, and plugin removal/reload with a +deterministic local provider, plus a separate hosted free-model test. See the +README's OpenCode 2 section for current +installation and testing instructions. The beta package names, version floors, +and release recommendations below are retained as historical research, not +current compatibility guidance. Last updated: 2026-08-30 Doc Class: report Doc Type: research diff --git a/README.md b/README.md index 7c142e9..eb93163 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ hooks: - [Features](#features) - [Installation](#installation) -- [OpenCode 2 Beta](#opencode-2-beta) +- [OpenCode 2](#opencode-2) - [Configuration](#configuration) - [Examples](#examples) - [Template Placeholders](#template-placeholders) @@ -168,19 +168,20 @@ You can set up tool hooks to only trigger on specific arguments via `when.toolAr ## Installation -Add to your `opencode.json`: +Add to your `opencode.json` (OpenCode V2): ```jsonc { - "plugin": ["opencode-command-hooks"], + "$schema": "https://opencode.ai/config.json", + "plugins": ["opencode-command-hooks"], } ``` -The dual-host package requires OpenCode V1 1.18.23 or newer. Older V1 releases do not recognize the package's `./server` entrypoint. +For OpenCode V1, use `"plugin"` instead of `"plugins"`. The supported V1 minimum is 1.18.29, which is also exercised in CI. -## OpenCode 2 Beta +## OpenCode 2 -The same `opencode-command-hooks` package supports OpenCode V1 and V2. OpenCode V1 1.18.23 or newer automatically loads the package's `./server` export, while OpenCode V2 loads the package root. Existing V1 configuration therefore remains valid when upgrading hosts: +The same `opencode-command-hooks` package supports OpenCode V1 and V2. Supported V1 hosts automatically load the package's `./server` export, while OpenCode V2 loads the package root. Existing V1 configuration remains valid when upgrading hosts: ```jsonc { @@ -188,13 +189,22 @@ The same `opencode-command-hooks` package supports OpenCode V1 and V2. OpenCode } ``` -The V2 adapter currently targets `@opencode-ai/cli@0.0.0-beta-18684`. OpenCode 2 migrates the singular V1 `plugin` setting to its `plugins` representation when loading the config. Verify the plugin ID with `opencode2 api get /api/plugin` after the host finishes activating plugins. +The V2 adapter targets stable `@opencode/cli@2.0.12` and uses the matching `@opencode/plugin@2.0.12` API. OpenCode 2 normalizes the singular V1 `plugin` setting to `plugins`; use `plugins` for new V2 configurations. Verify the plugin ID with `opencode2 api get /api/plugin` after the host finishes activating plugins. The V2 adapter supports tool before/after hooks, `toolArgs` filters, agent frontmatter hooks, session start/idle hooks, explicit project-directory execution, and injection through synthetic context. Existing V1 config vocabulary remains valid: the V2 `subagent` tool and its `agent` argument are normalized to `task` and `subagent_type` for matching. V2 server plugins cannot currently show TUI toasts. When a matched hook requests one, the adapter emits one diagnostic and continues command execution and injection. Synthetic injections use `resume: false` so an idle hook does not create an unsolicited model turn. -Run the current beta host smoke test with `npm run test:v2:e2e`. +V2 has two complementary real-host test modes. Both build and install the npm-packed plugin into an isolated project with the pinned stable CLI: + +| Command | Model | Coverage | +| --- | --- | --- | +| `npm run test:v2:e2e` | Deterministic local responses | Tool/session hooks, model-visible injection, real subagent frontmatter hooks, root/child idle filtering, and cleanup across plugin removal/reload | +| `npm run test:v2:e2e:live` | Hosted OpenCode free model | Real model-selected shell execution, before/after and session hooks, and persisted synthetic injection | + +The deterministic suite needs npm registry access for installation but no model credentials or hosted model. The live suite waits for the host's model catalog and selects an available, tool-capable, zero-cost `opencode/*-free` model. Set `OPENCODE2_E2E_MODEL=opencode/-free` to choose another advertised free model; paid models are rejected, and user credentials/config are not inherited. Live failures remain failures rather than silently skipping or falling back to a stub. Both modes run in PR and release CI, with the live PR check reported separately so provider outages are distinguishable from deterministic regressions. + +Use `OPENCODE2_CLI_VERSION=` with either E2E command to check another V2 host release. Update the CLI pin and matching plugin dependency together when advancing the supported version. `npm run test:v2` runs adapter/package tests without a host; ordinary `npm test` skips the opt-in host suites. The separate V1 suite (`npm run test:v1:e2e`) uses a hosted credential-free model or `OPENCODE_E2E_MODEL`. ## Configuration From 4f1a4f6f9c530d19d4a75009e16226af8faa00d5 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:54:16 +0000 Subject: [PATCH 20/32] test: allow package builds to finish on CI runners --- tests/v2.package.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index c91e113..9acee7f 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -30,5 +30,5 @@ describe("dual-host package artifact", () => { expect(typeof v2.default.setup).toBe("function") expect(v1.default.id).toBe("opencode-command-hooks") expect(typeof v1.default.server).toBe("function") - }) + }, 30_000) }) From af10cfefd80be68b6fc0dfedd927779f1a83004a Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:44:03 -0400 Subject: [PATCH 21/32] fix: load the plugin by package name in OpenCode V2 OpenCode V2 resolves a package's `./server` export before its root, so installing `opencode-command-hooks` by name loaded the V1 descriptor and V2 rejected it. The repository E2E loaded the plugin from a directory path, which skips package exports and hid the failure. `./server` now exports one plain definition carrying both the V1 `server` and V2 `setup` adapters, which every current host accepts. The package root stays a callable plugin for V1 releases before 1.3.4, which call each root export as a plugin function. Root `server.js` and `index.js` mirror the exports for directory installs. --- index.js | 2 +- package.json | 9 +++++---- server.js | 1 + src/entry.ts | 12 ++++++++++++ src/server.ts | 9 +++++++-- src/v2.ts | 4 ---- tests/e2e.v2.test.ts | 2 +- tests/v2.package.test.ts | 38 ++++++++++++++++++++++++-------------- 8 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 server.js create mode 100644 src/entry.ts delete mode 100644 src/v2.ts diff --git a/index.js b/index.js index c912f23..804f210 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -export { default } from "./dist/v2.js" +export { default } from "./dist/entry.js" diff --git a/package.json b/package.json index a80f212..0e700ae 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,12 @@ "version": "0.7.1", "description": "OpenCode plugin for declaratively attaching shell commands to tool, subagent, and session events", "type": "module", - "main": "dist/v2.js", - "types": "dist/v2.d.ts", + "main": "dist/entry.js", + "types": "dist/entry.d.ts", "exports": { ".": { - "types": "./dist/v2.d.ts", - "import": "./dist/v2.js" + "types": "./dist/entry.d.ts", + "import": "./dist/entry.js" }, "./server": { "types": "./dist/server.d.ts", @@ -18,6 +18,7 @@ "files": [ "dist", "index.js", + "server.js", "types" ], "scripts": { diff --git a/server.js b/server.js new file mode 100644 index 0000000..c829723 --- /dev/null +++ b/server.js @@ -0,0 +1 @@ +export { default } from "./dist/server.js" diff --git a/src/entry.ts b/src/entry.ts new file mode 100644 index 0000000..851e073 --- /dev/null +++ b/src/entry.ts @@ -0,0 +1,12 @@ +import type { PluginInput } from "@opencode-ai/plugin" +import { CommandHooksPlugin } from "./index.js" +import plugin from "./server.js" + +/** + * Package root for OpenCode V1 before 1.3.4, which ignores the `./server` + * export and calls every root export as a plugin function. Current hosts + * resolve `./server` first, so they never load this module. + */ +const legacyPlugin = Object.assign((input: PluginInput) => CommandHooksPlugin(input), plugin) + +export default legacyPlugin diff --git a/src/server.ts b/src/server.ts index 625d7ce..0ce7bdd 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,9 +1,14 @@ -import type { PluginModule } from "@opencode-ai/plugin" import { CommandHooksPlugin } from "./index.js" +import { createV2Plugin } from "./v2/plugin.js" +/** + * Plugin definition for every host that reads the `./server` export: + * OpenCode V1 1.3.4+ uses `server`, and OpenCode V2 uses `setup`. + */ const plugin = { id: "opencode-command-hooks", server: CommandHooksPlugin, -} satisfies PluginModule + setup: createV2Plugin().setup, +} export default plugin diff --git a/src/v2.ts b/src/v2.ts deleted file mode 100644 index 0b52411..0000000 --- a/src/v2.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Plugin } from "@opencode/plugin" -import { createV2Plugin } from "./v2/plugin.js" - -export default Plugin.define(createV2Plugin()) diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index b62b813..6cc5817 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -172,7 +172,7 @@ const pluginIsLoaded = async (): Promise => { try { const response = await fetch(`${hostUrl}/api/plugin`, { headers: apiHeaders(), signal: AbortSignal.timeout(2_000) }) if (!response.ok) return undefined - return (await response.text()).includes("opencode-command-hooks.v2") + return (await response.text()).includes("\"opencode-command-hooks\"") } catch { return undefined } diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index 9acee7f..5691983 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -1,8 +1,10 @@ import { describe, expect, it } from "bun:test" import { $ } from "bun" +// Each OpenCode host resolves the package differently; these assertions mirror +// what each loader requires so one npm package works everywhere by name. describe("dual-host package artifact", () => { - it("exports V2 at the root and V1 through the server entrypoint", async () => { + it("serves every OpenCode host from one package", async () => { await $`npm run build`.quiet() const output = await $`npm pack --dry-run --json --ignore-scripts`.text() const [pack] = JSON.parse(output) as Array<{ @@ -12,23 +14,31 @@ describe("dual-host package artifact", () => { const manifest = await Bun.file("package.json").json() as { name: string exports: Record - engines: Record } + const files = pack.files.map(file => file.path) expect(pack.id).toStartWith("opencode-command-hooks@") - expect(pack.files.map(file => file.path)).toContain("dist/v2.js") - expect(pack.files.map(file => file.path)).toContain("dist/server.js") - expect(pack.files.map(file => file.path)).toContain("index.js") expect(manifest.name).toBe("opencode-command-hooks") - expect(manifest.exports["."]).toBeDefined() - expect(manifest.exports["./server"]).toBeDefined() - expect(manifest.engines.opencode).toBe(">=1.18.29") + for (const file of ["dist/entry.js", "dist/server.js", "index.js", "server.js"]) { + expect(files).toContain(file) + } + + // V1 1.3.4+ and V2 both resolve `./server` first: V1 reads `server`, and + // V2's schema requires a plain object with `setup`. + const server = await import("opencode-command-hooks/server") + expect(Object.keys(server)).toEqual(["default"]) + expect(typeof server.default).toBe("object") + expect(server.default.id).toBe("opencode-command-hooks") + expect(typeof server.default.server).toBe("function") + expect(typeof server.default.setup).toBe("function") + + // V1 before 1.3.4 imports the root and calls every export as a plugin. + const root = await import("opencode-command-hooks") + expect(Object.keys(root)).toEqual(["default"]) + expect(typeof root.default).toBe("function") - const v2 = await import("opencode-command-hooks") - const v1 = await import("opencode-command-hooks/server") - expect(v2.default.id).toBe("opencode-command-hooks.v2") - expect(typeof v2.default.setup).toBe("function") - expect(v1.default.id).toBe("opencode-command-hooks") - expect(typeof v1.default.server).toBe("function") + // Local directory installs load `server.js`, falling back to `index.js`. + expect((await import("../server.js")).default).toBe(server.default) + expect((await import("../index.js")).default).toBe(root.default) }, 30_000) }) From 1882915cdc2695a1e91633906f03c3d69febd230 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:44:12 -0400 Subject: [PATCH 22/32] fix: stop skipping the plugin on older OpenCode V1 hosts The `engines.opencode` range made every V1 release before 1.18.29 skip the plugin as incompatible, although the V1 adapter still runs on them. Package-name installs on OpenCode 1.0 through 1.18 now load as they did with 0.6.x. --- package.json | 3 +-- tests/v2.package.test.ts | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0e700ae..cff8423 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "typescript-eslint": "^8.69.0" }, "engines": { - "bun": ">=1.0.0", - "opencode": ">=1.18.29" + "bun": ">=1.0.0" } } diff --git a/tests/v2.package.test.ts b/tests/v2.package.test.ts index 5691983..03d3128 100644 --- a/tests/v2.package.test.ts +++ b/tests/v2.package.test.ts @@ -14,6 +14,7 @@ describe("dual-host package artifact", () => { const manifest = await Bun.file("package.json").json() as { name: string exports: Record + engines: Record } const files = pack.files.map(file => file.path) @@ -22,6 +23,8 @@ describe("dual-host package artifact", () => { for (const file of ["dist/entry.js", "dist/server.js", "index.js", "server.js"]) { expect(files).toContain(file) } + // An engines range would make older V1 hosts skip the plugin. + expect(manifest.engines.opencode).toBeUndefined() // V1 1.3.4+ and V2 both resolve `./server` first: V1 reads `server`, and // V2's schema requires a plain object with `setup`. From 9543851cce537744b2730c205d1fd089b7019685 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:50:57 -0400 Subject: [PATCH 23/32] test: install the plugin by package name across OpenCode hosts Publishes the packed plugin to a throwaway local registry and configures it as `"plugin": ["opencode-command-hooks"]` in V1 1.3.3 (package root), V1 1.3.4 (first `./server` release), the latest V1, and the latest V2. Each host runs a real deterministic shell tool call and must execute the before, after, and (where the host waits for it) idle hooks. Directory-path installs bypass package exports, so this is the only coverage of the entrypoint each host actually selects. Against the previous layout it fails on V1 1.3.3, V1 1.3.4, and V2. --- .github/workflows/ci.yml | 24 +++ package.json | 3 +- tests/e2e.hosts.test.ts | 258 ++++++++++++++++++++++++++ tests/fixtures/local-openai-server.ts | 5 +- 4 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 tests/e2e.hosts.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e27aa06..a2c1e12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,30 @@ jobs: - name: Test packed package in OpenCode 2 with a deterministic provider run: npm run test:v2:e2e + hosts: + name: Package-name install across OpenCode hosts + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.9" + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Install the packed plugin by name in V1 and V2 hosts + run: npm run test:hosts:e2e + v2-live: name: V2 live-model E2E runs-on: ubuntu-latest diff --git a/package.json b/package.json index cff8423..4d61f26 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "typecheck:bun-run": "bun run src/index.ts", "test:v1:e2e": "npm run build && OPENCODE_E2E=1 bun test tests/e2e.v1-headless.test.ts", "dev": "tsc --watch", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test:hosts:e2e": "OPENCODE_HOSTS_E2E=1 bun test tests/e2e.hosts.test.ts" }, "keywords": [ "opencode", diff --git a/tests/e2e.hosts.test.ts b/tests/e2e.hosts.test.ts new file mode 100644 index 0000000..2ea2380 --- /dev/null +++ b/tests/e2e.hosts.test.ts @@ -0,0 +1,258 @@ +import { afterAll, beforeAll, describe, expect, it } from "bun:test" +import { $ } from "bun" +import { existsSync } from "fs" +import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises" +import { tmpdir } from "os" +import { join, resolve } from "path" + +/** + * Installs the packed plugin by package name, the way users configure it, in + * each supported OpenCode host. Directory-path installs bypass package + * exports, so only a registry install proves which entrypoint a host selects. + */ + +const enabled = process.env.OPENCODE_HOSTS_E2E === "1" +const repositoryRoot = resolve(import.meta.dir, "..") +const providerFixture = resolve(repositoryRoot, "tests/fixtures/local-openai-server.ts") + +interface Host { + name: string + generation: "v1" | "v2" + spec: string + // Old V1 `run` can exit before the idle hook finishes, so idle is asserted + // only where the host reliably waits for it. + idle: boolean +} + +const hosts: Host[] = [ + { name: "V1 1.3.3 (package root)", generation: "v1", spec: "opencode-ai@1.3.3", idle: false }, + { name: "V1 1.3.4 (./server)", generation: "v1", spec: "opencode-ai@1.3.4", idle: false }, + { name: "V1 latest", generation: "v1", spec: "opencode-ai@latest", idle: true }, + { name: "V2 latest", generation: "v2", spec: "@opencode/cli@latest", idle: true }, +] + +let workspace = "" +let registryUrl = "" +let registry: Bun.Subprocess | undefined +let provider: Bun.ReadableSubprocess | undefined +let providerUrl = "" + +const sleep = (ms: number) => new Promise(done => setTimeout(done, ms)) + +const readUntil = async (stream: ReadableStream, pattern: RegExp, timeoutMs: number): Promise => { + const reader = stream.getReader() + const decoder = new TextDecoder() + const deadline = Date.now() + timeoutMs + let output = "" + while (Date.now() < deadline) { + const chunk = await Promise.race([reader.read(), sleep(deadline - Date.now()).then(() => undefined)]) + if (!chunk || chunk.done) break + output += decoder.decode(chunk.value) + const match = output.match(pattern) + if (match) { + reader.releaseLock() + return match[1] ?? match[0] + } + } + throw new Error(`Timed out waiting for ${pattern}:\n${output}`) +} + +const freePort = (): number => { + const probe = Bun.serve({ port: 0, fetch: () => new Response() }) + const port = probe.port + probe.stop(true) + return port +} + +const stop = async (child: Bun.Subprocess | undefined) => { + if (!child || child.exitCode !== null) return + child.kill(9) + await Promise.race([child.exited, sleep(5_000)]) +} + +const startRegistry = async () => { + const directory = join(workspace, "registry") + await mkdir(directory, { recursive: true }) + const port = freePort() + await writeFile(join(directory, "config.yaml"), [ + "storage: ./storage", + "auth:", + " htpasswd:", + " file: ./htpasswd", + "uplinks:", + " npmjs:", + " url: https://registry.npmjs.org/", + " timeout: 60s", + "packages:", + " 'opencode-command-hooks':", + " access: $all", + " publish: $all", + " '**':", + " access: $all", + " proxy: npmjs", + `listen: 127.0.0.1:${port}`, + "log: { type: stdout, format: pretty, level: warn }", + "", + ].join("\n")) + registry = Bun.spawn(["npx", "--yes", "verdaccio@6.10.4", "--config", "config.yaml"], { + cwd: directory, + stdout: "ignore", + stderr: "ignore", + }) + registryUrl = `http://127.0.0.1:${port}/` + for (let attempt = 0; attempt < 120; attempt++) { + const ready = await fetch(`${registryUrl}-/ping`).then(response => response.ok).catch(() => false) + if (ready) return + await sleep(500) + } + throw new Error("Local npm registry did not start") +} + +const publishPackage = async () => { + const userconfig = join(workspace, "publish.npmrc") + await writeFile(userconfig, `registry=${registryUrl}\n//${registryUrl.slice("http://".length)}:_authToken=e2e\n`) + await $`npm run build`.cwd(repositoryRoot).quiet() + const archive = (await $`npm pack --ignore-scripts --pack-destination ${workspace}`.cwd(repositoryRoot).text()) + .trim().split("\n").at(-1) + if (!archive) throw new Error("npm pack did not return an archive name") + await $`npm publish ${join(workspace, archive)} --userconfig ${userconfig} --tag latest`.quiet() +} + +const hostConfig = (host: Host) => { + const config: Record = { + $schema: "https://opencode.ai/config.json", + // Unchanged V1-style configuration, which V2 migrates. + plugin: ["opencode-command-hooks"], + model: "local/deterministic", + } + if (host.generation === "v1") { + config.provider = { + local: { + npm: "@ai-sdk/openai-compatible", + name: "Deterministic local provider", + options: { baseURL: `${providerUrl}/v1` }, + models: { deterministic: { name: "Deterministic", tool_call: true } }, + }, + } + } else { + config.providers = { + local: { + name: "Deterministic local provider", + package: "@opencode/ai/providers/openai-compatible", + settings: { baseURL: `${providerUrl}/v1` }, + models: { + deterministic: { + modelID: "deterministic", + capabilities: { tools: true, input: ["text"], output: ["text"] }, + limit: { context: 32_768, output: 4_096 }, + }, + }, + }, + } + } + return config +} + +const runHost = async (host: Host) => { + const root = await mkdtemp(join(workspace, "host-")) + const install = join(root, "install") + const project = join(root, "project") + const home = join(root, "home") + for (const directory of [install, join(project, ".opencode"), home, join(root, "tmp")]) { + await mkdir(directory, { recursive: true }) + } + await writeFile(join(install, "package.json"), "{\"private\":true}") + await $`npm install --no-audit --no-fund ${host.spec}`.cwd(install).quiet() + const binary = join(install, "node_modules", ".bin", "opencode") + + await writeFile(join(home, ".npmrc"), `registry=${registryUrl}\n`) + await writeFile(join(project, "opencode.json"), JSON.stringify(hostConfig(host), null, 2)) + await writeFile(join(project, ".opencode", "command-hooks.jsonc"), JSON.stringify({ + tool: [ + { id: "hosts-before", when: { phase: "before", tool: "*" }, run: "printf before > hook-before.txt" }, + { id: "hosts-after", when: { phase: "after", tool: "*" }, run: "printf after > hook-after.txt" }, + ], + session: [{ id: "hosts-idle", when: { event: "session.idle" }, run: "printf idle > hook-idle.txt" }], + })) + + const env: Record = { + PATH: process.env.PATH ?? "", + PWD: project, + HOME: home, + TMPDIR: join(root, "tmp"), + XDG_CONFIG_HOME: join(home, ".config"), + XDG_DATA_HOME: join(home, ".local", "share"), + XDG_CACHE_HOME: join(home, ".cache"), + XDG_STATE_HOME: join(home, ".local", "state"), + npm_config_registry: registryUrl, + BUN_CONFIG_REGISTRY: registryUrl, + OPENCODE_DISABLE_AUTOUPDATE: "1", + CI: "1", + } + // V1 `run` talks to its in-process server without credentials. + if (host.generation === "v2") Object.assign(env, { OPENCODE_PASSWORD: "hosts-e2e", OPENCODE_SERVER_PASSWORD: "hosts-e2e" }) + + const run = async (args: string[]) => { + const child = Bun.spawn([binary, ...args], { cwd: project, env, stdout: "pipe", stderr: "pipe" }) + const timer = setTimeout(() => child.kill(9), 150_000) + const [exitCode, stdout, stderr] = await Promise.all([ + child.exited, + new Response(child.stdout).text(), + new Response(child.stderr).text(), + ]) + clearTimeout(timer) + return { exitCode, output: `${stdout}\n${stderr}` } + } + + const prompt = "V2_E2E_SHELL V2_E2E_SHELL_FILE=model-shell.txt" + let logs = "" + let server: Bun.ReadableSubprocess | undefined + try { + if (host.generation === "v1") { + // The first run installs the plugin; older hosts can race that install. + logs += (await run(["run", "--print-logs", "-m", "local/deterministic", "Reply DONE."])).output + for (const file of ["hook-before.txt", "hook-after.txt", "hook-idle.txt"]) await rm(join(project, file), { force: true }) + logs += (await run(["run", "--print-logs", "-m", "local/deterministic", prompt])).output + } else { + server = Bun.spawn([binary, "serve", "--hostname", "127.0.0.1", "--port", "0", "--print-logs"], { + cwd: project, env, stdout: "pipe", stderr: "pipe", + }) + const url = await readUntil(server.stdout, /listening on (http:\/\/\S+)/, 120_000) + logs += (await run(["run", "--server", url, "--model", "local/deterministic", "--auto", prompt])).output + } + const expected = ["model-shell.txt", "hook-before.txt", "hook-after.txt", ...(host.idle ? ["hook-idle.txt"] : [])] + for (let attempt = 0; attempt < 30 && expected.some(file => !existsSync(join(project, file))); attempt++) await sleep(500) + return Object.fromEntries(expected.map(file => [file, existsSync(join(project, file))])) as Record & { logs?: string } + } finally { + await stop(server) + if (!process.env.OPENCODE_HOSTS_E2E_KEEP) await rm(root, { recursive: true, force: true }) + if (logs) await writeFile(join(workspace, `${host.spec.replaceAll(/[@/]/g, "_")}.log`), logs) + } +} + +describe.skipIf(!enabled)("package-name installs across OpenCode hosts", () => { + beforeAll(async () => { + workspace = await mkdtemp(join(tmpdir(), "opencode-hooks-hosts-")) + provider = Bun.spawn([process.execPath, providerFixture, "0"], { stdout: "pipe", stderr: "ignore" }) + providerUrl = await readUntil(provider.stdout, /READY (http:\/\/\S+)/, 10_000) + await startRegistry() + await publishPackage() + }, 300_000) + + afterAll(async () => { + await stop(provider) + await stop(registry) + if (workspace && !process.env.OPENCODE_HOSTS_E2E_KEEP) await rm(workspace, { recursive: true, force: true }) + }) + + for (const host of hosts) { + it(`runs tool and session hooks in ${host.name}`, async () => { + const result = await runHost(host) + const log = await readFile(join(workspace, `${host.spec.replaceAll(/[@/]/g, "_")}.log`), "utf8").catch(() => "") + const context = `${host.spec}\n${log.split("\n").filter(line => /plugin|command-hooks|error/i.test(line)).join("\n")}` + for (const [file, present] of Object.entries(result)) { + expect(present, `${file} missing\n${context}`).toBe(true) + } + }, 300_000) + } +}) diff --git a/tests/fixtures/local-openai-server.ts b/tests/fixtures/local-openai-server.ts index e52e7c8..e7f4ef7 100644 --- a/tests/fixtures/local-openai-server.ts +++ b/tests/fixtures/local-openai-server.ts @@ -29,8 +29,9 @@ const toolDetails = (tool: unknown): { name: string; schema: JsonObject } => { const chooseTool = (tools: unknown[], mode: "shell" | "subagent"): { name: string; schema: JsonObject } | undefined => { const details = tools.map(toolDetails) - const expectedName = mode === "subagent" ? "subagent" : "shell" - return details.find(tool => tool.name === expectedName) + // OpenCode V2 names its shell tool `shell`; V1 names it `bash`. + const expectedNames = mode === "subagent" ? ["subagent"] : ["shell", "bash"] + return details.find(tool => expectedNames.includes(tool.name)) } const shellQuote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'` From bd0458bb2dc6b2cd4648f418c2d7ff9d71a7de5a Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:51:27 -0400 Subject: [PATCH 24/32] docs: describe automatic V1 and V2 support from one package Every OpenCode 1.x release and OpenCode 2 load the same package name without configuration changes. Document how each host selects its entrypoint, the plugin ID, and the package-name host matrix. --- OPENCODE_V2_IMPACT_REPORT.md | 23 ++++++++++++----------- README.md | 8 ++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/OPENCODE_V2_IMPACT_REPORT.md b/OPENCODE_V2_IMPACT_REPORT.md index 8015f14..a3b31f3 100644 --- a/OPENCODE_V2_IMPACT_REPORT.md +++ b/OPENCODE_V2_IMPACT_REPORT.md @@ -3,7 +3,7 @@ Status: Historical beta migration research; superseded by the stable V2 adapter Current implementation (2026-09-21): the adapter targets `@opencode/cli@2.0.12` -and `@opencode/plugin@2.0.12`, with V1 support starting at 1.18.29. Real-host V2 +and `@opencode/plugin@2.0.12` and supports every OpenCode 1.x release. Real-host V2 tests now cover injection, subagents, and plugin removal/reload with a deterministic local provider, plus a separate hosted free-model test. See the README's OpenCode 2 section for current @@ -23,7 +23,7 @@ Report topic: OpenCode 2 plugin and client migration OpenCode 2 is currently a beta distributed separately from stable OpenCode 1. The upstream project warns that its APIs, configuration, data, and plugin APIs may still change. V1 and V2 plugin modules use incompatible runtime contracts, but current OpenCode hosts can select different modules from one npm package. -`opencode-command-hooks` now exports a V2 descriptor with a unique `id` and `setup` function from its package root. Its `./server` export provides the V1 `{ id, server }` descriptor. OpenCode V1 1.18.23 or newer selects `./server`, while OpenCode V2 selects the root. +`opencode-command-hooks` exports one plain `{ id, server, setup }` definition from `./server`. OpenCode V2 resolves `./server` before the package root and reads `setup`; OpenCode V1 1.3.4 or newer selects `./server` and reads `server`. The package root is a callable V1 plugin for V1 releases before 1.3.4, which call every root export as a plugin function. The current OpenCode 2 beta has enough API surface to prototype most core behavior: @@ -40,14 +40,14 @@ Important V2 contracts remain unstable or incomplete: - Plugin reload isolation and config-directory reload have open defects. - The package must determine the correct location/workspace rather than use `process.cwd()` in the long-lived v2 service. -The release strategy is one package with host-specific entrypoints. Existing users retain `"plugin": ["opencode-command-hooks"]`; V2 migrates that V1 config field internally. OpenCode V1 versions older than 1.18.23 are outside the supported range because they do not select `./server`. +The release strategy is one package with host-specific entrypoints. Existing users retain `"plugin": ["opencode-command-hooks"]`; V2 migrates that V1 config field internally. OpenCode V1 releases before 1.3.4 do not select `./server`, so they load the callable package root instead. ## Implementation Addendum The opt-in beta adapter is now implemented in this repository: - `src/v2/plugin.ts` owns the V2 Promise adapter and keeps callback failures non-blocking. -- `src/v2.ts` builds to the package root and `src/server.ts` builds to the V1 `./server` export. +- `src/server.ts` builds to the shared V1/V2 `./server` export and `src/entry.ts` builds to the callable package root for V1 releases before 1.3.4. - Development pins the V2 plugin contract to `@opencode-ai/plugin@0.0.0-beta-18684` through an npm alias. - Tool hooks use direct V2 event input, and `subagent`/`agent` are normalized to the existing `task`/`subagent_type` config vocabulary. - Session lookup supplies the authoritative project directory and agent for config discovery and command execution. @@ -334,13 +334,14 @@ Confidence: high. ### Decision -Publish one `opencode-command-hooks` package with a V2 root export and a V1 `./server` export. +Publish one `opencode-command-hooks` package whose `./server` export carries both adapters and whose root is a callable V1 plugin. Why this is safest: - Existing configuration keeps the same package name through a host upgrade. -- Current V1 hosts select `./server` without loading the incompatible V2 descriptor. -- V2 hosts load the package root using the documented V2 descriptor contract. +- V1 1.3.4+ and V2 both resolve `./server` first, so one plain definition with `server` and `setup` satisfies both. +- V1 releases before 1.3.4 load the package root, which remains a callable plugin. +- V2 rejects a function default export, so the shared definition must stay a plain object. - Both adapters share config, matching, execution, and template behavior. - One packed artifact can be tested against both hosts before publication. @@ -351,7 +352,7 @@ Why this is safest: | Separate v2 package | Strong isolation | Package-name fragmentation and config changes | Rejected because host-specific exports are supported | | Same package, v2 on `next`/`v2` dist-tag | Preserves one package name | Hosts still resolve one package version and users must pin | Not needed | | Same package, v2 major on `latest` | Conventional semver | OpenCode auto-installs bare specs; many users do not pin plugin versions | Do not do while v1 remains common | -| One package with `.` and `./server` | One artifact and unchanged config | Requires OpenCode V1 1.18.23 or newer | Selected | +| One package with `.` and `./server` | One artifact and unchanged config on every host | Entry shapes must satisfy each host loader; covered by the package-name host matrix | Selected | ### Version policy @@ -493,7 +494,7 @@ Actions: - Publish `opencode-command-hooks` only after the packed artifact passes both host suites. - State the exact supported OpenCode 2 beta build. -- Document the OpenCode V1 1.18.23 minimum and unchanged package configuration. +- Document unchanged package configuration across OpenCode 1.x and 2. - Require users to verify active plugin IDs with `opencode2 api get /api/plugin`. - Collect issue reports with host version, plugin version, event type, and server logs. @@ -615,7 +616,7 @@ Rollback triggers: - [x] Recheck the beta guide, exact published package types, and `v2` source before implementation. - [ ] Obtain upstream clarification on session hook naming, location access, toast/logging, and synthetic delivery. - [x] Add packed V2 artifact verification while preserving the V1 regression suite. -- [x] Replace the isolated beta package with V2 `.` and V1 `./server` exports. +- [x] Replace the isolated beta package with one package serving V1 and V2 by name. - [ ] Confirm npm ownership before publishing any prerelease. - [x] Enable the isolated pinned-host E2E loading gate. -- [ ] Expand to a dual-host runtime matrix when the blocking host defects are resolved. +- [x] Install the packed package by name across V1 1.3.3, V1 1.3.4, latest V1, and latest V2. diff --git a/README.md b/README.md index 9f1dc3c..7323486 100644 --- a/README.md +++ b/README.md @@ -212,11 +212,11 @@ Add to your `opencode.json` (OpenCode V2): } ``` -For OpenCode V1, use `"plugin"` instead of `"plugins"`. The supported V1 minimum is 1.18.29, which is also exercised in CI. +For OpenCode V1, use `"plugin"` instead of `"plugins"`. Every OpenCode 1.x release is supported. ## OpenCode 2 -The same `opencode-command-hooks` package supports OpenCode V1 and V2. Supported V1 hosts automatically load the package's `./server` export, while OpenCode V2 loads the package root. Existing V1 configuration remains valid when upgrading hosts: +The same `opencode-command-hooks` package supports OpenCode V1 and V2, and each host selects the right adapter automatically. OpenCode V2 and V1 1.3.4+ load the package's `./server` export, which carries both adapters; earlier V1 releases load the package root. Existing V1 configuration remains valid when upgrading hosts: ```jsonc { @@ -224,7 +224,7 @@ The same `opencode-command-hooks` package supports OpenCode V1 and V2. Supported } ``` -The V2 adapter targets stable `@opencode/cli@2.0.12` and uses the matching `@opencode/plugin@2.0.12` API. OpenCode 2 normalizes the singular V1 `plugin` setting to `plugins`; use `plugins` for new V2 configurations. Verify the plugin ID with `opencode2 api get /api/plugin` after the host finishes activating plugins. +The V2 adapter targets stable `@opencode/cli@2.0.12` and uses the matching `@opencode/plugin@2.0.12` API. OpenCode 2 normalizes the singular V1 `plugin` setting to `plugins`; use `plugins` for new V2 configurations. Verify the `opencode-command-hooks` plugin ID with `opencode api get /api/plugin` after the host finishes activating plugins. The V2 adapter supports tool before/after hooks, `toolArgs` filters, agent frontmatter hooks, session start/idle hooks, explicit project-directory execution, and injection through synthetic context. Existing V1 config vocabulary remains valid: the V2 `subagent` tool and its `agent` argument are normalized to `task` and `subagent_type` for matching. @@ -239,7 +239,7 @@ V2 has two complementary real-host test modes. Both build and install the npm-pa The deterministic suite needs npm registry access for installation but no model credentials or hosted model. The live suite waits for the host's model catalog and selects an available, tool-capable, zero-cost `opencode/*-free` model. Set `OPENCODE2_E2E_MODEL=opencode/-free` to choose another advertised free model; paid models are rejected, and user credentials/config are not inherited. Live failures remain failures rather than silently skipping or falling back to a stub. Both modes run in PR and release CI, with the live PR check reported separately so provider outages are distinguishable from deterministic regressions. -Use `OPENCODE2_CLI_VERSION=` with either E2E command to check another V2 host release. Update the CLI pin and matching plugin dependency together when advancing the supported version. `npm run test:v2` runs adapter/package tests without a host; ordinary `npm test` skips the opt-in host suites. The separate V1 suite (`npm run test:v1:e2e`) uses a hosted credential-free model or `OPENCODE_E2E_MODEL`. +Use `OPENCODE2_CLI_VERSION=` with either E2E command to check another V2 host release. Update the CLI pin and matching plugin dependency together when advancing the supported version. `npm run test:hosts:e2e` publishes the packed plugin to a local registry and installs it by package name in V1 1.3.3, V1 1.3.4, and the latest V1 and V2 hosts, which is the only path that exercises the entrypoint each host selects. `npm run test:v2` runs adapter/package tests without a host; ordinary `npm test` skips the opt-in host suites. The separate V1 suite (`npm run test:v1:e2e`) uses a hosted credential-free model or `OPENCODE_E2E_MODEL`. ## Configuration From 4bf8f352628dad2e8ddf19171bb4914de11d3405 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:51:35 -0400 Subject: [PATCH 25/32] ci: gate releases on package-name installs across OpenCode hosts --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a260ef1..de42c87 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,6 +69,9 @@ jobs: - name: Test packed package in OpenCode 2 with a hosted free model run: npm run test:v2:e2e:live + - name: Install the packed plugin by name in V1 and V2 hosts + run: npm run test:hosts:e2e + publish-npm: needs: verify runs-on: ubuntu-latest From 75e66f38476addc007aa54f24f18a1971ade75c3 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:09:08 -0400 Subject: [PATCH 26/32] test: drive V1 E2E with the deterministic local provider The V1 suite picked a hosted free model, which now answers with "Unexpected server error", so the check failed on any commit. It now uses the local OpenAI-compatible fixture by default, like the V2 suite; `OPENCODE_E2E_MODEL` still selects a hosted model. The fixture issues V1 `write` and `task` tool calls in addition to V2 `shell`/`subagent`. With the fast local model, injection and subagent evidence come from the model's request log instead of host log lines that are not flushed before `opencode run` exits. The sandbox path is canonical so macOS temp symlinks do not turn writes into external directory requests. --- README.md | 2 +- tests/e2e.v1-headless.test.ts | 98 +++++++++++++++++++-------- tests/fixtures/local-openai-server.ts | 39 ++++++++--- 3 files changed, 100 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 7323486..c98a8b9 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ V2 has two complementary real-host test modes. Both build and install the npm-pa The deterministic suite needs npm registry access for installation but no model credentials or hosted model. The live suite waits for the host's model catalog and selects an available, tool-capable, zero-cost `opencode/*-free` model. Set `OPENCODE2_E2E_MODEL=opencode/-free` to choose another advertised free model; paid models are rejected, and user credentials/config are not inherited. Live failures remain failures rather than silently skipping or falling back to a stub. Both modes run in PR and release CI, with the live PR check reported separately so provider outages are distinguishable from deterministic regressions. -Use `OPENCODE2_CLI_VERSION=` with either E2E command to check another V2 host release. Update the CLI pin and matching plugin dependency together when advancing the supported version. `npm run test:hosts:e2e` publishes the packed plugin to a local registry and installs it by package name in V1 1.3.3, V1 1.3.4, and the latest V1 and V2 hosts, which is the only path that exercises the entrypoint each host selects. `npm run test:v2` runs adapter/package tests without a host; ordinary `npm test` skips the opt-in host suites. The separate V1 suite (`npm run test:v1:e2e`) uses a hosted credential-free model or `OPENCODE_E2E_MODEL`. +Use `OPENCODE2_CLI_VERSION=` with either E2E command to check another V2 host release. Update the CLI pin and matching plugin dependency together when advancing the supported version. `npm run test:hosts:e2e` publishes the packed plugin to a local registry and installs it by package name in V1 1.3.3, V1 1.3.4, and the latest V1 and V2 hosts, which is the only path that exercises the entrypoint each host selects. `npm run test:v2` runs adapter/package tests without a host; ordinary `npm test` skips the opt-in host suites. The separate V1 suite (`npm run test:v1:e2e`) drives the host with the same deterministic local provider; set `OPENCODE_E2E_MODEL` to run it against a hosted model instead. ## Configuration diff --git a/tests/e2e.v1-headless.test.ts b/tests/e2e.v1-headless.test.ts index 3dbc2af..184b26a 100644 --- a/tests/e2e.v1-headless.test.ts +++ b/tests/e2e.v1-headless.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeAll, afterAll } from "bun:test" -import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, statSync, unlinkSync, rmSync } from "fs" -import { join } from "path" +import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, realpathSync, statSync, unlinkSync, rmSync } from "fs" +import { join, resolve } from "path" import { tmpdir } from "os" import { $ } from "bun" @@ -21,10 +21,17 @@ const LOG_WINDOW_MS = 15 * 60 * 1000 const LOG_FALLBACK_FILES = 3 const OPENCODE_COMMAND_TIMEOUT_MS = 110_000 const E2E_ENABLED = process.env.OPENCODE_E2E === "1" -let E2E_MODEL = process.env.OPENCODE_E2E_MODEL ?? "" +// Hosted models are opt-in; by default a deterministic local provider drives +// the real host so results never depend on an external model service. +const HOSTED_MODEL = process.env.OPENCODE_E2E_MODEL ?? "" +const E2E_MODEL = HOSTED_MODEL || "local/deterministic" +const PROVIDER_FIXTURE = resolve(import.meta.dir, "fixtures", "local-openai-server.ts") +let providerProcess: Bun.ReadableSubprocess | undefined +let providerUrl = "" function createTestSandbox(): void { - TEST_SANDBOX_DIR = mkdtempSync(join(tmpdir(), "opencode-command-hooks-e2e-")) + // Canonical paths keep tool permissions inside the project when tmpdir is a symlink (macOS). + TEST_SANDBOX_DIR = realpathSync(mkdtempSync(join(tmpdir(), "opencode-command-hooks-e2e-"))) TEST_CONFIG_DIR = join(TEST_SANDBOX_DIR, "project") TEST_OPENCODE_SUBDIR = join(TEST_CONFIG_DIR, ".opencode") TEST_OPENCODE_CONFIG = join(TEST_CONFIG_DIR, "opencode.jsonc") @@ -66,17 +73,38 @@ function formatOpenCodeCommand(args: string[]): string { return ["opencode", ...args].map(argument => JSON.stringify(argument)).join(" ") } -function selectFreeOpenCodeModel(output: string): string { - const models = output - .split("\n") - .map(line => line.trim()) - .filter(line => line.startsWith("opencode/") && line.endsWith("-free")) - const model = ["opencode/mimo-v2.5-free", "opencode/north-mini-code-free"] - .find(candidate => models.includes(candidate)) ?? models[0] - if (!model) { - throw new Error(`OpenCode did not advertise a credential-free model:\n${output}`) +const providerLogPath = () => join(TEST_SANDBOX_DIR, "provider.jsonl") + +/** + * Wait until text appears in model context: the local provider's request log, + * or the host log when a hosted model is used. + */ +async function waitForModelContext(text: string, timeoutMs = 10000): Promise { + if (HOSTED_MODEL) return waitForLogMatch(content => content.includes(text), timeoutMs) + const start = Date.now() + let content = "" + while (Date.now() - start < timeoutMs) { + content = existsSync(providerLogPath()) ? readFileSync(providerLogPath(), "utf-8") : "" + if (content.includes(text)) return text + await new Promise(resolve => setTimeout(resolve, 500)) } - return model + return content +} + +async function startLocalProvider(): Promise { + providerProcess = Bun.spawn([process.execPath, PROVIDER_FIXTURE, "0", providerLogPath()], { stdout: "pipe", stderr: "ignore" }) + const reader = providerProcess.stdout.getReader() + const decoder = new TextDecoder() + let output = "" + const deadline = Date.now() + 10_000 + while (!providerUrl && Date.now() < deadline) { + const chunk = await reader.read() + if (chunk.done) break + output += decoder.decode(chunk.value) + providerUrl = output.match(/READY (http:\/\/\S+)/)?.[1] ?? "" + } + reader.releaseLock() + if (!providerUrl) throw new Error(`Local provider did not start:\n${output}`) } function formatOpenCodeCommandError( @@ -222,10 +250,19 @@ function writeTestOpencodeConfig(): void { if (!existsSync(TEST_CONFIG_DIR)) { mkdirSync(TEST_CONFIG_DIR, { recursive: true }) } - const pluginConfig = { - // OpenCode V1 must select the package's ./server export instead of its V2 root. + const pluginConfig: Record = { plugin: [TEST_PLUGIN_DIR], } + if (!HOSTED_MODEL) { + pluginConfig.provider = { + local: { + npm: "@ai-sdk/openai-compatible", + name: "Deterministic local provider", + options: { baseURL: `${providerUrl}/v1` }, + models: { deterministic: { name: "Deterministic", tool_call: true } }, + }, + } + } writeFileSync(TEST_OPENCODE_CONFIG, JSON.stringify(pluginConfig, null, 2)) } @@ -321,16 +358,14 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { // Verify the executable works in the same isolated environment as the tests. await runOpenCodeCommand(["--version"]) - if (!E2E_MODEL) { - const models = await runOpenCodeCommand(["models", "opencode"]) - E2E_MODEL = selectFreeOpenCodeModel(models.stdout) - } + if (!HOSTED_MODEL) await startLocalProvider() // Enable the plugin in the test opencode config writeTestOpencodeConfig() }, 120_000) afterAll(() => { + if (providerProcess?.exitCode === null) providerProcess.kill(9) if (TEST_SANDBOX_DIR) { rmSync(TEST_SANDBOX_DIR, { recursive: true, force: true }) } @@ -367,12 +402,11 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { try { const opencodeResponse = await runOpenCode( - `Use the write tool, not bash, to create ${writtenFileName} with exactly this content and no trailing newline: ${writtenContent}` + `Use the write tool, not bash, to create ${writtenFileName} with exactly this content and no trailing newline: ${writtenContent} E2E_WRITE_FILE=${writtenFileName} E2E_WRITE_CONTENT=${writtenContent}` ) expect(await waitForFileContent(writtenFilePath, writtenContent)).toBe(writtenContent) expect(await waitForFileContent(hookProofFilePath, hookProofContent)).toBe(hookProofContent) - const logContent = await waitForLogMatch(content => content.includes(injectedMarker)) - expect(logContent).toContain(injectedMarker) + expect(await waitForModelContext(injectedMarker)).toContain(injectedMarker) expect(opencodeResponse).not.toContain(toastMarker) } finally { for (const filePath of [writtenFilePath, hookProofFilePath]) { @@ -390,7 +424,7 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { const rootOnlyFilePath = join(TEST_CONFIG_DIR, rootOnlyFileName) const allSessionsFilePath = join(TEST_CONFIG_DIR, allSessionsFileName) const runSubagent = () => runOpenCode( - "Use the task tool to invoke the e2e-worker subagent. Ask it to reply exactly WORKER_DONE, then reply exactly PARENT_DONE." + "Use the task tool to invoke the e2e-worker subagent. Ask it to reply exactly WORKER_DONE, then reply exactly PARENT_DONE. V2_E2E_SUBAGENT_ROOT" ) writeTestAgent("e2e-worker", `--- @@ -416,12 +450,16 @@ Reply exactly WORKER_DONE. await runSubagent() expect(await waitForMinimumLineCount(rootOnlyFilePath, 1)).toBe(1) - const logContent = getRecentLogContent() - const childCreation = logContent - .split("\n") - .find(line => line.includes("message=created") && line.includes("agent=e2e-worker")) - expect(childCreation).toBeDefined() - expect(childCreation).not.toContain("parentID=undefined") + if (HOSTED_MODEL) { + const childCreation = (await waitForLogMatch(content => content.includes("agent=e2e-worker"))) + .split("\n") + .find(line => line.includes("message=created") && line.includes("agent=e2e-worker")) + expect(childCreation).toBeDefined() + expect(childCreation).not.toContain("parentID=undefined") + } else { + // The child session's own model request proves the subagent ran. + expect(await waitForModelContext("V2_E2E_SUBAGENT_CHILD")).toContain("V2_E2E_SUBAGENT_CHILD") + } writeTestConfig({ session: [ diff --git a/tests/fixtures/local-openai-server.ts b/tests/fixtures/local-openai-server.ts index e7f4ef7..6ab2e3c 100644 --- a/tests/fixtures/local-openai-server.ts +++ b/tests/fixtures/local-openai-server.ts @@ -27,13 +27,18 @@ const toolDetails = (tool: unknown): { name: string; schema: JsonObject } => { } } -const chooseTool = (tools: unknown[], mode: "shell" | "subagent"): { name: string; schema: JsonObject } | undefined => { - const details = tools.map(toolDetails) - // OpenCode V2 names its shell tool `shell`; V1 names it `bash`. - const expectedNames = mode === "subagent" ? ["subagent"] : ["shell", "bash"] - return details.find(tool => expectedNames.includes(tool.name)) +type ToolMode = "shell" | "subagent" | "write" + +// OpenCode V2 names these tools `shell` and `subagent`; V1 names them `bash` and `task`. +const toolNames: Record = { + shell: ["shell", "bash"], + subagent: ["subagent", "task"], + write: ["write"], } +const chooseTool = (tools: unknown[], mode: ToolMode): { name: string; schema: JsonObject } | undefined => + tools.map(toolDetails).find(tool => toolNames[mode].includes(tool.name)) + const shellQuote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'` const shellArguments = (schema: JsonObject, target: string): Record => { @@ -45,6 +50,17 @@ const shellArguments = (schema: JsonObject, target: string): Record => { + const properties = asObject(schema.properties) + const args: Record = {} + const filePath = prompt.match(/E2E_WRITE_FILE=([^\s"\\]+)/)?.[1] ?? "e2e-write.txt" + const content = prompt.match(/E2E_WRITE_CONTENT=([^\s"\\]+)/)?.[1] ?? "E2E_WRITE" + if ("filePath" in properties) args.filePath = filePath + if ("path" in properties) args.path = filePath + if ("content" in properties) args.content = content + return args +} + const subagentArguments = (schema: JsonObject): Record => { const properties = asObject(schema.properties) const args: Record = {} @@ -101,8 +117,9 @@ const server = Bun.serve({ const hasToolMessage = messages.some(message => asObject(message).role === "tool") const isSubagent = prompt.includes("V2_E2E_SUBAGENT_ROOT") && !hasToolMessage const isShell = prompt.includes("V2_E2E_SHELL") && !hasToolMessage - const mode = isSubagent ? "subagent" : isShell ? "shell" : "text" - const selected = mode === "subagent" || mode === "shell" ? chooseTool(tools, mode) : undefined + const isWrite = prompt.includes("E2E_WRITE_FILE=") && !hasToolMessage + const mode: ToolMode | "text" = isSubagent ? "subagent" : isShell ? "shell" : isWrite ? "write" : "text" + const selected = mode === "text" ? undefined : chooseTool(tools, mode) const target = prompt.match(/V2_E2E_SHELL_FILE=([^\s"\\]+)/)?.[1] ?? "v2-e2e-model.txt" const id = `e2e-call-${++callNumber}` const toolCall = selected && mode !== "text" @@ -111,7 +128,13 @@ const server = Bun.serve({ type: "function", function: { name: selected.name, - arguments: JSON.stringify(mode === "shell" ? shellArguments(selected.schema, target) : subagentArguments(selected.schema)), + arguments: JSON.stringify( + mode === "shell" + ? shellArguments(selected.schema, target) + : mode === "write" + ? writeArguments(selected.schema, prompt) + : subagentArguments(selected.schema), + ), }, } : undefined From bbc45841ecb76d5c0322c4b05a1e1bbfa7382490 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:09:08 -0400 Subject: [PATCH 27/32] test: use canonical temp paths in host E2E suites On macOS the temp directory is a symlink, so the V2 server (started with PWD) and the CLI (process cwd) saw two project locations. Session lookups then missed, and the subagent and reload tests failed locally while passing on Linux CI. --- tests/e2e.hosts.test.ts | 5 +++-- tests/e2e.v2.test.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/e2e.hosts.test.ts b/tests/e2e.hosts.test.ts index 2ea2380..da81407 100644 --- a/tests/e2e.hosts.test.ts +++ b/tests/e2e.hosts.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test" import { $ } from "bun" import { existsSync } from "fs" -import { mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises" +import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "fs/promises" import { tmpdir } from "os" import { join, resolve } from "path" @@ -232,7 +232,8 @@ const runHost = async (host: Host) => { describe.skipIf(!enabled)("package-name installs across OpenCode hosts", () => { beforeAll(async () => { - workspace = await mkdtemp(join(tmpdir(), "opencode-hooks-hosts-")) + // Canonical paths keep each host in one project location when tmpdir is a symlink (macOS). + workspace = await realpath(await mkdtemp(join(tmpdir(), "opencode-hooks-hosts-"))) provider = Bun.spawn([process.execPath, providerFixture, "0"], { stdout: "pipe", stderr: "ignore" }) providerUrl = await readUntil(provider.stdout, /READY (http:\/\/\S+)/, 10_000) await startRegistry() diff --git a/tests/e2e.v2.test.ts b/tests/e2e.v2.test.ts index 6cc5817..f2c3af2 100644 --- a/tests/e2e.v2.test.ts +++ b/tests/e2e.v2.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, it } from "bun:test" -import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "fs/promises" +import { access, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "fs/promises" import { tmpdir } from "os" import { join, resolve } from "path" import { $ } from "bun" @@ -369,7 +369,8 @@ const startHost = async (): Promise<{ process: Bun.ReadableSubprocess; url: stri describe.skipIf(!enabled)("OpenCode V2 deterministic and opt-in hosted/free-model real-host E2E", () => { beforeAll(async () => { - projectDirectory = await mkdtemp(join(tmpdir(), "opencode-hooks-v2-e2e-")) + // Canonical paths keep the host and CLI in one location when tmpdir is a symlink (macOS). + projectDirectory = await realpath(await mkdtemp(join(tmpdir(), "opencode-hooks-v2-e2e-"))) configPath = join(projectDirectory, "opencode.jsonc") hooksPath = join(projectDirectory, ".opencode", "command-hooks.jsonc") homeDirectory = join(projectDirectory, "home") From 056107e7eee2a83d2d1263cfbd9889192318a1f8 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:17:21 -0400 Subject: [PATCH 28/32] test: run V1 E2E against a long-lived server With a fast local model, a one-shot `opencode run` exits as soon as the session is idle, cutting off the idle hook it should observe. Attaching runs to `opencode serve` keeps the host alive like the TUI does. --- tests/e2e.v1-headless.test.ts | 68 ++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/tests/e2e.v1-headless.test.ts b/tests/e2e.v1-headless.test.ts index 184b26a..e64bb68 100644 --- a/tests/e2e.v1-headless.test.ts +++ b/tests/e2e.v1-headless.test.ts @@ -28,6 +28,10 @@ const E2E_MODEL = HOSTED_MODEL || "local/deterministic" const PROVIDER_FIXTURE = resolve(import.meta.dir, "fixtures", "local-openai-server.ts") let providerProcess: Bun.ReadableSubprocess | undefined let providerUrl = "" +// A long-lived server, like the TUI, so hooks that run after the session goes +// idle are not cut short by a one-shot `opencode run` exiting. +let serverProcess: Bun.ReadableSubprocess | undefined +let serverUrl = "" function createTestSandbox(): void { // Canonical paths keep tool permissions inside the project when tmpdir is a symlink (macOS). @@ -91,6 +95,38 @@ async function waitForModelContext(text: string, timeoutMs = 10000): Promise, pattern: RegExp, timeoutMs: number): Promise { + const reader = stream.getReader() + const decoder = new TextDecoder() + const deadline = Date.now() + timeoutMs + let output = "" + try { + while (Date.now() < deadline) { + const chunk = await Promise.race([ + reader.read(), + new Promise(resolve => setTimeout(resolve, deadline - Date.now())), + ]) + if (!chunk || chunk.done) break + output += decoder.decode(chunk.value) + const match = output.match(pattern) + if (match?.[1]) return match[1] + } + } finally { + reader.releaseLock() + } + throw new Error(`Timed out waiting for ${pattern}:\n${output}`) +} + +async function startServer(): Promise { + serverProcess = Bun.spawn(["opencode", "serve", "--hostname", "127.0.0.1", "--port", "0"], { + cwd: TEST_CONFIG_DIR, + env: getOpenCodeEnvironment(), + stdout: "pipe", + stderr: "pipe", + }) + serverUrl = await readUntil(serverProcess.stdout, /listening on (http:\/\/\S+)/, 60_000) +} + async function startLocalProvider(): Promise { providerProcess = Bun.spawn([process.execPath, PROVIDER_FIXTURE, "0", providerLogPath()], { stdout: "pipe", stderr: "ignore" }) const reader = providerProcess.stdout.getReader() @@ -334,7 +370,7 @@ async function waitForMinimumLineCount( * Run OpenCode with a prompt and capture its successful output. */ async function runOpenCode(prompt: string): Promise { - const result = await runOpenCodeCommand(["-m", E2E_MODEL, "run", prompt]) + const result = await runOpenCodeCommand(["run", "--attach", serverUrl, "--dir", TEST_CONFIG_DIR, "-m", E2E_MODEL, prompt]) await new Promise(resolve => setTimeout(resolve, 2000)) return `${result.stdout}${result.stderr}` } @@ -362,14 +398,29 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { // Enable the plugin in the test opencode config writeTestOpencodeConfig() + // The server discovers agents at startup. + writeTestAgent("e2e-worker", `--- +description: E2E worker that returns a fixed response +mode: subagent +model: ${E2E_MODEL} +--- + +Reply exactly WORKER_DONE. +`) + await startServer() }, 120_000) - afterAll(() => { - if (providerProcess?.exitCode === null) providerProcess.kill(9) + afterAll(async () => { + for (const child of [serverProcess, providerProcess]) { + if (child?.exitCode === null) { + child.kill(9) + await child.exited + } + } if (TEST_SANDBOX_DIR) { rmSync(TEST_SANDBOX_DIR, { recursive: true, force: true }) } - }) + }, 30_000) it("runs one write after-hook transaction with isolated observable evidence", async () => { const uniqueId = generateUniqueId() @@ -427,15 +478,6 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { "Use the task tool to invoke the e2e-worker subagent. Ask it to reply exactly WORKER_DONE, then reply exactly PARENT_DONE. V2_E2E_SUBAGENT_ROOT" ) - writeTestAgent("e2e-worker", `--- -description: E2E worker that returns a fixed response -mode: subagent -model: ${E2E_MODEL} ---- - -Reply exactly WORKER_DONE. -`) - try { writeTestConfig({ session: [ From 62482214648a25fe8717678b4697f220a63b7375 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:17:37 -0400 Subject: [PATCH 29/32] docs: keep the README to one line on OpenCode 2 support --- README.md | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index c98a8b9..b0a7e92 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ hooks: - [Features](#features) - [Installation](#installation) -- [OpenCode 2](#opencode-2) - [Configuration](#configuration) - [Examples](#examples) - [Template Placeholders](#template-placeholders) @@ -203,43 +202,15 @@ is not placed in the environment. ## Installation -Add to your `opencode.json` (OpenCode V2): +Add to your `opencode.json`: ```jsonc { - "$schema": "https://opencode.ai/config.json", - "plugins": ["opencode-command-hooks"], + "plugin": ["opencode-command-hooks"], } ``` -For OpenCode V1, use `"plugin"` instead of `"plugins"`. Every OpenCode 1.x release is supported. - -## OpenCode 2 - -The same `opencode-command-hooks` package supports OpenCode V1 and V2, and each host selects the right adapter automatically. OpenCode V2 and V1 1.3.4+ load the package's `./server` export, which carries both adapters; earlier V1 releases load the package root. Existing V1 configuration remains valid when upgrading hosts: - -```jsonc -{ - "plugin": ["opencode-command-hooks"] -} -``` - -The V2 adapter targets stable `@opencode/cli@2.0.12` and uses the matching `@opencode/plugin@2.0.12` API. OpenCode 2 normalizes the singular V1 `plugin` setting to `plugins`; use `plugins` for new V2 configurations. Verify the `opencode-command-hooks` plugin ID with `opencode api get /api/plugin` after the host finishes activating plugins. - -The V2 adapter supports tool before/after hooks, `toolArgs` filters, agent frontmatter hooks, session start/idle hooks, explicit project-directory execution, and injection through synthetic context. Existing V1 config vocabulary remains valid: the V2 `subagent` tool and its `agent` argument are normalized to `task` and `subagent_type` for matching. - -V2 server plugins cannot currently show TUI toasts. When a matched hook requests one, the adapter emits one diagnostic and continues command execution and injection. Synthetic injections use `resume: false` so an idle hook does not create an unsolicited model turn. - -V2 has two complementary real-host test modes. Both build and install the npm-packed plugin into an isolated project with the pinned stable CLI: - -| Command | Model | Coverage | -| --- | --- | --- | -| `npm run test:v2:e2e` | Deterministic local responses | Tool/session hooks, model-visible injection, real subagent frontmatter hooks, root/child idle filtering, and cleanup across plugin removal/reload | -| `npm run test:v2:e2e:live` | Hosted OpenCode free model | Real model-selected shell execution, before/after and session hooks, and persisted synthetic injection | - -The deterministic suite needs npm registry access for installation but no model credentials or hosted model. The live suite waits for the host's model catalog and selects an available, tool-capable, zero-cost `opencode/*-free` model. Set `OPENCODE2_E2E_MODEL=opencode/-free` to choose another advertised free model; paid models are rejected, and user credentials/config are not inherited. Live failures remain failures rather than silently skipping or falling back to a stub. Both modes run in PR and release CI, with the live PR check reported separately so provider outages are distinguishable from deterministic regressions. - -Use `OPENCODE2_CLI_VERSION=` with either E2E command to check another V2 host release. Update the CLI pin and matching plugin dependency together when advancing the supported version. `npm run test:hosts:e2e` publishes the packed plugin to a local registry and installs it by package name in V1 1.3.3, V1 1.3.4, and the latest V1 and V2 hosts, which is the only path that exercises the entrypoint each host selects. `npm run test:v2` runs adapter/package tests without a host; ordinary `npm test` skips the opt-in host suites. The separate V1 suite (`npm run test:v1:e2e`) drives the host with the same deterministic local provider; set `OPENCODE_E2E_MODEL` to run it against a hosted model instead. +This works unchanged on OpenCode 1.x and OpenCode 2 (which also accepts `"plugins"`). On OpenCode 2, hooks cannot show toasts yet; everything else works the same. ## Configuration From daa7b9916fa6c4ffd8e326621c797a7c734c796e Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:37:39 -0400 Subject: [PATCH 30/32] fix: drop the unused @opencode/plugin dependency The V2 adapter declares its own types and the shared entry no longer calls `Plugin.define`, so nothing imports the package. Removing it keeps it and its dependency tree out of every user's install. --- bun.lock | 561 --------------------------------------------------- package.json | 1 - 2 files changed, 562 deletions(-) diff --git a/bun.lock b/bun.lock index 477b01e..e6c2608 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,6 @@ "dependencies": { "@opencode-ai/plugin": "^1.18.29", "@opencode-ai/sdk": "^1.18.29", - "@opencode/plugin": "2.0.12", "js-yaml": "^4.3.2", "jsonc-parser": "^3.3.1", "picomatch": "^4.0.3", @@ -29,60 +28,6 @@ "packages": { "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], - "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], - - "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "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" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], - - "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], - - "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], - - "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - - "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1057.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/fetch-http-handler": "^5.4.5", "@smithy/node-http-handler": "^4.7.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-5MliYkp2u0+2arTp5fZIaxl+xmm90LEKv/VeSxhfNQW4t0fvWJrNO429/jchWQenNoDRrOGE59VfbuZUfwFujg=="], - - "@aws-sdk/core": ["@aws-sdk/core@3.978.0", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@aws-sdk/xml-builder": "^3.972.40", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.33.3", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA=="], - - "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.70", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg=="], - - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.71", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ=="], - - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.73", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg=="], - - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.16", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/credential-provider-env": "^3.972.71", "@aws-sdk/credential-provider-http": "^3.972.73", "@aws-sdk/credential-provider-login": "^3.972.78", "@aws-sdk/credential-provider-process": "^3.972.71", "@aws-sdk/credential-provider-sso": "^3.973.15", "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw=="], - - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.78", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA=="], - - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.83", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.71", "@aws-sdk/credential-provider-http": "^3.972.73", "@aws-sdk/credential-provider-ini": "^3.973.16", "@aws-sdk/credential-provider-process": "^3.972.71", "@aws-sdk/credential-provider-sso": "^3.973.15", "@aws-sdk/credential-provider-web-identity": "^3.972.77", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ=="], - - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.71", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA=="], - - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.15", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/token-providers": "3.1129.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q=="], - - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.77", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ=="], - - "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1057.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.1057.0", "@aws-sdk/core": "^3.974.15", "@aws-sdk/credential-provider-cognito-identity": "^3.972.38", "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-login": "^3.972.45", "@aws-sdk/credential-provider-node": "^3.972.47", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/nested-clients": "^3.997.13", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-rbrEHtz11g0kxsSkYr3fx2HABNNblp4AhB2MgPvJHgYOWfJ2eBviU7Mvoaef0PW8QH6lbZDfJcnM7eKvtvz3sw=="], - - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.45", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/signature-v4-multi-region": "^3.996.46", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ=="], - - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.46", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ=="], - - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1129.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.0", "@aws-sdk/nested-clients": "^3.997.45", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA=="], - - "@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], - - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.10", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g=="], - - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.40", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA=="], - - "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], - - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.112", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.0-rc.112" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ=="], - - "@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="], - - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -101,8 +46,6 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], - "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], @@ -113,12 +56,6 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], - - "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], - "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], @@ -131,148 +68,12 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], - - "@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="], - - "@npmcli/config": ["@npmcli/config@10.8.1", "", { "dependencies": { "@npmcli/map-workspaces": "^5.0.0", "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", "ini": "^6.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "walk-up-path": "^4.0.0" } }, "sha512-MAYk9IlIGiyC0c9fnjdBSQfIFPZT0g1MfeSiD1UXTq2zJOLX55jS9/sETJHqw/7LN18JjITrhYfgCfapbmZHiQ=="], - - "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], - - "@npmcli/git": ["@npmcli/git@7.0.2", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", "semver": "^7.3.5", "which": "^6.0.0" } }, "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="], - - "@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@4.0.0", "", { "dependencies": { "npm-bundled": "^5.0.0", "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="], - - "@npmcli/map-workspaces": ["@npmcli/map-workspaces@5.0.3", "", { "dependencies": { "@npmcli/name-from-folder": "^4.0.0", "@npmcli/package-json": "^7.0.0", "glob": "^13.0.0", "minimatch": "^10.0.3" } }, "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw=="], - - "@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@9.0.3", "", { "dependencies": { "cacache": "^20.0.0", "json-parse-even-better-errors": "^5.0.0", "pacote": "^21.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5" } }, "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg=="], - - "@npmcli/name-from-folder": ["@npmcli/name-from-folder@4.0.0", "", {}, "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg=="], - - "@npmcli/node-gyp": ["@npmcli/node-gyp@5.0.0", "", {}, "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ=="], - - "@npmcli/package-json": ["@npmcli/package-json@7.0.5", "", { "dependencies": { "@npmcli/git": "^7.0.0", "glob": "^13.0.0", "hosted-git-info": "^9.0.0", "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", "spdx-expression-parse": "^4.0.0" } }, "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ=="], - - "@npmcli/promise-spawn": ["@npmcli/promise-spawn@9.0.1", "", { "dependencies": { "which": "^6.0.0" } }, "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q=="], - - "@npmcli/query": ["@npmcli/query@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ=="], - - "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], - - "@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="], - "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.31", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-Rdc1bPK06PByaGyGd0kf7JUZ4pTkexz2OOUNlqZWLpHOiMEZ+/rFGjt46ypZ3QwA697gNdWwwwQbHbKG5NMwGA=="], "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.31", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Raouthf8Lhe9edjvYeeSK7SgvdoU6bBjH9qV3f70dHoa6h+z0X2TMz/e22/wKp/StlFUZ4kIRpYYxFnY8/k01w=="], - "@opencode/ai": ["@opencode/ai@2.0.12", "", { "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", "@opencode/schema": "2.0.12", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-3ODFv7a3mKHZ92uUgJDKUM8wY2akNwpvC6YDck0U2gwkc6zfXwNXXpWAEjhdlHPujmQub6dNb0FS5St4oKpSNQ=="], - - "@opencode/client": ["@opencode/client@2.0.12", "", { "dependencies": { "@opencode/protocol": "2.0.12", "@opencode/schema": "2.0.12" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-WxZLWIv0Sx9NwoCDmkEqKPnn+NOpk3Kt9FWh5gMgrl1mDbCI+6EQtFOsgrhqJSrFZXMzFqgLMAiTTIYD7wZWpA=="], - - "@opencode/plugin": ["@opencode/plugin@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode/ai": "2.0.12", "@opencode/client": "2.0.12", "@opencode/protocol": "2.0.12", "@opencode/schema": "2.0.12", "@opencode/util": "2.0.12", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode/theme": "2.0.12", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-eUO61AEruyxK6wrL82XhjRuZ5zvoWpLdhzAWbFgoLe3/8IgwrY/4SUKGEou5WgXtzh0zWNDQQqMpaVMe66n8cQ=="], - - "@opencode/protocol": ["@opencode/protocol@2.0.12", "", { "dependencies": { "@opencode/schema": "2.0.12", "effect": "4.0.0-rc.112" } }, "sha512-YYp9ElZMW8UG7cV9kSd9j3svPTEt0sTP7G4tXeQ2icbCIwFki28x+fCiuF8wBGcpYMEcLg1GrF3aEqxPAZOpWA=="], - - "@opencode/schema": ["@opencode/schema@2.0.12", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-T3RJb371gH813pdaWpAvXAgZ3GSHpPcHSkP8nRbPsSKYOlfmNId2s8Js9aLCJHeVfIKmW3+I9poa3Bx+aiiROQ=="], - - "@opencode/util": ["@opencode/util@2.0.12", "", { "dependencies": { "@effect/opentelemetry": "4.0.0-rc.112", "@effect/platform-node": "4.0.0-rc.112", "@effect/platform-node-shared": "4.0.0-rc.112", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "cross-spawn": "7.0.6", "effect": "4.0.0-rc.112", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "pacote": "21.5.1" } }, "sha512-LgecrvN4dhsbdgG7Lt6+Cp/+0+VswyV8RJsE/mjjJ6Ns39EwFVTnZYMHT4bkR+vRL/w3IF0VCFg8Ohnfwkn3lw=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], - - "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], - - "@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="], - - "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], - - "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], - - "@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], - - "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], - - "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="], - - "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], - - "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], - - "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], - - "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="], - - "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="], - - "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], - - "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], - - "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], - - "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], - - "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="], - - "@redis/bloom": ["@redis/bloom@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag=="], - - "@redis/client": ["@redis/client@6.2.1", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0", "@opentelemetry/api": ">=1 <2" }, "optionalPeers": ["@node-rs/xxhash", "@opentelemetry/api"] }, "sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q=="], - - "@redis/json": ["@redis/json@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ=="], - - "@redis/search": ["@redis/search@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ=="], - - "@redis/time-series": ["@redis/time-series@6.2.1", "", { "peerDependencies": { "@redis/client": "^6.2.1" } }, "sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw=="], - - "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], - - "@sigstore/core": ["@sigstore/core@3.2.1", "", {}, "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g=="], - - "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.2", "", {}, "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw=="], - - "@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="], - - "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], - - "@sigstore/verify": ["@sigstore/verify@3.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], - - "@smithy/core": ["@smithy/core@3.34.1", "", { "dependencies": { "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw=="], - - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="], - - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="], - - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.8.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA=="], - - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.1", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw=="], - - "@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="], - - "@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], - - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-nxu3SgmAw9JXT2CtkU0m/XNLWpP9MsaBx1zAGAypCbYj15tIFlmcYwpF+Oh18le83d+IM9PT7ENdXnE4C+d5mA=="], - - "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-iq+cW3mAb7vfcxEEpYi3zXKpDtbrIFyanWjQl4zBq4seWD4OSxXDWSfespZxenX6aEaighn+NR3u1nU1DSvs3w=="], - - "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], - - "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], - "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], @@ -285,8 +86,6 @@ "@types/picomatch": ["@types/picomatch@4.0.3", "", {}, "sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ=="], - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.70.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.70.1", "@typescript-eslint/type-utils": "8.70.1", "@typescript-eslint/utils": "8.70.1", "@typescript-eslint/visitor-keys": "8.70.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.70.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.70.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.70.1", "@typescript-eslint/types": "8.70.1", "@typescript-eslint/typescript-estree": "8.70.1", "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw=="], @@ -307,86 +106,42 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.70.1", "", { "dependencies": { "@typescript-eslint/types": "8.70.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw=="], - "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], - "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], - - "bin-links": ["bin-links@6.0.2", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w=="], - - "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], - "brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], - "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], - "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], - - "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], - - "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], - - "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - - "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="], - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], @@ -405,10 +160,6 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], - - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - "fast-check": ["fast-check@4.10.2", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -419,8 +170,6 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], @@ -431,94 +180,40 @@ "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], - - "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], - - "gaxios": ["gaxios@7.3.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ=="], - - "gcp-metadata": ["gcp-metadata@8.1.4", "", { "dependencies": { "gaxios": "7.1.3", "google-logging-utils": "1.1.3", "json-bigint": "^1.0.0" } }, "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw=="], - - "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], - "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], - - "google-logging-utils": ["google-logging-utils@1.2.0", "", {}, "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], - - "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], - - "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], - "ip-address": ["ip-address@10.7.2", "", {}, "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], - "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - "json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], - "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], - - "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], - - "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], - - "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], - - "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], @@ -529,34 +224,8 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], - - "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], - - "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], - - "mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="], - - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], - - "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], - - "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], - - "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], - - "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], - - "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "msgpackr": ["msgpackr@2.1.0", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ=="], @@ -567,312 +236,82 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], - - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - - "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - - "node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], - "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], - "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], - - "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], - - "npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="], - - "npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="], - - "npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], - - "npm-packlist": ["npm-packlist@10.0.4", "", { "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" } }, "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng=="], - - "npm-pick-manifest": ["npm-pick-manifest@11.0.3", "", { "dependencies": { "npm-install-checks": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ=="], - - "npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "pacote": ["pacote@21.5.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], - "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], - "postcss-selector-parser": ["postcss-selector-parser@7.1.6", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], - - "proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="], - - "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], - - "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], - - "protobufjs": ["protobufjs@7.6.6", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], - "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], - - "redis": ["redis@6.2.1", "", { "dependencies": { "@redis/bloom": "6.2.1", "@redis/client": "6.2.1", "@redis/json": "6.2.1", "@redis/search": "6.2.1", "@redis/time-series": "6.2.1" } }, "sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "sigstore": ["sigstore@4.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.1", "@sigstore/tuf": "^4.0.2", "@sigstore/verify": "^3.1.1" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], - - "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - - "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], - - "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], - - "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], - - "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], - - "spdx-license-ids": ["spdx-license-ids@3.0.24", "", {}, "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ=="], - - "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], - "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="], - "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="], - "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.70.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.70.1", "@typescript-eslint/parser": "8.70.1", "@typescript-eslint/typescript-estree": "8.70.1", "@typescript-eslint/utils": "8.70.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA=="], - "undici": ["undici@8.10.2", "", {}, "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ=="], - "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "uuid": ["uuid@14.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ=="], - "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], - - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], - - "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="], - - "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], - - "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@effect/opentelemetry/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@effect/platform-node/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@effect/platform-node-shared/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - "@npmcli/arborist/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - - "@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], - - "@npmcli/git/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], - - "@npmcli/git/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], - - "@npmcli/map-workspaces/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - - "@npmcli/promise-spawn/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], - "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - "@opencode/ai/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@opencode/plugin/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@opencode/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], - - "@opencode/protocol/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@opencode/schema/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@opencode/util/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - - "@opencode/util/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "@tufjs/models/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.9", "", {}, "sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - "gcp-metadata/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], - - "gcp-metadata/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], - - "glob/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - - "ignore-walk/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - - "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "node-gyp/undici": ["undici@6.28.1", "", {}, "sha512-zWpdTVD54H48CIybL0rWQ3ukpb9d23wM7eH5RtfdmeP70cWHNjtfo7P4vZX+5CoDcO53J4Pu5uXp7lNfjc6DRA=="], - - "node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], - - "rimraf/glob": ["glob@10.5.0", "", { "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" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@npmcli/arborist/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - - "@npmcli/git/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], - - "@npmcli/map-workspaces/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - - "@npmcli/promise-spawn/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], - - "@opencode/util/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - - "@tufjs/models/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - "glob/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - - "ignore-walk/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], - - "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - - "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - - "node-gyp/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], - - "rimraf/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "@npmcli/arborist/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "@npmcli/map-workspaces/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "@opencode/util/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "@tufjs/models/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "ignore-walk/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.1.7", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g=="], - - "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], } } diff --git a/package.json b/package.json index 4d61f26..e817179 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "dependencies": { "@opencode-ai/plugin": "^1.18.29", "@opencode-ai/sdk": "^1.18.29", - "@opencode/plugin": "2.0.12", "js-yaml": "^4.3.2", "jsonc-parser": "^3.3.1", "picomatch": "^4.0.3", From d8b8b692bcbc97f39cca7a571aed580fbd5ef215 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:37:39 -0400 Subject: [PATCH 31/32] docs: remove the V2 migration research report It recorded beta-era research and is not referenced by the package or docs. The PR description covers the final design. --- OPENCODE_V2_IMPACT_REPORT.md | 622 ----------------------------------- 1 file changed, 622 deletions(-) delete mode 100644 OPENCODE_V2_IMPACT_REPORT.md diff --git a/OPENCODE_V2_IMPACT_REPORT.md b/OPENCODE_V2_IMPACT_REPORT.md deleted file mode 100644 index a3b31f3..0000000 --- a/OPENCODE_V2_IMPACT_REPORT.md +++ /dev/null @@ -1,622 +0,0 @@ -# OpenCode 2 Impact and Migration Report - -Status: Historical beta migration research; superseded by the stable V2 adapter - -Current implementation (2026-09-21): the adapter targets `@opencode/cli@2.0.12` -and `@opencode/plugin@2.0.12` and supports every OpenCode 1.x release. Real-host V2 -tests now cover injection, subagents, and plugin removal/reload with a -deterministic local provider, plus a separate hosted free-model test. See the -README's OpenCode 2 section for current -installation and testing instructions. The beta package names, version floors, -and release recommendations below are retained as historical research, not -current compatibility guidance. -Last updated: 2026-08-30 -Doc Class: report -Doc Type: research -Report Type: engineering migration -Decision Status: accepted for one-package dual-host distribution -Authority: advisory -Canonical Source: `OPENCODE_V2_IMPACT_REPORT.md` -Report topic: OpenCode 2 plugin and client migration - -## Summary - -OpenCode 2 is currently a beta distributed separately from stable OpenCode 1. The upstream project warns that its APIs, configuration, data, and plugin APIs may still change. V1 and V2 plugin modules use incompatible runtime contracts, but current OpenCode hosts can select different modules from one npm package. - -`opencode-command-hooks` exports one plain `{ id, server, setup }` definition from `./server`. OpenCode V2 resolves `./server` before the package root and reads `setup`; OpenCode V1 1.3.4 or newer selects `./server` and reads `server`. The package root is a callable V1 plugin for V1 releases before 1.3.4, which call every root export as a plugin function. - -The current OpenCode 2 beta has enough API surface to prototype most core behavior: - -- `ctx.tool.hook("execute.before", ...)` and `ctx.tool.hook("execute.after", ...)` can replace the v1 tool hooks. -- `ctx.event.subscribe()` exposes the public event stream for session lifecycle handling. -- `ctx.session.synthetic()` appears to be the intended replacement for injecting context without creating a normal user prompt. -- Existing v1-shaped config and agent Markdown locations are intended to be translated in memory. - -Important V2 contracts remain unstable or incomplete: - -- The beta guide and current `v2` branch disagree on at least one hook name: the guide uses `ctx.session.hook("request")`, while source currently defines `ctx.session.hook("context")`. -- The guide and source also differ on some tool context field names. -- Server plugins have no public equivalent to v1 `client.tui.showToast()` or `client.app.log()`. -- Plugin reload isolation and config-directory reload have open defects. -- The package must determine the correct location/workspace rather than use `process.cwd()` in the long-lived v2 service. - -The release strategy is one package with host-specific entrypoints. Existing users retain `"plugin": ["opencode-command-hooks"]`; V2 migrates that V1 config field internally. OpenCode V1 releases before 1.3.4 do not select `./server`, so they load the callable package root instead. - -## Implementation Addendum - -The opt-in beta adapter is now implemented in this repository: - -- `src/v2/plugin.ts` owns the V2 Promise adapter and keeps callback failures non-blocking. -- `src/server.ts` builds to the shared V1/V2 `./server` export and `src/entry.ts` builds to the callable package root for V1 releases before 1.3.4. -- Development pins the V2 plugin contract to `@opencode-ai/plugin@0.0.0-beta-18684` through an npm alias. -- Tool hooks use direct V2 event input, and `subagent`/`agent` are normalized to the existing `task`/`subagent_type` config vocabulary. -- Session lookup supplies the authoritative project directory and agent for config discovery and command execution. -- Injection uses `ctx.session.synthetic()` with `resume: false`; toast requests produce one explicit unsupported diagnostic. -- The event consumer maps compatibility and durable execution events to `session.start`/`session.idle`, deduplicates event IDs and session starts, and is closed during plugin cleanup. -- Unit, V1 regression, packed-artifact, and gated pinned-host E2E tests cover the adapter. -- The normal release workflow requires the current V2 host E2E to pass before publishing the shared package. - -The first one-shot standalone E2E queried the plugin list before asynchronous plugin activation completed. The corrected test starts an isolated persistent server, polls the location-scoped plugin endpoint until setup finishes, and passes against `0.0.0-beta-18684`. It uses temporary HOME/XDG directories and a test-only server password, so it does not connect to or modify the normal OpenCode service. - -## Research Question - -- How will OpenCode 2 affect `opencode-command-hooks`? -- Which features can be migrated now, which require redesign, and which are blocked? -- How should v1 and v2 support coexist while users upgrade at different times? - -## Scope - -### In Scope - -- OpenCode 2 beta plugin loader and authoring API. -- Tool and session lifecycle contracts. -- Session context injection, notifications, logging, location, and configuration. -- Direct impact on the current repository. -- Package compatibility, release strategy, testing, rollout, and rollback. - -### Out of Scope - -- Implementing the v2 adapter. -- Changing the package, source, tests, release workflow, or npm metadata. -- Promising compatibility with an API that upstream still labels beta. - -## Method - -- Inspected this repository's package metadata, source, tests, generated `dist`, and release workflow. -- Used Firecrawl for current OpenCode 1 docs, OpenCode 2 beta docs, upstream issues, pull requests, and source pages. -- Used Context7 for the current OpenCode plugin API documentation and examples. -- Audited upstream Git branches at these snapshots: - - `dev`: `45cd8d76920839e4a7b6b931c4e26b52e1495636`, 2026-07-17. - - `v2`: `987242b3e83ff7ed870b376b94394d068c7d8790`, 2026-07-17. -- Queried npm metadata for current dist-tags. At research time, stable was `1.18.3`; beta/next/dev remained prerelease channels. -- Ran five parallel focused research tracks covering the v2 API, local impact, lifecycle contracts, coexistence strategy, and rollout risks. - -## Current State - -### Stable and beta are separate - -OpenCode 1 remains the stable line. The OpenCode 2 beta is installed from `@opencode-ai/cli@next` and runs as `opencode2`, allowing side-by-side evaluation with the stable `opencode` executable. - -Upstream's migration documentation says: - -- Plugins use a new API. -- Server APIs and clients use new contracts. -- V1 plugins will not work in V2. -- Detailed plugin migration guidance will be published after the API is finalized. -- Existing v1 server config, agent definitions, commands, skills, and `.opencode` files are intended to remain compatible through in-memory translation. - -### Two upstream development surfaces matter - -The default `dev` branch and the dedicated `v2` branch currently expose different v2 plugin capabilities. - -- `dev` has early v2 package exports but lacks tool, event, and session domains in its public context. -- `v2`, which backs the beta docs, has tool hooks, event subscription, session actions, and broader client capabilities. - -This branch divergence is a strong signal not to infer a stable contract from package paths alone. Migration work should pin and test a specific OpenCode beta build and matching `@opencode-ai/plugin` package. - -## Impact Matrix - -| Surface | Current implementation | OpenCode 2 impact | Severity | Proposed treatment | -| --- | --- | --- | --- | --- | -| Plugin export | `src/index.ts:253-405` exports a v1 `Plugin` function returning hooks | V2 loader accepts only a default `{ id, setup }` or `{ id, effect }` descriptor | Critical | New v2 entrypoint/package | -| Hook registration | Returned keys such as `tool.execute.before`, `tool.execute.after`, `event`, `chat.params` | V2 setup registers hooks imperatively through `ctx.tool.hook`, `ctx.event.subscribe`, and other domains | Critical | V2 host adapter | -| Before-tool payload | Separate `input` and mutable `output.args` | One event object with mutable `event.input`; IDs and agent are included directly | High | Normalize into shared internal context | -| After-tool payload | Args cached from before hook; `tool.result` fallback and dedupe | V2 after event includes `input`, `result`, `output`, and `outputPaths`; no cache should be necessary | High | Remove v1 cache/fallback from v2 adapter | -| Session lifecycle | Generic v1 `event` handling for `session.created`, `session.idle`, and `tool.result` | V2 has a typed event stream and durable execution events; envelope uses `data`, not v1 `properties` | High | Event normalization and dedupe | -| Session injection | `client.session.promptAsync({ path, body })` | New client shape is flat; `ctx.session.synthetic({ sessionID, text, ... })` is the likely context-only primitive | High | Injection port with explicit semantics tests | -| Toast | `client.tui.showToast({ body })` | No public server-plugin toast capability in current v2 context | High for feature parity | Defer, omit with diagnostics, or add a future TUI companion | -| Logging | `client.app.log({ body })` | No public v2 server-plugin logging facade found | Medium | Internal logger port; use supported diagnostics when finalized | -| Runtime directory | `process.cwd()` drives config discovery and command execution | V2 is a long-lived, multi-location service; ambient cwd is not a reliable project location | Critical | Capture plugin location and pass cwd explicitly | -| Agent Markdown | Directly reads `.opencode/agents` and legacy `.opencode/agent`; custom `hooks` frontmatter | V2 intends to keep all v1 agent directories compatible, but custom-field handling and agent identity must be verified | High | Preserve parser; replace discovery/context adapter | -| Provider option stripping | `chat.params` deletes `hooks` and `command_hooks` | No direct v2 equivalent is documented; v2 config decoding may ignore extras, but this must be tested | Medium | Contract test before removing behavior | -| Plugin config key | README tells users to use v1 `plugin` | Native v2 key is `plugins`; v1-shaped files are translated in memory | Medium | Do not force config migration for trial users; document native form separately | -| Shell execution | Global `Bun.$` with ambient cwd | V2 does not inject v1 `$`; Bun remains available but cwd must be explicit | High | Command runner port accepting cwd | -| Error semantics | Plugin catches failures so hooks remain non-blocking | V2 docs state an uncaught runtime-hook failure fails the intercepted operation | Critical | Keep a catch-all inside every v2 callback | -| Dependencies | `@opencode-ai/plugin` and SDK are broad v1 ranges | V2 docs require matching plugin package and host versions during beta | High | Exact prerelease pins in the v2 package | -| Test harness | E2E writes v1 `plugin` config and invokes `opencode` | Needs separate v1 stable and pinned `opencode2` suites | High | Versioned contract matrix | - -## Detailed Findings - -### 1. The current package cannot load in v2 - -The package's root export is a function: - -```ts -export const CommandHooksPlugin: Plugin = async ({ client }) => { - return { - event: async (...) => {}, - "tool.execute.before": async (...) => {}, - "tool.execute.after": async (...) => {}, - } -} -``` - -The v2 loader imports the module and validates its default export as one of: - -```ts -{ id: string, setup: function } -{ id: string, effect: function } -``` - -Invalid modules are skipped and logged. There is no automatic adapter for a v1 function returning hooks. - -Confidence: high. - -### 2. Tool hooks are migratable, but their contract is different - -The current v2 beta source exposes: - -```ts -await ctx.tool.hook("execute.before", async (event) => { - event.input = updatedInput -}) - -await ctx.tool.hook("execute.after", async (event) => { - // event.input, event.result, event.output, event.outputPaths -}) -``` - -Useful differences from v1: - -- `event.agent` is present directly. -- `event.input` is available to the after hook. -- The v1 `toolCallArgsCache` should not be needed in a v2 adapter. -- The undocumented `tool.result` fallback should not be carried into v2. -- Hook names inside the plugin context omit the `tool.` prefix. - -Risks: - -- Tool input is typed as `unknown` and must be narrowed. -- Built-in tool IDs and subagent input shapes must be observed in an actual beta build. The current plugin assumes a tool named `task` with `subagent_type`; v2 terminology and permission actions use `subagent`. -- The v2 guide says uncaught hook failures fail the intercepted operation. This plugin's non-blocking promise depends on handling every expected and unexpected error internally. - -Confidence: high for API shape, medium for built-in tool identities. - -### 3. Session lifecycle needs an adapter, not direct string substitution - -The v2 public event stream includes durable events such as: - -- `session.execution.started` -- `session.execution.succeeded` -- `session.execution.failed` -- `session.execution.interrupted` -- `session.input.admitted` -- `session.input.promoted` - -Compatibility events including `session.created` and `session.idle` also appear in generated client types, but the long-term event contract remains in motion. V2 events use an envelope with fields such as `type`, `data`, `durable`, and `location`; the current code expects v1 `event.properties`. - -Recommended normalization: - -- Preserve the public command-hooks config vocabulary initially: `session.start`, `session.created`, and `session.idle`. -- Normalize v2 `session.created` to start. -- Decide whether idle means compatibility `session.idle` or durable `session.execution.succeeded`; test both and dedupe by event/session/sequence. -- Resolve agent identity with the session client if the lifecycle event does not carry it. -- Do not add a v2 `session.end` mapping until upstream defines one. The current repository advertises `session.end` in `src/types/hooks.ts:173-178` but does not implement it in `src/index.ts`; that is an existing gap, not a v2 regression. - -The v2 Promise event stream is an `AsyncIterable`. Setup must start a cancellable background consumer and return cleanup. It must not await an infinite subscription inside `setup`. - -Confidence: medium-high. Event names are implemented, but upstream explicitly warns they may change. - -### 4. `session.synthetic` is the strongest injection candidate - -The current plugin injects text with v1 `session.promptAsync`. The v2 session API provides both normal prompts and synthetic messages. Synthetic messages carry text, optional description/metadata, and delivery policy, and are represented as synthetic context in the model-facing history. - -This maps closely to the plugin's intent: add deterministic hook output to context without impersonating a normal user prompt. - -Proposed v2 mapping: - -```ts -await ctx.session.synthetic({ - sessionID, - text: message, - description: "opencode-command-hooks", - metadata: { hookId }, -}) -``` - -Before adoption, tests must establish: - -- Whether default delivery queues or steers an active execution. -- Whether injection after a tool is visible at the intended continuation boundary. -- Whether synthetic insertion can cause recursion or an unwanted new model turn. -- Ordering when several hooks inject sequentially. -- Behavior after failed/interrupted executions. - -Confidence: medium. The API exists and its data model fits, but exact operational semantics need runtime proof. - -### 5. Toast parity is currently blocked - -The v2 server plugin context does not expose a TUI client or toast method. A separate TUI plugin surface has UI toast capabilities, but a server plugin cannot currently call them through the documented v2 context. - -Options, in order: - -1. Keep `toast` in the shared config but report it as unsupported in the v2 beta package. -2. Wait for an upstream server notification capability. -3. Build a separate TUI companion only if upstream provides a supported bridge between server and TUI plugin surfaces. - -Do not silently claim toast success in v2. Injection and command execution should continue even when toast is unavailable. - -Confidence: high. - -### 6. Ambient cwd is unsafe in the v2 service - -The current implementation searches from and executes in `process.cwd()`: - -- `src/config/global.ts:268-274` -- `src/config/agent.ts:74-81` -- `src/execution/shell.ts:177-196` - -OpenCode 2 uses a background service with location-scoped plugin hosts. One process may serve multiple directories/workspaces, so process cwd is not a valid location contract. - -The v2 client returns location metadata on several calls. A beta adapter may be able to capture the host location from a location-aware client response such as `ctx.agent.list()`, but upstream should ideally expose location directly in plugin context. The adapter should not be released until it has a deterministic directory source. - -The shared command engine should receive cwd as an explicit argument. Configuration search and agent-file resolution should receive the same location rather than reading global process state. - -Confidence: high for the risk, medium for the final upstream location API. - -### 7. Existing config can coexist, but native v2 config differs - -V2 intends to translate v1 config in memory. Existing users can keep: - -```jsonc -{ - "plugin": ["opencode-command-hooks"] -} -``` - -Native v2 configuration may use: - -```jsonc -{ - "plugins": [ - "opencode-command-hooks" - ] -} -``` - -Tuple package options become `{ "package": "...", "options": { ... } }`. - -Do not advise users to convert shared config to native v2 while they still run v1 against the same file. Upstream explicitly recommends keeping v1-shaped config during side-by-side evaluation. - -The plugin's own `command-hooks.jsonc` contract is independent of OpenCode's root config and can remain stable. Its location resolution must still become host-location-aware. - -Confidence: high. - -### 8. The v2 beta is not stable enough for a production default - -Evidence of active churn and risk includes: - -- Official warning that plugin entrypoints, hooks, draft shapes, and configuration may change before stable release. -- Guide/source disagreement over `session.hook("request")` versus `session.hook("context")`. -- Guide/source disagreement over some tool context field names. -- An open issue where a bad plugin tool can poison all sessions in a location and replacement is not atomic. -- An open issue where changes inside config directories fail to trigger command, agent, and plugin reloads. -- An open issue where self-update can orphan the v2 background service and hang clients. -- A same-day fix restoring `opencode2 plugin list`, whose discussion reiterates intentional v1 incompatibility. - -Confidence: high. - -### 9. The current release artifact needs a clean baseline before branching - -`package.json` publishes `dist/index.js`, but the checked-in `dist` does not match current source behavior. For example, the source contains `chat.params` stripping and newer after-hook dedupe behavior that are absent from the built artifact inspected during this research. - -This is not caused by v2, but it matters for migration: - -- A v2 port should not fork from behavior that exists only in source and not in the shipped artifact. -- Tests should exercise the packed artifact, not only `src`. -- Release CI builds before publish, but local E2E and repository inspection can still test stale `dist`. - -Establish and document the intended v1 behavior before extracting shared code. - -Confidence: high. - -## Recommended Compatibility Strategy - -### Decision - -Publish one `opencode-command-hooks` package whose `./server` export carries both adapters and whose root is a callable V1 plugin. - -Why this is safest: - -- Existing configuration keeps the same package name through a host upgrade. -- V1 1.3.4+ and V2 both resolve `./server` first, so one plain definition with `server` and `setup` satisfies both. -- V1 releases before 1.3.4 load the package root, which remains a callable plugin. -- V2 rejects a function default export, so the shared definition must stay a plain object. -- Both adapters share config, matching, execution, and template behavior. -- One packed artifact can be tested against both hosts before publication. - -### Alternatives considered - -| Option | Benefit | Risk | Recommendation | -| --- | --- | --- | --- | -| Separate v2 package | Strong isolation | Package-name fragmentation and config changes | Rejected because host-specific exports are supported | -| Same package, v2 on `next`/`v2` dist-tag | Preserves one package name | Hosts still resolve one package version and users must pin | Not needed | -| Same package, v2 major on `latest` | Conventional semver | OpenCode auto-installs bare specs; many users do not pin plugin versions | Do not do while v1 remains common | -| One package with `.` and `./server` | One artifact and unchanged config on every host | Entry shapes must satisfy each host loader; covered by the package-name host matrix | Selected | - -### Version policy - -- Publish both adapters together under the existing `opencode-command-hooks` version. -- Keep the V2 development dependency pinned to the exact supported beta build. -- Pin `@opencode-ai/plugin` and other v2 packages to the exact matching beta build. -- Do not depend on `next`, `beta`, or `latest` ranges inside a production plugin release. -- Add host compatibility metadata when upstream confirms enforcement for the target host, but treat it as a guardrail rather than the compatibility mechanism. -- Never unpublish v1 versions needed for rollback. - -## Target Architecture - -Do not duplicate the entire plugin. Keep host-independent behavior shared and isolate host contracts. - -### Shared core - -Retain or extract only behavior that does not know about OpenCode: - -- Zod schemas and config validation. -- Global/project merge semantics. -- Hook matching. -- Template interpolation. -- Sequential command execution and output truncation. -- Non-blocking execution policy. - -### V1 adapter - -Own: - -- Legacy returned hook object. -- V1 event payload normalization. -- V1 SDK nested `path`/`body` calls. -- V1 toast and structured logging. -- Existing tool-args cache and fallback only as long as supported versions need them. - -### V2 adapter - -Own: - -- `Plugin.define({ id, setup })` default export. -- Imperative hook registration and cleanup. -- V2 tool event normalization. -- Event-stream background consumer and dedupe. -- Session lookup for missing agent context. -- Synthetic context injection. -- Explicit location/workspace and command cwd. -- Explicit unsupported-toast diagnostics until parity exists. - -Use the Promise v2 API unless Effect provides a concrete capability the plugin needs. The current codebase is Promise-based, and choosing Effect only for conformity would increase migration scope without improving behavior. - -## Draft Migration Plan - -### Gate 0: Watch upstream and freeze assumptions - -Actions: - -- Track the v2 plugin guide, `v2` branch, package exports, and migration page. -- Record one exact OpenCode 2 build and matching plugin/client package versions for every spike. -- Open or follow upstream questions for location access, server-side notifications/logging, synthetic delivery semantics, and the `request`/`context` hook-name mismatch. - -Exit criteria: - -- Tool before/after, event subscription, session synthetic, and location contracts are documented consistently with source for a pinned build. - -### Gate 1: Stabilize the v1 baseline - -Actions: - -- Rebuild `dist` and verify the packed package matches source. -- Add packed-artifact tests for current v1 behavior. -- Decide and document the actual support status of `session.end`, slash-command filters, and `tool.result` fallback. -- Pass runtime directory explicitly through the internal command/config APIs without changing v1 behavior. - -Exit criteria: - -- One reproducible v1 contract suite passes against the oldest and newest supported OpenCode 1 releases. -- `npm pack` contents are the artifact used by tests. - -### Gate 2: Introduce host ports - -Actions: - -- Define narrow internal ports for lifecycle input, location, injection, notification, and logging. -- Keep schemas, matching, templates, execution, and merge logic host-neutral. -- Move v1-specific caches and payload aliases behind the v1 adapter. - -Exit criteria: - -- Existing v1 behavior passes unchanged through the adapter boundary. -- No v2 dependency is required by the v1 package. - -### Gate 3: Build a pinned v2 prototype - -Actions: - -- Create a default v2 Promise plugin descriptor with a stable plugin ID. -- Register `execute.before` and `execute.after` hooks. -- Consume session events in a cancellable background task. -- Use direct v2 `event.input` in both phases; do not carry the v1 args cache. -- Resolve the plugin location deterministically and run commands with explicit cwd. -- Inject with `ctx.session.synthetic()` behind an adapter. -- Treat toast as unsupported with one clear diagnostic, not as a silent no-op. -- Catch all callback failures to preserve non-blocking tool execution. - -Exit criteria: - -- Prototype passes against one exact beta build. -- Before/after hooks, tool-argument filtering, session start/idle, injection, config reload, and cleanup have runtime evidence. -- A hook command failure never fails the intercepted OpenCode tool. - -### Gate 4: Add a dual-host contract matrix - -Minimum matrix: - -| Dimension | V1 | V2 beta | -| --- | --- | --- | -| Host executable | `opencode` | `opencode2` | -| Plugin install | packed v1 package | packed exact v2 package | -| Config shape | v1 `plugin` | v1 translated and native v2 `plugins` | -| Tool phases | before and after | before and after | -| Tool filters | name and args | name and args | -| Agent hooks | project/global, singular/plural dirs | translated legacy and preferred v2 dirs | -| Session lifecycle | created and idle | created and selected execution/idle mapping | -| Injection | v1 async prompt behavior | v2 synthetic behavior | -| Toast | supported | explicit unsupported status or supported replacement | -| Location | single project and nested cwd | two simultaneous locations/workspaces | -| Failures | nonzero shell, timeout, malformed config | same plus hook callback exception | -| Reload | config and plugin updates | config and plugin updates, cleanup, no duplicate hooks | - -Exit criteria: - -- No unexplained behavioral divergence. -- All expected degradations are documented and surfaced to users. -- Open v2 reload defects are fixed upstream or mitigated and tested. - -### Gate 5: Publish the dual-host package - -Actions: - -- Publish `opencode-command-hooks` only after the packed artifact passes both host suites. -- State the exact supported OpenCode 2 beta build. -- Document unchanged package configuration across OpenCode 1.x and 2. -- Require users to verify active plugin IDs with `opencode2 api get /api/plugin`. -- Collect issue reports with host version, plugin version, event type, and server logs. - -Exit criteria: - -- At least 20 representative successful canary runs across multiple projects. -- Zero tool-blocking plugin failures. -- No unresolved injection recursion, location, or duplicate-event defects. - -### Gate 6: Decide the stable package future - -After OpenCode 2 and its plugin API are stable, choose one: - -- Keep permanent `-v2` package separation while v1 remains maintained. -- Move v2 to a new major of `opencode-command-hooks`, but keep npm `latest` on the v1 line until users have clear pinning/migration guidance. -- Eventually move `latest` to v2 only after a declared migration window and adoption threshold. - -Do not make this decision during the upstream beta. - -## User Migration and Rollback - -### Side-by-side trial - -1. Keep `opencode` and `opencode-command-hooks` unchanged. -2. Install a pinned `opencode2` beta separately. -3. Use v1-shaped shared config initially so both hosts can read it. -4. Add the exact v2 plugin package only in an isolated v2 test config or disposable environment. -5. Verify plugin loading, model/provider credentials, agents, permissions, and hooks before regular use. - -### Production migration - -1. Record the exact working v1 OpenCode and plugin versions. -2. Remove the v1 plugin entry and add the exact v2 package entry in one controlled config change. -3. Verify active plugin ID, before/after execution, session injection, and project cwd. -4. Keep the old v1 config and package version available through at least one stable OpenCode 2 release cycle. - -### Rollback - -1. Stop using `opencode2` for the affected workflow. -2. Restore the exact prior v1 plugin spec and v1 config. -3. Restart OpenCode so cached plugin state and registrations are cleared. -4. Diagnose cache only if an exact package does not reload; cache deletion should not be the normal rollback mechanism. - -Rollback triggers: - -- A hook exception blocks a tool. -- Hook commands execute in the wrong directory. -- Injection triggers an unintended model turn or recursion. -- Duplicate lifecycle events execute hooks twice. -- Required toast behavior is silently lost. -- Plugin reload breaks unrelated sessions or tools. -- A beta service update causes hangs or unbounded retries. - -## Recommendations - -1. Do not port the production package until the v2 API is documented consistently with the beta source. -2. Keep the current npm package and `latest` tag v1-compatible. -3. Use a separate v2 beta package with exact OpenCode/plugin dependency pins. -4. Stabilize the packed v1 artifact before extracting shared code. -5. Separate host adapters from the schema/matching/execution core. -6. Use v2 tool hooks directly and avoid carrying v1 cache/fallback complexity forward. -7. Prototype injection with `session.synthetic`, but require runtime proof of delivery and recursion semantics. -8. Treat toast and structured logging as explicit parity gaps, not silent no-ops. -9. Make project location and shell cwd explicit before supporting the v2 background service. -10. Maintain a dual-host contract test matrix and a documented exact-version rollback path. - -## Open Questions - -- Will the stable hook be named `session.request`, `session.context`, or something else? -- What is the stable way for a server plugin to obtain its location and workspace directly? -- Will server plugins receive a supported logging and user-notification capability? -- What synthetic delivery policy is correct for injection during an active tool execution? -- Which v2 event should define the user-facing `session.idle` compatibility behavior? -- What are the stable built-in tool IDs and subagent input shapes? -- Are custom agent-frontmatter fields ignored, preserved, or forwarded into provider request options? -- Will plugin replacement become atomic and retain the last known good generation? -- What semver or compatibility guarantee will apply to the stable v2 plugin API? - -## Evidence and References - -### Primary current sources - -- [OpenCode 2 beta introduction](https://v2.opencode.ai/) - Beta status, warnings, `@next` installation, and `opencode2`. Retrieved with Firecrawl. -- [Migrate from V1](https://v2.opencode.ai/migrate-v1) - Intentional breaking changes, v1 config translation, plugin incompatibility, side-by-side guidance, and deferred migration details. Retrieved with Firecrawl. -- [OpenCode 2 plugin guide](https://v2.opencode.ai/build/plugins) - Current beta loader, setup model, context, transforms, tool hooks, version matching, and publication guidance. Retrieved with Firecrawl and corroborated with Context7. -- [OpenCode 1 plugin guide](https://opencode.ai/docs/plugins/) - Current stable returned-hooks API, injected client, event list, toast/logging, and install behavior. Retrieved with Firecrawl and Context7. -- [V2 plugin loader source](https://github.com/anomalyco/opencode/blob/v2/packages/core/src/plugin/supervisor.ts) - Default export validation, package loading, ordering, discovery, and reload flow. Retrieved with Firecrawl; verified in the upstream clone. -- [V2 Promise plugin contract](https://github.com/anomalyco/opencode/blob/v2/packages/plugin/src/v2/promise/plugin.ts) - `{ id, setup }`, context domains, and cleanup contract. Verified in the upstream clone; indexed by Context7. -- [V2 tool contract](https://github.com/anomalyco/opencode/blob/v2/packages/plugin/src/v2/effect/tool.ts) - Mutable before/after event fields and hook names. Retrieved with Firecrawl; verified in the upstream clone. -- [V2 session contract](https://github.com/anomalyco/opencode/blob/v2/packages/plugin/src/v2/effect/session.ts) - Session client subset and session hook surface. Verified in the upstream clone. -- [V2 host wiring](https://github.com/anomalyco/opencode/blob/v2/packages/core/src/plugin/host.ts) - Implemented event, tool, session, and location-scoped behavior. Verified in the upstream clone. -- [V2 session event schema](https://github.com/anomalyco/opencode/blob/v2/packages/schema/src/session-event.ts) - Durable execution and synthetic event contracts. Verified in the upstream clone. -- [V2 API design map](https://github.com/anomalyco/opencode/blob/dev/specs/v2/api.html) - Revised client/server operation model and session-pinned context. Retrieved with Firecrawl. -- [V2 core instructions](https://github.com/anomalyco/opencode/blob/dev/specs/v2/instructions.md) - Architectural direction toward typed, domain-oriented, sequential plugin hooks. Retrieved with Firecrawl. - -### Stability and risk sources - -- [PR #37540: restore plugin list diagnostics](https://github.com/anomalyco/opencode/pull/37540) - Reiterates intentional v1 plugin incompatibility and shows same-day beta repair. Retrieved with Firecrawl. -- [Issue #37533: v2 plugin list and loading report](https://github.com/anomalyco/opencode/issues/37533) - Maintainer comment that v1 plugins are incompatible with v2. Retrieved through GitHub CLI after Firecrawl discovery. -- [Issue #35963: isolate invalid plugin tools during reload](https://github.com/anomalyco/opencode/issues/35963) - Open plugin validation, atomic replacement, and location-wide failure risk. Retrieved with Firecrawl. -- [Issue #37429: config directory changes do not trigger reload](https://github.com/anomalyco/opencode/issues/37429) - Open command/agent/plugin reload defect. Retrieved with Firecrawl. -- [Issue #37521: self-update can orphan the service](https://github.com/anomalyco/opencode/issues/37521) - Open background-service hang and recovery risk. Retrieved with Firecrawl. -- [Issue #34832: v2 pre-beta event/schema audit](https://github.com/anomalyco/opencode/issues/34832) - Explicit concern about durable event names and migration-sensitive contracts. Retrieved with Firecrawl. -- [Issue #35016: execution/retry/error event redesign](https://github.com/anomalyco/opencode/issues/35016) - Direction replacing ambiguous status with typed execution lifecycle events. Retrieved with Firecrawl. -- [Issue #33896: v2 plugin skill discovery](https://github.com/anomalyco/opencode/issues/33896) - Evidence that published v2 plugin capabilities are still receiving compatibility fixes. Retrieved with Firecrawl and GitHub CLI. -- [Issue #7641: plugin and v2 SDK type mismatch](https://github.com/anomalyco/opencode/issues/7641) - Historical evidence of nested v1 versus flattened v2 client types. Retrieved through GitHub CLI after Firecrawl discovery. -- [OpenCode releases](https://github.com/anomalyco/opencode/releases) - Stable release status. Retrieved with Firecrawl and GitHub CLI. -- [npm registry: `@opencode-ai/plugin`](https://www.npmjs.com/package/@opencode-ai/plugin) - Stable and prerelease package channels. Queried with npm after Firecrawl discovery. - -## Limitations - -- OpenCode 2 is changing daily; this report is a point-in-time analysis for 2026-07-17. -- Some upstream beta documentation already differs from the current `v2` branch source. -- A V2 adapter, packed package test, and isolated pinned-host E2E loading test are now present and passing. -- No claim here should override a later stable OpenCode 2 migration guide or package contract. - -## Follow-ups - -- [x] Recheck the beta guide, exact published package types, and `v2` source before implementation. -- [ ] Obtain upstream clarification on session hook naming, location access, toast/logging, and synthetic delivery. -- [x] Add packed V2 artifact verification while preserving the V1 regression suite. -- [x] Replace the isolated beta package with one package serving V1 and V2 by name. -- [ ] Confirm npm ownership before publishing any prerelease. -- [x] Enable the isolated pinned-host E2E loading gate. -- [x] Install the packed package by name across V1 1.3.3, V1 1.3.4, latest V1, and latest V2. From 166821aedd62a0139472f8ce5291707c61c28870 Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:38:01 -0400 Subject: [PATCH 32/32] chore: drop leftover tsconfig baseUrl It only served the removed `@opencode-ai/plugin/v2` path alias. --- tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 343f02c..1666494 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,6 @@ "module": "ESNext", "lib": ["ES2020"], "moduleResolution": "bundler", - "baseUrl": ".", "declaration": true, "declarationMap": true, "sourceMap": true,