Răsfoiți Sursa

Merge pull request #1156 from alvinunreal/v3

[BETA] v3 beta release: promote v3 to beta
Alvin 5 zile în urmă
părinte
comite
57d61febe2
100 a modificat fișierele cu 5271 adăugiri și 1497 ștergeri
  1. 63 0
      .all-contributorsrc
  2. 53 8
      .github/workflows/ci.yml
  3. 1 3
      .github/workflows/contributors.yml
  4. 49 0
      .github/workflows/opencode-compatibility.yml
  5. 17 4
      .github/workflows/package-smoke.yml
  6. 1 1
      .gitignore
  7. 5 5
      .slim/clonedeps.json
  8. 3 2
      AGENTS.md
  9. 1 0
      CONTEXT.md
  10. 8 8
      README.ja-JP.md
  11. 8 8
      README.ko-KR.md
  12. 103 22
      README.md
  13. 8 8
      README.zh-CN.md
  14. 81 357
      bun.lock
  15. 1 0
      bunfig.toml
  16. 5 2
      codemap.md
  17. 1 1
      companion/Cargo.lock
  18. 1 1
      companion/Cargo.toml
  19. 32 0
      companion/VIDEOS/README.md
  20. BIN
      companion/VIDEOS/council.mp4
  21. BIN
      companion/VIDEOS/designer.mp4
  22. BIN
      companion/VIDEOS/explorer.mp4
  23. BIN
      companion/VIDEOS/fixer.mp4
  24. BIN
      companion/VIDEOS/idle.mp4
  25. BIN
      companion/VIDEOS/librarian.mp4
  26. BIN
      companion/VIDEOS/observer.mp4
  27. BIN
      companion/VIDEOS/oracle.mp4
  28. BIN
      companion/VIDEOS/orchestrator.mp4
  29. BIN
      companion/VIDEOS/question.mp4
  30. BIN
      companion/VIDEOS/unknown.mp4
  31. BIN
      companion/animations/council.jpg
  32. BIN
      companion/animations/designer.jpg
  33. BIN
      companion/animations/explorer.jpg
  34. BIN
      companion/animations/fixer.jpg
  35. BIN
      companion/animations/intro.jpg
  36. BIN
      companion/animations/librarian.jpg
  37. BIN
      companion/animations/observer.jpg
  38. BIN
      companion/animations/oracle.jpg
  39. BIN
      companion/animations/orchestrator.jpg
  40. BIN
      companion/animations/question.jpg
  41. BIN
      companion/animations/unknown.jpg
  42. 29 9
      companion/src/gifs.rs
  43. 84 0
      docs/adr/002-v3-agent-resolution-foundation.md
  44. 56 5
      docs/background-orchestration.md
  45. 3 1
      docs/companion.md
  46. 146 4
      docs/configuration.md
  47. 79 0
      docs/marketplace.md
  48. 412 34
      docs/opencode-v2-compatibility.md
  49. 2 2
      docs/preset-switching.md
  50. 1 0
      docs/quick-reference.md
  51. 7 0
      docs/release.md
  52. 26 0
      docs/tools.md
  53. BIN
      img/council.jpg
  54. BIN
      img/council.png
  55. BIN
      img/designer.jpg
  56. BIN
      img/designer.png
  57. BIN
      img/explorer.jpg
  58. BIN
      img/explorer.png
  59. BIN
      img/fixer.jpg
  60. BIN
      img/fixer.png
  61. BIN
      img/librarian.jpg
  62. BIN
      img/librarian.png
  63. BIN
      img/observer.jpg
  64. BIN
      img/oracle.jpg
  65. BIN
      img/oracle.png
  66. BIN
      img/orchestrator.jpg
  67. BIN
      img/orchestrator.png
  68. BIN
      img/v2.jpg
  69. BIN
      img/v2.webp
  70. 538 366
      oh-my-opencode-slim.schema.json
  71. 19 12
      package.json
  72. 8 4
      scripts/codemap.md
  73. 5 2
      scripts/verify-opencode-host-smoke.ts
  74. 92 9
      scripts/verify-release-artifact.ts
  75. 102 0
      src/admission-runtime.test.ts
  76. 139 0
      src/admission-runtime.ts
  77. 10 10
      src/agents/codemap.md
  78. 8 9
      src/agents/council.ts
  79. 52 20
      src/agents/custom.test.ts
  80. 7 82
      src/agents/designer.ts
  81. 3 0
      src/agents/display-name.test.ts
  82. 7 50
      src/agents/explorer.ts
  83. 7 57
      src/agents/fixer.ts
  84. 702 23
      src/agents/index.test.ts
  85. 785 79
      src/agents/index.ts
  86. 7 41
      src/agents/librarian.ts
  87. 7 39
      src/agents/observer.ts
  88. 7 44
      src/agents/oracle.ts
  89. 24 0
      src/agents/orchestrator.test.ts
  90. 104 83
      src/agents/orchestrator.ts
  91. 382 0
      src/agents/registry.test.ts
  92. 10 0
      src/agents/resolve-prompt-warn.test.ts
  93. 424 0
      src/agents/role-definitions.ts
  94. 4 0
      src/cache-safety-tripwire.test.ts
  95. 61 13
      src/cli/config-io.test.ts
  96. 134 60
      src/cli/config-io.ts
  97. 11 9
      src/cli/doctor.test.ts
  98. 16 0
      src/cli/index.ts
  99. 63 0
      src/cli/marketplace.test.ts
  100. 247 0
      src/cli/marketplace.ts

+ 63 - 0
.all-contributorsrc

@@ -947,6 +947,69 @@
       "contributions": [
         "code"
       ]
+    },
+    {
+      "login": "Alfiegerner",
+      "name": "Alfiegerner",
+      "avatar_url": "https://avatars.githubusercontent.com/u/596188?v=4",
+      "profile": "https://github.com/Alfiegerner",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "leducmaxime",
+      "name": "Maxime Leduc",
+      "avatar_url": "https://avatars.githubusercontent.com/u/37900355?v=4",
+      "profile": "https://github.com/leducmaxime",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "vmvarela",
+      "name": "Victor M. Varela",
+      "avatar_url": "https://avatars.githubusercontent.com/u/11040851?v=4",
+      "profile": "https://github.com/vmvarela",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "gitslim",
+      "name": "gitslim",
+      "avatar_url": "https://avatars.githubusercontent.com/u/24612825?v=4",
+      "profile": "https://github.com/gitslim",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "xiaolf0813",
+      "name": "xiaolf0813",
+      "avatar_url": "https://avatars.githubusercontent.com/u/314566924?v=4",
+      "profile": "https://github.com/xiaolf0813",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "BaconDroid",
+      "name": "BaconDroid",
+      "avatar_url": "https://avatars.githubusercontent.com/u/11067111?v=4",
+      "profile": "https://github.com/BaconDroid",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "bferanmi806-sketch",
+      "name": "Enoch",
+      "avatar_url": "https://avatars.githubusercontent.com/u/277080233?v=4",
+      "profile": "https://sprightly-strudel-e1f939.netlify.app/",
+      "contributions": [
+        "code"
+      ]
     }
   ],
   "commitConvention": "angular"

+ 53 - 8
.github/workflows/ci.yml

@@ -20,11 +20,9 @@ concurrency:
 
 jobs:
   test:
+    name: Required / Bun from package.json
     runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        bun-version: [latest]
+    timeout-minutes: 10
 
     steps:
       - name: Checkout code
@@ -32,14 +30,61 @@ jobs:
 
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
+
+      - name: Install dependencies
+        run: bun ci
+
+      - name: Run formatting and lint checks
+        run: bun run check:ci
+
+      - name: Run typecheck
+        run: bun run typecheck
+
+      - name: Run tests
+        run: |
+          mkdir -p reports
+          bun test \
+            --coverage \
+            --coverage-reporter=text \
+            --coverage-reporter=lcov \
+            --coverage-dir=coverage \
+            --reporter=junit \
+            --reporter-outfile=reports/junit.xml
+
+      - name: Upload test and coverage reports
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v4
+        with:
+          name: test-results-bun-pinned
+          path: |
+            reports/junit.xml
+            coverage/lcov.info
+          if-no-files-found: ignore
+          retention-days: 14
+
+      - name: Build
+        run: bun run build
+
+  bun-latest-canary:
+    name: Canary / Bun latest
+    continue-on-error: true
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup latest Bun
+        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
         with:
-          bun-version: ${{ matrix.bun-version }}
+          bun-version: latest
 
       - name: Install dependencies
-        run: bun install --frozen-lockfile
+        run: bun ci
 
-      - name: Run lint
-        run: bun run lint
+      - name: Run formatting and lint checks
+        run: bun run check:ci
 
       - name: Run typecheck
         run: bun run typecheck

+ 1 - 3
.github/workflows/contributors.yml

@@ -37,11 +37,9 @@ jobs:
 
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
-        with:
-          bun-version: latest
 
       - name: Install dependencies
-        run: bun install --frozen-lockfile
+        run: bun ci
 
       - name: Add contributor
         env:

+ 49 - 0
.github/workflows/opencode-compatibility.yml

@@ -0,0 +1,49 @@
+name: OpenCode Compatibility
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: '17 6 * * 1'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  host-smoke:
+    name: Host ${{ matrix.channel }} / OpenCode ${{ matrix.opencode-version }}
+    continue-on-error: ${{ matrix.experimental }}
+    runs-on: ubuntu-latest
+    timeout-minutes: 15
+
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - channel: supported
+            opencode-version: '1.18.13'
+            experimental: false
+          - channel: latest-canary
+            opencode-version: latest
+            experimental: true
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup Bun
+        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+
+      - name: Install dependencies
+        run: bun ci
+
+      - name: Build
+        run: bun run build
+
+      - name: Verify OpenCode host compatibility
+        env:
+          OPENCODE_SMOKE_VERSION: ${{ matrix.opencode-version }}
+        run: bun run verify:host-smoke

+ 17 - 4
.github/workflows/package-smoke.yml

@@ -2,6 +2,21 @@ name: Package Smoke
 
 on:
   workflow_dispatch:
+  pull_request:
+    branches: [main, master]
+    paths:
+      - '.github/workflows/package-smoke.yml'
+      - 'package.json'
+      - 'bun.lock'
+      - 'bunfig.toml'
+      - 'tsconfig.json'
+      - 'oh-my-opencode-slim.schema.json'
+      - 'LICENSE'
+      - 'README*.md'
+      - 'scripts/generate-schema.ts'
+      - 'scripts/verify-opencode-host-smoke.ts'
+      - 'scripts/verify-release-artifact.ts'
+      - 'src/**'
   push:
     tags: ['v*']
 
@@ -12,11 +27,11 @@ concurrency:
 jobs:
   package-smoke:
     runs-on: ${{ matrix.os }}
+    timeout-minutes: 15
 
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        bun-version: [latest]
 
     steps:
       - name: Checkout code
@@ -24,11 +39,9 @@ jobs:
 
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
-        with:
-          bun-version: ${{ matrix.bun-version }}
 
       - name: Install dependencies
-        run: bun install --frozen-lockfile
+        run: bun ci
 
       - name: Build
         run: bun run build

+ 1 - 1
.gitignore

@@ -32,6 +32,7 @@ yarn-error.log*
 
 # Test coverage
 coverage/
+reports/
 
 # Temporary files
 tmp/
@@ -101,5 +102,4 @@ companion/target/
 # Deepwork session artifacts
 .slim/deepwork/
 .codegraph/
-companion/VIDEOS/
 .worktrees/

+ 5 - 5
.slim/clonedeps.json

@@ -1,6 +1,6 @@
 {
   "version": "1.0.0",
-  "updatedAt": "2026-08-25T00:00:00.000Z",
+  "updatedAt": "2026-09-09T00:00:00.000Z",
   "dependencies": [
     {
       "name": "@opencode-ai/plugin",
@@ -31,12 +31,12 @@
     },
     {
       "name": "anomalyco/opencode",
-      "resolvedVersion": "beta",
+      "resolvedVersion": "v2",
       "repoUrl": "https://github.com/anomalyco/opencode.git",
-      "ref": "beta@d6deb62379c54dc60468b80c498bd6a5899797cf",
+      "ref": "v2@dbd9b18f3d7b4ef3e346eafd91613896c9e3c864",
       "path": ".slim/clonedeps/repos/opencode",
-      "packagePath": "packages/plugin",
-      "reason": "OpenCode v2 plugin SDK and runtime source for the v2 migration."
+      "packagePath": ".",
+      "reason": "OpenCode v2 TypeScript runtime source (split into packages/core, packages/server, packages/plugin, packages/sdk, packages/client) with background subagent support."
     },
     {
       "name": "agentclientprotocol/agent-client-protocol",

+ 3 - 2
AGENTS.md

@@ -83,8 +83,9 @@ oh-my-opencode-slim/
 
 ## Key Dependencies
 
-- `@modelcontextprotocol/sdk` - MCP protocol implementation
+- `@opencode-ai/plugin` - OpenCode server and TUI plugin contracts
 - `@opencode-ai/sdk` - OpenCode AI SDK
+- `@opentui/core` / `@opentui/solid` - Optional TUI rendering stack
 - `zod` - Runtime validation
 
 ## Development Workflow
@@ -180,7 +181,7 @@ Read-only dependency source repositories are available under
 `.slim/clonedeps/repos/` for inspection. Do not edit these clones.
 
 - `.slim/clonedeps/repos/anomalyco__opencode-v1.18.13/` - `https://github.com/anomalyco/opencode.git` at `v1.18.13@a105350812f05f914c768e468559dbd6bd508d8e`; inspect `packages/plugin` and `packages/sdk/js` for OpenCode plugin and SDK internals.
-- `.slim/clonedeps/repos/opencode/` - `https://github.com/anomalyco/opencode.git` at `beta@d6deb62379c54dc60468b80c498bd6a5899797cf`; inspect `packages/plugin`, `packages/sdk`, `packages/core`, and `packages/cli` for the OpenCode v2 migration.
+- `.slim/clonedeps/repos/opencode/` - `https://github.com/anomalyco/opencode.git` at `v2@dbd9b18f3d7b4ef3e346eafd91613896c9e3c864`; inspect `packages/core` and `packages/server` for the v2 TypeScript runtime (split from the former `packages/opencode`), `packages/plugin` for the v2 plugin/hook API, `packages/sdk` (embedded host) and `packages/client` (HTTP client) for v2 SDK internals, and `packages/core/src/tool/plugin/subagent.ts` for background subagent support.
 - `.slim/clonedeps/repos/modelcontextprotocol__typescript-sdk/` - `https://github.com/modelcontextprotocol/typescript-sdk.git` at `1.30.0@2d889f2b329e46680ec9bdd565de4616c497825a`; inspect it for MCP protocol and server integration internals.
 - `.slim/clonedeps/repos/agentclientprotocol__agent-client-protocol/` - `https://github.com/agentclientprotocol/agent-client-protocol.git` at `main@541daf8fa488c6b93aad4a874ac050b3daf9b282`; inspect it for ACP protocol specification and schema details.
 

+ 1 - 0
CONTEXT.md

@@ -77,6 +77,7 @@ A glossary of the terms used in this project's domain. Definitions describe what
 - **Plugin config** — The user-facing configuration loaded from `oh-my-opencode-slim.jsonc`.
 - **Preset** — A named set of per-agent overrides. The same word also names council councillor lineups (see Flagged).
 - **Model entry** — A normalized model reference with an optional variant, used in fallback chains.
+- **Model inheritance** — An explicit agent policy selecting the current `session` model or the configured `orchestrator` model when no agent model is set.
 - **Variant** — An optional model qualifier (e.g., a preview build) used in fallback resolution.
 - **Fallback / failover** — The mechanism that switches models when a call is rate-limited or returns empty.
 - **Disabled agents** — Agents turned off via config; `observer` is disabled by default.

+ 8 - 8
README.ja-JP.md

@@ -1,6 +1,6 @@
 <div align="center">
   <a href="https://github.com/alvinunreal/oh-my-opencode-slim/stargazers">
-    <img src="img/v2.webp" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
+    <img src="img/v2.jpg" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
   </a>
   <h3>✨ oh-my-opencode-slim ✨</h3>
 
@@ -218,7 +218,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/orchestrator.png" width="240" style="border-radius: 10px;">
+      <img src="img/orchestrator.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>複雑性の虚無から鍛え上げられし者。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -259,7 +259,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/explorer.png" width="240" style="border-radius: 10px;">
+      <img src="img/explorer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>知識を運ぶ風。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -300,7 +300,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/oracle.png" width="240" style="border-radius: 10px;">
+      <img src="img/oracle.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>分岐点に佇む声。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -344,7 +344,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/council.png" width="240" style="border-radius: 10px;">
+      <img src="img/council.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>多くの知性、ひとつの結論。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -390,7 +390,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/librarian.png" width="240" style="border-radius: 10px;">
+      <img src="img/librarian.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>理解を編み上げる者。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -431,7 +431,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/designer.png" width="240" style="border-radius: 10px;">
+      <img src="img/designer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>美は不可欠なもの。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -472,7 +472,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/fixer.png" width="240" style="border-radius: 10px;">
+      <img src="img/fixer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>構想と現実を結ぶ最後の一歩。</i></sub>
     </td>
     <td width="70%" valign="top">

+ 8 - 8
README.ko-KR.md

@@ -1,6 +1,6 @@
 <div align="center">
   <a href="https://github.com/alvinunreal/oh-my-opencode-slim/stargazers">
-    <img src="img/v2.webp" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
+    <img src="img/v2.jpg" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
   </a>
   <h3>✨ oh-my-opencode-slim ✨</h3>
 
@@ -235,7 +235,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/orchestrator.png" width="240" style="border-radius: 10px;">
+      <img src="img/orchestrator.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>복잡성의 공허 속에서 단련되다.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -276,7 +276,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/explorer.png" width="240" style="border-radius: 10px;">
+      <img src="img/explorer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>지식을 나르는 바람.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -317,7 +317,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/oracle.png" width="240" style="border-radius: 10px;">
+      <img src="img/oracle.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>갈림길에서 들리는 목소리.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -361,7 +361,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/council.png" width="240" style="border-radius: 10px;">
+      <img src="img/council.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>여러 정신, 하나의 결론.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -407,7 +407,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/librarian.png" width="240" style="border-radius: 10px;">
+      <img src="img/librarian.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>이해를 엮는 자.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -448,7 +448,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/designer.png" width="240" style="border-radius: 10px;">
+      <img src="img/designer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>아름다움은 필수적이다.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -489,7 +489,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/fixer.png" width="240" style="border-radius: 10px;">
+      <img src="img/fixer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>비전과 현실 사이의 마지막 단계.</i></sub>
     </td>
     <td width="70%" valign="top">

+ 103 - 22
README.md

@@ -1,6 +1,6 @@
 <div align="center">
   <a href="https://github.com/alvinunreal/oh-my-opencode-slim/stargazers">
-    <img src="img/v2.webp" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
+    <img src="img/v2.jpg" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
   </a>
   <h3>✨ oh-my-opencode-slim ✨</h3>
 
@@ -48,6 +48,9 @@ The main idea is simple: instead of forcing one model to do everything, the plug
   models at runtime with `/preset`.
 - **[Code intelligence tools](docs/tools.md)** - LSP tools, AST-aware search
   across 25 languages, and built-in MCPs for docs and GitHub code
+- **[Local marketplace](docs/marketplace.md)** - install and activate local
+  offline agent/profile packages from the CLI or the in-session
+  `marketplace` tool; changes apply after reload
   search.
 - **[Fully customizable](docs/configuration.md)** - custom agents, prompt
   overrides, per-agent skill/MCP permissions, and
@@ -108,6 +111,44 @@ have Bun installed:
 npx oh-my-opencode-slim@latest install
 ```
 
+### Local Marketplace Packages
+
+The marketplace lifecycle is local and offline. Package manifests are
+data-only, exact-version locked, and stored under the XDG data directory.
+Installation and preset activation are separate: install a package, then enable
+it in the active preset. Activated agents and profiles apply after the next
+OpenCode session/reload; the live registry is never hot-swapped.
+Manifests include bounded author, tag, license, plugin/role-contract,
+routing/delegation, role-target, capability, and override metadata. `instructions`
+is the single package instruction surface; profile packages declare explicit
+append/replace composition semantics. Executable fields,
+package-to-package dependencies, and arbitrary file maps are rejected.
+
+```bash
+bunx oh-my-opencode-slim marketplace install ./package.json
+bunx oh-my-opencode-slim marketplace list
+bunx oh-my-opencode-slim marketplace verify [author/name]
+bunx oh-my-opencode-slim marketplace update ./package-v2.json
+bunx oh-my-opencode-slim marketplace enable author/name
+bunx oh-my-opencode-slim marketplace profile librarian author/profile
+bunx oh-my-opencode-slim marketplace disable author/name
+bunx oh-my-opencode-slim marketplace remove author/name
+bunx oh-my-opencode-slim marketplace status
+```
+
+Use `update` explicitly to select a different exact version. `enable` activates
+an installed `agent` package in the active preset as a separately named
+role-derived agent. `profile` selects at most one installed `profile` package
+per supported specialist role; `--clear` writes a tombstone. Required skills
+and MCPs are preflighted against built-in capabilities and on-disk host
+configuration; missing required dependencies disable that package for the
+session. Optional requirements stay unavailable and are never auto-installed.
+`import` is an alias for `install` and records the canonical absolute local
+source path in the lockfile. Startup reads only the local store and never
+contacts a registry. The orchestrator can perform the same local lifecycle
+with the in-session `marketplace` tool; see
+[Local Marketplace](docs/marketplace.md).
+
 ### Run from Master
 
 Use this if you want the latest code, easier bug fixes, or a local setup for
@@ -132,6 +173,27 @@ bun install
 bun run build
 ```
 
+### OpenCode v2 (`opencode2`)
+
+The same package runs on both OpenCode v1 and v2. On v2 you get the full
+agent pantheon, delegation through the host `subagent` tool (bridged into the
+background job board), all built-in tools and slash commands, auto-registered
+MCPs, `/preset` in the TUI, webfetch secondary-model summaries, rate-limit
+model fallback, and the orchestrator-wake scheduler in children-driven
+degraded mode (a periodic watchdog over stuck background children and
+unreconciled jobs). Multiplexer panes stay v1-only by design (v2 renders and
+notifies subagents natively).
+
+v2 auto-refreshes unpinned plugins on startup, so pin an exact version while
+both v2 and this adapter evolve quickly:
+
+```json
+{ "plugin": ["oh-my-opencode-slim@2.2.17"] }
+```
+
+Details, the feature matrix, and per-feature minimum v2 builds:
+[OpenCode v2 Compatibility](docs/opencode-v2-compatibility.md).
+
 ### Getting Started
 
 The installer generates both OpenAI and OpenCode Go presets, with OpenAI active by default.
@@ -165,6 +227,10 @@ Then:
 > Because background agents are now the default workflow, it is **highly recommended** to enable and configure **[Multiplexer Integration](docs/multiplexer-integration.md)**. It automatically opens each agent in a dedicated Tmux, Zellij, Herdr, cmux, or kitty pane, so you can watch specialists work live while the Orchestrator continues coordinating the session.
 
 The default generated configuration includes both `openai` and `opencode-go` presets.
+Preset agent overrides are stored under each preset's explicit `agents` field;
+`marketplace` activation selects installed packages for that preset. Agent, model,
+skill, MCP, permission, and routing decisions are resolved once at startup and
+apply after the next reload rather than hot-swapping a live session.
 
 ```jsonc
 {
@@ -172,21 +238,25 @@ The default generated configuration includes both `openai` and `opencode-go` pre
   "preset": "openai",
   "presets": {
     "openai": {
-      "orchestrator": { "model": "openai/gpt-5.6-terra", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
-      "oracle": { "model": "openai/gpt-5.6-sol", "variant": "high", "skills": ["simplify"], "mcps": [] },
-      "librarian": { "model": "openai/gpt-5.6-luna", "variant": "low", "skills": [], "mcps": ["context7", "gh_grep"] },
-      "explorer": { "model": "openai/gpt-5.6-luna", "variant": "low", "skills": [], "mcps": [] },
-      "designer": { "model": "openai/gpt-5.6-luna", "variant": "medium", "skills": [], "mcps": [] },
-      "fixer": { "model": "openai/gpt-5.6-luna", "variant": "high", "skills": [], "mcps": [] }
+      "agents": {
+        "orchestrator": { "model": "openai/gpt-5.6-terra", "variant": "high", "skills": ["*"], "mcps": ["*", "!context7"] },
+        "oracle": { "model": "openai/gpt-5.6-sol", "variant": "high", "skills": ["simplify"], "mcps": [] },
+        "librarian": { "model": "openai/gpt-5.6-luna", "variant": "low", "skills": [], "mcps": ["context7", "gh_grep"] },
+        "explorer": { "model": "openai/gpt-5.6-luna", "variant": "low", "skills": [], "mcps": [] },
+        "designer": { "model": "openai/gpt-5.6-luna", "variant": "medium", "skills": [], "mcps": [] },
+        "fixer": { "model": "openai/gpt-5.6-luna", "variant": "high", "skills": [], "mcps": [] }
+      }
     },
     "opencode-go": {
-      "orchestrator": { "model": "opencode-go/minimax-m3", "variant": "thinking" },
-      "oracle": { "model": "opencode-go/qwen3.7-max", "variant": "max" },
-      "librarian": { "model": "opencode-go/deepseek-v4-flash", "variant": "high" },
-      "explorer": { "model": "opencode-go/deepseek-v4-flash", "variant": "high" },
-      "designer": { "model": "opencode-go/kimi-k2.7-code" },
-      "fixer": { "model": "opencode-go/deepseek-v4-flash", "variant": "high" },
-      "observer": { "model": "opencode-go/mimo-v2.5" }
+      "agents": {
+        "orchestrator": { "model": "opencode-go/minimax-m3", "variant": "thinking" },
+        "oracle": { "model": "opencode-go/qwen3.7-max", "variant": "max" },
+        "librarian": { "model": "opencode-go/deepseek-v4-flash", "variant": "high" },
+        "explorer": { "model": "opencode-go/deepseek-v4-flash", "variant": "high" },
+        "designer": { "model": "opencode-go/kimi-k2.7-code" },
+        "fixer": { "model": "opencode-go/deepseek-v4-flash", "variant": "high" },
+        "observer": { "model": "opencode-go/mimo-v2.5" }
+      }
     }
   }
 }
@@ -236,7 +306,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/orchestrator.png" width="240" style="border-radius: 10px;">
+      <img src="img/orchestrator.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>Forged in the void of complexity.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -277,7 +347,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/explorer.png" width="240" style="border-radius: 10px;">
+      <img src="img/explorer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>The wind that carries knowledge.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -318,7 +388,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/oracle.png" width="240" style="border-radius: 10px;">
+      <img src="img/oracle.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>The voice at the crossroads.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -362,7 +432,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/council.png" width="240" style="border-radius: 10px;">
+      <img src="img/council.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>Many minds, one verdict.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -408,7 +478,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/librarian.png" width="240" style="border-radius: 10px;">
+      <img src="img/librarian.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>The weaver of understanding.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -449,7 +519,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/designer.png" width="240" style="border-radius: 10px;">
+      <img src="img/designer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>Beauty is essential.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -490,7 +560,7 @@ If any agent fails to respond, check your provider authentication and config fil
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/fixer.png" width="240" style="border-radius: 10px;">
+      <img src="img/fixer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>The final step between vision and reality.</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -671,6 +741,7 @@ Use this section as a map: start with installation, then jump to features, confi
 | Doc | What it covers |
 |-----|----------------|
 | **[Installation Guide](docs/installation.md)** | Install the plugin, use CLI flags, reset config, and troubleshoot setup |
+| **[OpenCode v2 Compatibility](docs/opencode-v2-compatibility.md)** | Run the same plugin on `opencode2`: feature matrix, minimum v2 builds, version pinning |
 | **[Configuration](docs/configuration.md)** | Config file locations, JSONC support, prompt overrides, and full option reference |
 | **[Project Customization](docs/project-local-customization.md)** | Repository-specific custom agents, prompt overrides, per-agent skills, and precedence |
 | **[Background Orchestration](docs/background-orchestration.md)** | Scheduler-first orchestrator model built around native background subagents |
@@ -679,6 +750,7 @@ Use this section as a map: start with installation, then jump to features, confi
 | **[Skills](docs/skills.md)** | Bundled skills such as `simplify`, `codemap`, `clonedeps`, `deepwork`, `verification-planning`, `reflect`, `worktrees`, and `oh-my-opencode-slim` |
 | **[MCPs](docs/mcps.md)** | `context7`, `gh_grep`, and how MCP permissions work per agent |
 | **[Tools](docs/tools.md)** | Built-in tool capabilities like `webfetch`, LSP tools, code search, and formatters |
+| **[Marketplace](docs/marketplace.md)** | Local offline package install, activation, status, and the in-session `marketplace` tool |
 
 ---
 
@@ -689,7 +761,7 @@ Use this section as a map: start with installation, then jump to features, confi
   <p><sub>Every merged contribution leaves a mark on the realm.</sub></p>
 
   <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
-[![All Contributors](https://img.shields.io/badge/all_contributors-104-orange.svg?style=flat-square)](#contributors-)
+[![All Contributors](https://img.shields.io/badge/all_contributors-111-orange.svg?style=flat-square)](#contributors-)
 <!-- ALL-CONTRIBUTORS-BADGE:END -->
 </div>
 
@@ -839,6 +911,15 @@ Use this section as a map: start with installation, then jump to features, confi
     <tr>
       <td align="center" valign="top" width="16.66%"><a href="https://github.com/zjm54321"><img src="https://avatars.githubusercontent.com/u/20168947?v=4?s=100" width="100px;" alt="落花有意"/><br /><sub><b>落花有意</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=zjm54321" title="Code">💻</a></td>
       <td align="center" valign="top" width="16.66%"><a href="https://github.com/JoJohanse"><img src="https://avatars.githubusercontent.com/u/97782983?v=4?s=100" width="100px;" alt="JoJohanse"/><br /><sub><b>JoJohanse</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=JoJohanse" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://github.com/Alfiegerner"><img src="https://avatars.githubusercontent.com/u/596188?v=4?s=100" width="100px;" alt="Alfiegerner"/><br /><sub><b>Alfiegerner</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=Alfiegerner" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://github.com/leducmaxime"><img src="https://avatars.githubusercontent.com/u/37900355?v=4?s=100" width="100px;" alt="Maxime Leduc"/><br /><sub><b>Maxime Leduc</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=leducmaxime" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://github.com/vmvarela"><img src="https://avatars.githubusercontent.com/u/11040851?v=4?s=100" width="100px;" alt="Victor M. Varela"/><br /><sub><b>Victor M. Varela</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=vmvarela" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://github.com/gitslim"><img src="https://avatars.githubusercontent.com/u/24612825?v=4?s=100" width="100px;" alt="gitslim"/><br /><sub><b>gitslim</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=gitslim" title="Code">💻</a></td>
+    </tr>
+    <tr>
+      <td align="center" valign="top" width="16.66%"><a href="https://github.com/xiaolf0813"><img src="https://avatars.githubusercontent.com/u/314566924?v=4?s=100" width="100px;" alt="xiaolf0813"/><br /><sub><b>xiaolf0813</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=xiaolf0813" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://github.com/BaconDroid"><img src="https://avatars.githubusercontent.com/u/11067111?v=4?s=100" width="100px;" alt="BaconDroid"/><br /><sub><b>BaconDroid</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=BaconDroid" title="Code">💻</a></td>
+      <td align="center" valign="top" width="16.66%"><a href="https://sprightly-strudel-e1f939.netlify.app/"><img src="https://avatars.githubusercontent.com/u/277080233?v=4?s=100" width="100px;" alt="Enoch"/><br /><sub><b>Enoch</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=bferanmi806-sketch" title="Code">💻</a></td>
     </tr>
   </tbody>
 </table>

+ 8 - 8
README.zh-CN.md

@@ -1,6 +1,6 @@
 <div align="center">
   <a href="https://github.com/alvinunreal/oh-my-opencode-slim/stargazers">
-    <img src="img/v2.webp" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
+    <img src="img/v2.jpg" alt="oh-my-opencode-slim V2 Release" style="border-radius: 10px;">
   </a>
   <h3>✨ oh-my-opencode-slim ✨</h3>
 
@@ -210,7 +210,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/orchestrator.png" width="240" style="border-radius: 10px;">
+      <img src="img/orchestrator.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>在复杂性的深渊中锻造而成。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -251,7 +251,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/explorer.png" width="240" style="border-radius: 10px;">
+      <img src="img/explorer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>传播知识的清风。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -292,7 +292,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/oracle.png" width="240" style="border-radius: 10px;">
+      <img src="img/oracle.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>十字路口的声音。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -336,7 +336,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/council.png" width="240" style="border-radius: 10px;">
+      <img src="img/council.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>集思广益,终成一断。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -382,7 +382,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/librarian.png" width="240" style="border-radius: 10px;">
+      <img src="img/librarian.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>理解的编织者。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -423,7 +423,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/designer.png" width="240" style="border-radius: 10px;">
+      <img src="img/designer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>美是不可或缺的。</i></sub>
     </td>
     <td width="70%" valign="top">
@@ -464,7 +464,7 @@ ping all agents
 <table>
   <tr>
     <td width="30%" align="center" valign="top">
-      <img src="img/fixer.png" width="240" style="border-radius: 10px;">
+      <img src="img/fixer.jpg" width="240" style="border-radius: 10px;">
       <br><sub><i>愿景与现实之间的最后一步。</i></sub>
     </td>
     <td width="70%" valign="top">

+ 81 - 357
bun.lock

@@ -6,18 +6,20 @@
       "name": "oh-my-opencode-slim",
       "dependencies": {
         "@ast-grep/cli": "^0.44.1",
-        "@modelcontextprotocol/sdk": "^1.30.0",
         "@mozilla/readability": "^0.6.0",
-        "@opencode-ai/plugin": "1.18.13",
-        "@opencode-ai/sdk": "1.18.13",
+        "@opencode-ai/plugin": "1.18.23",
+        "@opencode-ai/sdk": "1.18.23",
+        "gray-matter": "4.0.3",
         "jsdom": "^29.0.0",
         "lru-cache": "^11.5.2",
+        "semver": "^7.7.4",
         "turndown": "^7.2.4",
       },
       "devDependencies": {
         "@biomejs/biome": "2.5.4",
         "@types/jsdom": "28",
         "@types/node": "^26.1.1",
+        "@types/semver": "^7.8.0",
         "@types/turndown": "^5.0.6",
         "all-contributors-cli": "^6.26.1",
         "bun-types": "^1.3.14",
@@ -25,7 +27,9 @@
         "zod": "^4.4.3",
       },
       "optionalDependencies": {
-        "@opentui/solid": "^0.1.97",
+        "@opentui/core": "0.5.8",
+        "@opentui/solid": "0.5.8",
+        "solid-js": "1.9.12",
       },
       "peerDependencies": {
         "zod": "^4.0.0",
@@ -154,68 +158,8 @@
 
     "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
 
-    "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],
-
     "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="],
 
-    "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="],
-
-    "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
-
-    "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
-
-    "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="],
-
-    "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="],
-
-    "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="],
-
-    "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="],
-
-    "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="],
-
-    "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="],
-
-    "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="],
-
-    "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="],
-
-    "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="],
-
-    "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="],
-
-    "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="],
-
-    "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="],
-
-    "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="],
-
-    "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="],
-
-    "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="],
-
-    "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="],
-
-    "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="],
-
-    "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="],
-
-    "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="],
-
-    "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="],
-
-    "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="],
-
-    "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="],
-
-    "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="],
-
-    "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="],
-
-    "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="],
-
-    "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
-
     "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
 
     "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
@@ -226,8 +170,6 @@
 
     "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
 
-    "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.30.0", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA=="],
-
     "@mozilla/readability": ["@mozilla/readability@0.6.0", "", {}, "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ=="],
 
     "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
@@ -242,34 +184,38 @@
 
     "@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/plugin": ["@opencode-ai/plugin@1.18.13", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.13", "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-2H9YT80M1PYElpG+lmd/9kGqsNouiJIBCUhLblmgFwoSrB4wyahgkCS6NcFQR/AYXNH4I1Yd3lmQcVaEPu1qNg=="],
+    "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.23", "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-a+LbJe+wyyiwOQ7DeYOY4MkI/6VL45wkgYunVNZGC93UQgaj6sMGemS45CsLXrjyF4bvJMtqGd66GJvgjzbXAg=="],
 
-    "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.13", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-JY9etiVcu1G/pZjaH2vjK/b8z54ujxaWCD1GziO4ADUhRM6m6zm2332bPGcxEfA6TwweiJfNlK6wVZQ0f/X4KQ=="],
+    "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.23", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-VouYbL8O2ynLq0atr5fjzCv8YLtFi/zKLQ/uYkCvTJ4CmUdA01XwPn8om+u3TvxnGEfQsBfmThGU141vt3sg5w=="],
 
-    "@opentui/core": ["@opentui/core@0.1.97", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.97", "@opentui/core-darwin-x64": "0.1.97", "@opentui/core-linux-arm64": "0.1.97", "@opentui/core-linux-x64": "0.1.97", "@opentui/core-win32-arm64": "0.1.97", "@opentui/core-win32-x64": "0.1.97", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-2ENH0Dc4NUAeHeeQCQhF1lg68RuyntOUP68UvortvDqTz/hqLG0tIwF+DboCKtWi8Nmao4SAQEJ7lfmyQNEDOQ=="],
+    "@opentui/core": ["@opentui/core@0.5.8", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.8", "@opentui/core-darwin-x64": "0.5.8", "@opentui/core-linux-arm64": "0.5.8", "@opentui/core-linux-arm64-musl": "0.5.8", "@opentui/core-linux-x64": "0.5.8", "@opentui/core-linux-x64-musl": "0.5.8", "@opentui/core-win32-arm64": "0.5.8", "@opentui/core-win32-x64": "0.5.8" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-GbZ+nSLYZqxj2Z5TU19Mx6IsAvVsn2+7WEXz+6OlMGoootvt3TxP3vWwfGI0jWk9qp1ftRlML1JPNEkzy+9I8g=="],
 
-    "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.97", "", { "os": "darwin", "cpu": "arm64" }, "sha512-t7oMGEfMPQsqLEx7/rPqv/UGJ+vqhe4RWHRRQRYcuHuLKssZ2S8P9mSS7MBPtDqGcxg4PosCrh5nHYeZ94EXUw=="],
+    "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c9Y1FBrSnA4sKUCMETsrLYOmsMTyJae8mU9cE6M4o9rXcr3ZLPA7o9AkPA3S0+kH+kZ0o1Fn0wLejgNxvEp5mg=="],
 
-    "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.97", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZuPWAawlVat6ZHb8vaH/CVUeGwI0pI4vd+6zz1ZocZn95ZWJztfyhzNZOJrq1WjHmUROieJ7cOuYUZfvYNuLrg=="],
+    "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-oZ/6Iz1KN+4volMFKmmvziYJhMgyyJ99LfK1S+uPRyIRqzT3CESoJ58D4h04M1g0dHeHE4PvkU1p3uQKrXiP3g=="],
 
-    "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.97", "", { "os": "linux", "cpu": "arm64" }, "sha512-QXxhz654vXgEu2wrFFFFnrSWbyk6/r6nXNnDTcMRWofdMZQLx87NhbcsErNmz9KmFdzoPiQSmlpYubLflKKzqQ=="],
+    "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-N6i/ocrsTjIq9aUQyrfJkqUo+tc4P5ZS6xz38Cm1MhtDzwBE+CrNIJdHfoJfmRLhJkYHlw6Z0ToICn8ZNj4Bpw=="],
 
-    "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.97", "", { "os": "linux", "cpu": "x64" }, "sha512-v3z0QWpRS3p8blE/A7pTu15hcFMtSndeiYhRxhrjp6zAhQ+UlruQs9DAG1ifSuVO1RJJ0pUKklFivdbu0pMzuw=="],
+    "@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-eFMB41AWODaYf8PsCx3vtTMX33tFgtSpR9tKNnUErCXlcnZ+WCRHVaJ9F6DrVNX3ir6HmgbwAmaWXvaVF+kLtg=="],
 
-    "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.97", "", { "os": "win32", "cpu": "arm64" }, "sha512-o/m9mD1dvOCwkxOUUyoEILl+d6tzh/85foJc4uqjXYi71NNcwg8u+Eq3/gdHuSKnlT1pusCPKoS1IDuBvZE24A=="],
+    "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.8", "", { "os": "linux", "cpu": "x64" }, "sha512-/2QM7/wMnML/sxchzbwgoU5tUu/7k836/kSOKMti8opjuecv1K+WWNKGXufhTNeRcXFZaba5rsCdYrf3VqnVsQ=="],
 
-    "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.97", "", { "os": "win32", "cpu": "x64" }, "sha512-Rwp7JOwrYm4wtzPHY2vv+2l91LXmKSI7CtbmWN1sSUGhBPtPGSvfwux3W5xaAZQa2KPEXicPjaKJZc+pob3YRg=="],
+    "@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.8", "", { "os": "linux", "cpu": "x64" }, "sha512-YXo+qUHYmep2uvv3ECvTeqr10aD7+lBsavYmsTLBzS5hHabbzlQ10oX/99nIRPC3Au1BMWD6d6zQP5czPx23eg=="],
 
-    "@opentui/solid": ["@opentui/solid@0.1.97", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.97", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-ma/uihG38F+6oLJVD8yR7z82FWmR8QhfesNV5SBXbN74riMCRyy6kyQ6SI4xs4ykt9BbZOjrKLq+Xt/0Pd0SJQ=="],
+    "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-7qBdhEAlh4tLFzW7nWLPlREtNiF6NZMDMi+4uDpUlAMhRavLr6wjcIcgfhNAF/puq06DjuZlPTwaMCgB3qOuwA=="],
 
-    "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
+    "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Z76YaTKnmRDSHKdKa7iTBCXBQdInQLq7UG3qIE84nvyHCDtkhYtPWOaCzDisSgXC8Y6hJt6NiFbaNjQyxPkfpQ=="],
 
-    "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
+    "@opentui/solid": ["@opentui/solid@0.5.8", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.8", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-L0NxuAU8XT+jlE5G90oA3kspqkof48b0hmzi5XLw+1gxnkxrkTb+YfKys+GzVK4UqhgwY9aW+TeDfrKbnfwCMw=="],
+
+    "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
 
     "@types/jsdom": ["@types/jsdom@28.0.3", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^8.0.0", "undici-types": "^7.21.0" } }, "sha512-/HQ2uFoetFTXuye8vzIcHw2z6Fwi7Hi/qcgC+RoS9NCyewiqxhVGqlG+ViGB6lkax481R6dmhf1I7lIGlzJStQ=="],
 
     "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
 
+    "@types/semver": ["@types/semver@7.8.0", "", {}, "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ=="],
+
     "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
 
     "@types/turndown": ["@types/turndown@5.0.6", "", {}, "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg=="],
@@ -314,74 +260,38 @@
 
     "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
 
-    "@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
-
-    "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
-
-    "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
-
-    "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
-
-    "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
-
     "all-contributors-cli": ["all-contributors-cli@6.26.1", "", { "dependencies": { "@babel/runtime": "^7.7.6", "async": "^3.1.0", "chalk": "^4.0.0", "didyoumean": "^1.2.1", "inquirer": "^7.3.3", "json-fixer": "^1.6.8", "lodash": "^4.11.2", "node-fetch": "^2.6.0", "pify": "^5.0.0", "yargs": "^15.0.1" }, "optionalDependencies": { "prettier": "^2" }, "bin": { "all-contributors": "dist/cli.js" } }, "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw=="],
 
     "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
 
-    "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+    "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=="],
 
-    "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
+    "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
 
     "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
 
-    "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
-
     "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.6", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ=="],
 
     "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="],
 
-    "babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="],
+    "babel-preset-solid": ["babel-preset-solid@1.9.12", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.12" }, "optionalPeers": ["solid-js"] }, "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg=="],
 
     "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
 
-    "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
-
     "baseline-browser-mapping": ["baseline-browser-mapping@2.10.24", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA=="],
 
     "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
 
-    "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
-
-    "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
-
     "brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
 
     "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
 
-    "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
-
-    "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
+    "bun-ffi-structs": ["bun-ffi-structs@0.3.1", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-3gM7PpVWLyrwxWjcilSiGuhWanhZivvo6l0u573NziPH6f/gwk6McbaYgn7oJWov6pKGRTDbrg94W5DcJsKTtQ=="],
 
     "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
 
-    "bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
-
-    "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mRrFFyHzPWjsTRidAZBRcu808CPQBOUL0P6b4nxLhp+XHcV/mbUHERZMgW9s58tsojQfSdzschiQa8q+JCgRWA=="],
-
-    "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-g0NXGNgvaVCSH/jCWWlfdiquOHkbUN6vP4zqzSkIxWKQeLnqm3oADcok7SO3yIgI7v5mKpRc/ks7NDEKNH+jNQ=="],
-
-    "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-UEP7UZdEhx9otvkZczjsszL8ZVlrODANQvgl+C88/bNVmxDoFi7w1fWzGi1sZyakiETjmtFDq2/xCLhbSZxjqw=="],
-
-    "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-KZktiFkBz6sN7PEm1NVdeaLP5Q5X/PlSHZqefY4nNuWtf0LNvh54NhZe7yVv/Plz/nGbv92b0KHMBY3ki/pp6g=="],
-
-    "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
-
-    "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
-
-    "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
-
     "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
 
     "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="],
@@ -400,18 +310,8 @@
 
     "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
 
-    "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
-
-    "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
-
     "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
 
-    "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
-
-    "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
-
-    "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
-
     "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
 
     "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
@@ -426,80 +326,42 @@
 
     "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
 
-    "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
-
     "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
 
     "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
 
-    "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
-
-    "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
-
-    "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
+    "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="],
 
     "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=="],
 
     "electron-to-chromium": ["electron-to-chromium@1.5.344", "", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="],
 
-    "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
-    "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+    "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
 
     "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
 
-    "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
-
     "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
 
-    "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
-
     "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
 
-    "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
-
     "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
 
-    "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
-
-    "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
-
-    "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
-
-    "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
+    "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
 
-    "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
-
-    "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
-
-    "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
-
-    "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="],
+    "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
 
     "external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
 
     "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-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
-
     "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="],
 
-    "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
-
-    "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
-
     "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
 
     "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
 
     "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
 
-    "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
-
-    "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
-
     "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
 
     "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -508,64 +370,40 @@
 
     "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
 
-    "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
-
-    "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
-
-    "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
+    "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
 
     "glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
 
-    "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+    "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
 
     "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
 
-    "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
-
     "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
 
-    "hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
-
     "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
 
     "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="],
 
-    "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
-
-    "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
-
-    "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
-
-    "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
-
-    "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+    "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
 
     "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
 
     "inquirer": ["inquirer@7.3.3", "", { "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.19", "mute-stream": "0.0.8", "run-async": "^2.4.0", "rxjs": "^6.6.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6" } }, "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA=="],
 
-    "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="],
-
-    "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
-
     "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
 
+    "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
+
     "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
 
     "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
 
-    "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
-
     "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
 
-    "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
-
-    "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
-
-    "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
-
     "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
 
+    "js-yaml": ["js-yaml@3.15.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w=="],
+
     "jsdom": ["jsdom@29.1.1", "", { "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.3", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.3.5", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q=="],
 
     "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
@@ -574,12 +412,10 @@
 
     "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
 
-    "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
-
-    "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
-
     "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
 
+    "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
+
     "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
 
     "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
@@ -590,20 +426,8 @@
 
     "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
 
-    "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
-
     "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
 
-    "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
-
-    "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
-
-    "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
-
-    "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=="],
-
     "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
 
     "minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
@@ -620,24 +444,12 @@
 
     "mute-stream": ["mute-stream@0.0.8", "", {}, "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="],
 
-    "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
-
     "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
 
     "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=="],
 
     "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
 
-    "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
-
-    "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
-
-    "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
-
-    "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
-
-    "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
-
     "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
 
     "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
@@ -648,18 +460,8 @@
 
     "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
 
-    "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
-
-    "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
-
-    "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="],
-
-    "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
-
     "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="],
 
-    "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
-
     "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
 
     "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -668,46 +470,20 @@
 
     "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-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
-
-    "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
-
     "pegjs": ["pegjs@0.10.0", "", { "bin": { "pegjs": "bin/pegjs" } }, "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow=="],
 
     "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
 
     "pify": ["pify@5.0.0", "", {}, "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="],
 
-    "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
-
-    "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
-
     "pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
 
-    "planck": ["planck@1.5.0", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-dlvqJE+FscZgrGUXJ5ybd0o5bvZ5XXyZNbm08xGsXp9WjXeAyWSFT6n9s/1PQcUBo4546fDXA5RMA4wbDyZw6g=="],
-
-    "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
-
     "prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
 
-    "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
-
-    "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
-
     "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
 
     "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="],
 
-    "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
-
-    "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
-
-    "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
-
-    "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
-
-    "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
-
     "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
 
     "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
@@ -720,67 +496,43 @@
 
     "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
 
-    "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
-
     "run-async": ["run-async@2.4.1", "", {}, "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="],
 
     "rxjs": ["rxjs@6.6.7", "", { "dependencies": { "tslib": "^1.9.0" } }, "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ=="],
 
     "s-js": ["s-js@0.4.9", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="],
 
-    "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
-
     "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
 
-    "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
-
     "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
 
-    "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+    "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
 
-    "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
+    "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
 
     "seroval": ["seroval@1.5.2", "", {}, "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q=="],
 
     "seroval-plugins": ["seroval-plugins@1.5.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg=="],
 
-    "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
-
     "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
 
-    "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
-
     "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=="],
 
-    "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
-
-    "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
-
-    "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
-
-    "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
-
     "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
 
-    "simple-xml-to-json": ["simple-xml-to-json@1.2.7", "", {}, "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q=="],
-
-    "solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="],
+    "solid-js": ["solid-js@1.9.12", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw=="],
 
     "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
 
-    "stage-js": ["stage-js@1.0.2", "", {}, "sha512-EWTRBYlg7Qv9wGUao99/PfRe3KaiQqWmgSvTOXvaWnu1Jk/q/vV8yJVu6bi/3EqDZeMVnCPAjheba6OFc5k1GQ=="],
-
-    "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
-    "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=="],
+    "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
 
-    "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+    "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
 
-    "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+    "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
 
-    "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
+    "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
 
     "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
 
@@ -788,22 +540,14 @@
 
     "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
 
-    "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
-
     "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
 
-    "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
-
     "tldts": ["tldts@7.4.9", "", { "dependencies": { "tldts-core": "^7.4.9" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA=="],
 
     "tldts-core": ["tldts-core@7.4.9", "", {}, "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg=="],
 
     "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
 
-    "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
-
-    "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
-
     "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
 
     "tough-cookie": ["tough-cookie@6.0.2", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA=="],
@@ -816,24 +560,16 @@
 
     "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
 
-    "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
-
     "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
 
     "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
 
     "undici-types": ["undici-types@7.28.0", "", {}, "sha512-LJAfY+2w6HGeT8d8J1wNQsUGUEGio6NWWpwdwurQe4f6oojzCFuGLizl1KSve4irsTxyLly1QhEeE6iapdaIvQ=="],
 
-    "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
-
     "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
 
-    "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
-
     "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
 
-    "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
-
     "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
 
     "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
@@ -850,16 +586,8 @@
 
     "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
 
-    "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
-
     "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
 
-    "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
-
-    "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
-
-    "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
-
     "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
 
     "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
@@ -872,45 +600,15 @@
 
     "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
 
-    "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
-
     "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
 
-    "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
+    "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
 
     "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
 
-    "@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-circle/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-color/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-contain/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-cover/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-crop/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-displace/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-fisheye/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+    "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
 
-    "@jimp/plugin-flip/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-mask/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-print/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-quantize/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-resize/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-rotate/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/plugin-threshold/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
-
-    "@jimp/types/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+    "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
 
     "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
 
@@ -920,9 +618,13 @@
 
     "babel-plugin-jsx-dom-expressions/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
 
-    "external-editor/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
+    "cliui/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=="],
+
+    "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+    "inquirer/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=="],
 
-    "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
+    "inquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
 
     "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
 
@@ -932,20 +634,42 @@
 
     "path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
 
-    "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
-
     "pkg-up/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
 
+    "wrap-ansi/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/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+    "yargs/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=="],
+
     "babel-plugin-jsx-dom-expressions/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
 
+    "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+    "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+    "inquirer/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+    "inquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
     "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
 
     "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
 
     "pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
 
+    "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+    "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+    "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+    "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
     "pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
 
     "pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
+
+    "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
   }
 }

+ 1 - 0
bunfig.toml

@@ -1,2 +1,3 @@
 [test]
 root = "./src"
+preload = ["@opentui/solid/preload"]

+ 5 - 2
codemap.md

@@ -43,7 +43,7 @@ This codemap covers the plugin repository itself and excludes the nested `openco
 | `src/hooks/post-file-tool-nudge/` | Post-read/write reminder path that nudges delegation-aware next steps. | [View Map](src/hooks/post-file-tool-nudge/codemap.md) |
 | `src/hooks/task-session-manager/` | Resumable `task` session tracking: job-board injection, short alias resolution, cache-safe prompt injection, idle/stop-confirmation reconciliation, live runtime-status reads, HITL wait gating, and revived-run tracking. | [View Map](src/hooks/task-session-manager/codemap.md) |
 | `src/hooks/cache-monitor/` | Observation-only runtime watchdog over provider cache telemetry (`tokens.cache.read/write`) that warns on prompt-cache busts and frozen-prefix plateaus. | [View Map](src/hooks/cache-monitor/codemap.md) |
-| `src/hooks/orchestrator-wake/` | Periodic orchestrator wake scheduler: after continuous parent idle, sends a static internal wake prompt when incomplete TODOs remain; process-global one-flight/no-progress gate. | [View Map](src/hooks/orchestrator-wake/codemap.md) |
+| `src/hooks/orchestrator-wake/` | Periodic orchestrator wake scheduler: after continuous parent idle, sends a static internal wake prompt when incomplete TODOs remain (v1) or when background children lack a terminal outcome (v2 children-driven degraded mode); process-global one-flight/no-progress gate. | [View Map](src/hooks/orchestrator-wake/codemap.md) |
 | `src/hooks/loop-command/` | `/loop` runtime command: extracts goal/successCriteria/maxAttempts and drives an iterative retry loop with a per-run history directory. | [View Map](src/hooks/loop-command/codemap.md) |
 | `src/interview/` | `/interview` feature: per-session and dashboard prompt/state orchestration, persistence, local UI, and cross-process coordination. | [View Map](src/interview/codemap.md) |
 | `src/mcp/` | Built-in MCP registry and per-provider MCP definitions. | [View Map](src/mcp/codemap.md) |
@@ -104,7 +104,10 @@ This codemap covers the plugin repository itself and excludes the nested `openco
 - `src/tools/preset-switch.ts` + `src/tui-preset.ts` implement `/preset` switching: the preset name (or preset edits) is persisted to the user config file and takes effect on the next reload; the agent registry is never hot-swapped mid-session.
 - `src/hooks/task-session-manager/` depends on `src/utils/background-job-board.ts`, `background-job-store.ts`, `background-job-coordinator.ts`, `background-job-supervisor.ts`, `session-runtime-status.ts`, and `task.ts`, and injects prompt content only through `src/hooks/cache-safe-injection.ts`.
 - `src/hooks/cache-monitor/` watches `message.updated` cache telemetry across all sessions and logs prompt-cache-bust/plateau warnings; it is observation-only and never mutates messages.
-- `src/hooks/orchestrator-wake/` reads host todo/children/status APIs, gates on the task-session-manager's `hasInputWait` and continuation-model seams, and shares one-flight/no-progress state via a process-global wake gate.
+- `src/hooks/orchestrator-wake/` reads host todo/children/status APIs (v1)
+  or `session.list({parentID})` + event tracking (v2 degraded mode), gates on
+  the task-session-manager's `hasInputWait` and continuation-model seams, and
+  shares one-flight/no-progress state via a process-global wake gate.
 - `src/v2/` wraps the v1 factory for the v2 host: `setup(ctx)` shims a v1 `PluginInput`, runs the v1 `config()` hook, and adapts agent/tool/command/hook registrations into v2 domains.
 - `src/hooks/filter-available-skills/` and agent permission logic rely on shared skill names from the CLI/config layer.
 - `src/interview/` hooks into plugin command/event surfaces exposed by `src/index.ts`.

+ 1 - 1
companion/Cargo.lock

@@ -2250,7 +2250,7 @@ dependencies = [
 
 [[package]]
 name = "oh-my-opencode-slim-companion"
-version = "0.1.3"
+version = "0.1.4"
 dependencies = [
  "dirs",
  "eframe",

+ 1 - 1
companion/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "oh-my-opencode-slim-companion"
-version = "0.1.3"
+version = "0.1.4"
 edition = "2021"
 description = "Desktop companion for oh-my-opencode-slim - shows active agent GIFs per session"
 

+ 32 - 0
companion/VIDEOS/README.md

@@ -0,0 +1,32 @@
+# Companion animation sources
+
+These MP4 files are source media for default Companion animations. Runtime does
+not decode them directly; release binaries embed JPEG sprite sheets from
+`companion/animations/`.
+
+Each sheet uses first three seconds at 24 FPS: 72 square `200x200` frames in a
+`12x6` grid, producing a `2400x1200` JPEG.
+
+| Source | Runtime animation |
+| --- | --- |
+| `idle.mp4` | `intro.jpg` |
+| `question.mp4` | `question.jpg` (`input` state) |
+| `unknown.mp4` | `unknown.jpg` (unknown/custom agents) |
+| Other `<name>.mp4` files | Matching `<name>.jpg` agent animation |
+
+Regenerate sheets from repository root with FFmpeg:
+
+```bash
+for video in companion/VIDEOS/*.mp4; do
+  name=$(basename "$video" .mp4)
+  output_name=$name
+  if [ "$name" = "idle" ]; then
+    output_name=intro
+  fi
+  ffmpeg -hide_banner -loglevel error -y \
+    -i "$video" \
+    -vf "trim=duration=3,setpts=PTS-STARTPTS,fps=24,scale=200:200,setsar=1,tile=12x6" \
+    -frames:v 1 -q:v 2 \
+    "companion/animations/$output_name.jpg"
+done
+```

BIN
companion/VIDEOS/council.mp4


BIN
companion/VIDEOS/designer.mp4


BIN
companion/VIDEOS/explorer.mp4


BIN
companion/VIDEOS/fixer.mp4


BIN
companion/VIDEOS/idle.mp4


BIN
companion/VIDEOS/librarian.mp4


BIN
companion/VIDEOS/observer.mp4


BIN
companion/VIDEOS/oracle.mp4


BIN
companion/VIDEOS/orchestrator.mp4


BIN
companion/VIDEOS/question.mp4


BIN
companion/VIDEOS/unknown.mp4


BIN
companion/animations/council.jpg


BIN
companion/animations/designer.jpg


BIN
companion/animations/explorer.jpg


BIN
companion/animations/fixer.jpg


BIN
companion/animations/intro.jpg


BIN
companion/animations/librarian.jpg


BIN
companion/animations/observer.jpg


BIN
companion/animations/oracle.jpg


BIN
companion/animations/orchestrator.jpg


BIN
companion/animations/question.jpg


BIN
companion/animations/unknown.jpg


+ 29 - 9
companion/src/gifs.rs

@@ -2,7 +2,7 @@ use egui::{ColorImage, Context, Rect, TextureHandle, TextureId, TextureOptions};
 use std::collections::HashMap;
 
 const DEFAULT_SPEED: f32 = 1.0;
-const BASE_SPEED_MULTIPLIER: f32 = 2.0;
+const BASE_SPEED_MULTIPLIER: f32 = 1.0;
 const FRAME_RATE: f32 = 24.0;
 const FRAME_COUNT: usize = 72;
 const SHEET_COLS: usize = 12;
@@ -30,11 +30,13 @@ impl Gifs {
         sheets.insert("input", include_bytes!("../animations/question.jpg"));
         sheets.insert("intro", include_bytes!("../animations/intro.jpg"));
         sheets.insert("librarian", include_bytes!("../animations/librarian.jpg"));
+        sheets.insert("observer", include_bytes!("../animations/observer.jpg"));
         sheets.insert("oracle", include_bytes!("../animations/oracle.jpg"));
         sheets.insert(
             "orchestrator",
             include_bytes!("../animations/orchestrator.jpg"),
         );
+        sheets.insert("unknown", include_bytes!("../animations/unknown.jpg"));
         Self {
             sheets,
             textures: HashMap::new(),
@@ -92,10 +94,13 @@ impl Gifs {
         if gif_pack != "default" {
             return "orchestrator";
         }
+        if agent.starts_with("councillor-") {
+            return "council";
+        }
         self.sheets
             .get_key_value(agent)
             .map(|(name, _)| *name)
-            .unwrap_or("intro")
+            .unwrap_or("unknown")
     }
 }
 
@@ -154,14 +159,21 @@ mod tests {
     use super::{frame_index, frame_uv, normalized_speed, Gifs, FRAME_COUNT};
 
     #[test]
-    fn unknown_agents_use_the_neutral_intro_animation() {
+    fn unknown_agents_use_the_unknown_animation() {
         let gifs = Gifs::new();
 
         for agent in ["plan", "build", "general", "explore", "custom-agent"] {
-            assert_eq!(gifs.resolve_name(agent, "default"), "intro");
+            assert_eq!(gifs.resolve_name(agent, "default"), "unknown");
         }
     }
 
+    #[test]
+    fn unknown_animation_is_distinct_from_observer() {
+        let gifs = Gifs::new();
+
+        assert_ne!(gifs.sheets["unknown"], gifs.sheets["observer"]);
+    }
+
     #[test]
     fn known_agents_keep_their_own_animations() {
         let gifs = Gifs::new();
@@ -170,6 +182,7 @@ mod tests {
             "orchestrator",
             "explorer",
             "librarian",
+            "observer",
             "oracle",
             "designer",
             "fixer",
@@ -179,7 +192,14 @@ mod tests {
     }
 
     #[test]
-    fn speed_is_clamped_and_defaults_fast() {
+    fn dynamic_councillors_use_the_council_animation() {
+        let gifs = Gifs::new();
+
+        assert_eq!(gifs.resolve_name("councillor-alpha", "default"), "council");
+    }
+
+    #[test]
+    fn speed_is_clamped_and_defaults_to_one() {
         assert_eq!(normalized_speed(f32::NAN), 1.0);
         assert_eq!(normalized_speed(0.1), 0.25);
         assert_eq!(normalized_speed(9.0), 4.0);
@@ -188,18 +208,18 @@ mod tests {
     #[test]
     fn classic_loop_wraps_forward() {
         assert_eq!(frame_index(0.0, 1.0, "classic"), 0);
-        assert_eq!(frame_index(1.5, 1.0, "classic"), 0);
-        assert_eq!(frame_index(1.5 + 1.0 / 48.0, 1.0, "classic"), 1);
+        assert_eq!(frame_index(3.0, 1.0, "classic"), 0);
+        assert_eq!(frame_index(3.0 + 1.0 / 24.0, 1.0, "classic"), 1);
     }
 
     #[test]
     fn smooth_loop_ping_pongs_without_duplicate_endpoints() {
         assert_eq!(frame_index(0.0, 1.0, "smooth"), 0);
         assert_eq!(
-            frame_index((FRAME_COUNT - 1) as f64 / 48.0, 1.0, "smooth"),
+            frame_index((FRAME_COUNT - 1) as f64 / 24.0, 1.0, "smooth"),
             71
         );
-        assert_eq!(frame_index(FRAME_COUNT as f64 / 48.0, 1.0, "smooth"), 70);
+        assert_eq!(frame_index(FRAME_COUNT as f64 / 24.0, 1.0, "smooth"), 70);
     }
 
     #[test]

+ 84 - 0
docs/adr/002-v3-agent-resolution-foundation.md

@@ -0,0 +1,84 @@
+# ADR 002: V3 agent resolution foundation
+
+## Status
+
+Accepted and implemented for Deepwork Phase 1.
+
+## Decision
+
+The plugin builds one runtime-only `ResolvedAgentRegistry`. Agent SDK
+registration, model fallback chains, routing guidance, skill visibility, MCP
+selection, TUI model state, and diagnostics all consume that snapshot. The
+registry is rebuilt on plugin startup/reload; it is never hot-swapped during a
+conversation.
+
+The supported specialist role catalog is explicit: `explorer`, `librarian`,
+`oracle`, `designer`, `fixer`, and `observer`. Their reusable
+`RoleDefinition`s are the only role baselines available for future derived
+agents and profiles. `orchestrator`, `council`, and `councillor` remain special
+agents and are not profile targets.
+
+Presets are structured objects:
+
+```json
+{
+  "agents": {
+    "explorer": { "model": "provider/model" }
+  },
+  "marketplace": {
+    "agents": ["community/example"],
+    "profiles": { "oracle": "community/oracle-profile" }
+  }
+}
+```
+
+Activation arrays are optional so omitted and explicit empty values remain
+distinguishable during typed merges. Package IDs are trimmed and unique; profile
+targets are limited to supported specialist roles and `null` is a removal
+tombstone. Phase 3 resolves installed packages from the local store for the
+effective active preset, preflights required skills/MCPs, and applies derived
+agents and specialist profiles into the one `ResolvedAgentRegistry`. Package
+state remains outside `PluginConfigSchema`.
+
+Prompt resolution is a pure ordered pipeline: built-in baseline, package
+append/replace (reserved for later phases), preset override, root override,
+host-supported fields, then user append files. Mandatory plugin suffixes are
+added last, so replacements cannot remove workflow or role-policy gates.
+Inline replacement wins over a replacement file, and append files remain
+supported. Inputs are construction-time deterministic and do not contain
+volatile request data.
+
+Typed alias-aware merges are used for agent records and structured presets.
+Scalars and arrays replace; provider option objects merge; model inheritance is
+an explicit directive that clears a lower model, while an explicit model clears
+lower inheritance. The loader preserves structural presets and the resolved
+registry combines the active preset with root/host layers exactly once. User
+configuration remains editable and higher precedence than package content.
+Task controls are editable defaults; only documented plugin gates are applied
+after host projection.
+
+Required dependencies for future packages will be preflighted from built-in
+capabilities and on-disk host configuration. Missing required dependencies
+disable a package for that session; optional dependencies remain unavailable.
+Those package behaviors belong to later phases, not this ADR's implementation.
+
+## Resolution truth table
+
+| Surface | Lower layer | Higher layer | Merge rule | Phase 1 result |
+| --- | --- | --- | --- | --- |
+| Role baseline | `RoleDefinition` | user agent override | typed field merge | Implemented |
+| Prompt | baseline | replacement file/inline prompt | inline > file > baseline | Implemented |
+| Prompt append | selected prompt | append file | append after selected prompt | Implemented |
+| Prompt gates | all editable prompt layers | plugin suffix | append last, immutable | Implemented |
+| Model | baseline/active preset | root/host override | explicit model wins; inheritance is directive | Implemented |
+| Model array | lower chain | higher chain | replace array; first entry is startup model | Implemented |
+| Options | lower object | higher object | shallow typed object merge | Implemented |
+| Skills | role defaults | explicit agent skills | explicit list replaces defaults; disabled skills deny | Implemented |
+| MCPs | role defaults | explicit agent MCPs | explicit list replaces defaults; disabled MCPs are unavailable | Implemented |
+| Permissions | defaults | user rules | editable rules first, immutable gates after host projection | Implemented |
+| Routing | resolved enabled agents | none | canonical deterministic name ordering | Implemented |
+| Preset | user preset | project preset | structural merge by name; active agent layers merge once in registry | Implemented |
+| Marketplace agent | role baseline | package config | append-only role derivation | Implemented |
+| Marketplace profile | role baseline | one selected profile | one profile per explicit supported role; null is tombstone | Implemented |
+| Dependency preflight | package requirements | capabilities | required missing dependency disables package | Implemented |
+| Package state | plugin config | lock/store | separate parsed state, exact versions, offline startup | Implemented |

+ 56 - 5
docs/background-orchestration.md

@@ -359,15 +359,19 @@ periodic internal wake prompt so incomplete TODOs are not abandoned. This is
   "backgroundJobs": {
     "orchestratorWake": {
       "enabled": true,
-      "intervalMs": 300000
+      "intervalMs": 300000,
+      "mode": "auto"
     }
   }
 }
 ```
 
 `intervalMs` must be an integer from `60000` to `2147483647`. `0` is invalid.
-Set `enabled: false` to disable wakes while keeping idle reconciliation and
-background-job orchestration.
+`mode` selects the wake condition: `"auto"` (default) uses todo-gating on v1
+hosts and children-driven mode on v2 hosts; `"todo"` and `"children"` pin one
+mode (an explicit `"todo"` degrades to children on hosts without the todo
+API). Set `enabled: false` to disable wakes while keeping idle reconciliation
+and background-job orchestration.
 
 Behavior:
 
@@ -406,8 +410,20 @@ The scheduler does **not** perform automatic cancellation and does not rely on
 the local job board. When no incomplete TODOs remain, it ends the current idle
 spell and stops polling until new activity.
 
-**v2 availability:** the v2 shim lacks the required session APIs, so this
-capability-gated feature remains inactive there.
+**v2 hosts (children-driven degraded mode):** v2 has no todo/children/status
+surfaces, so with `mode: "auto"` the scheduler runs in children-driven mode.
+The wake condition becomes "children without a terminal `outcome`" — v2
+records an outcome (succeeded|failed|interrupted) only on terminal transition —
+plus pending stopped-job recovery. Children are enumerated via
+`session.list({parentID})` (event-tracked fallback from `session.created`
+links when the listing is unavailable), scoped to the session's directory, and
+a child with no fresh update evidence (host `time.updated` or a tracked status
+change within 3× the interval) counts as inactive. The wake prompt asks the
+orchestrator to check on unfinished background child sessions and unreconciled
+jobs, is delivered with `queue` semantics (like v1's queued prompt_async), and
+the children-only fingerprint keeps the two-wake no-progress cap bounding
+cost. v2's native subagent completion nudges still cover the happy path; this
+watchdog covers stuck children and unreconciled jobs.
 
 For external manual work, the orchestrator first gives the user concrete steps,
 then calls `wait_for_user` as its final tool action. This explicit signal covers
@@ -479,6 +495,41 @@ uncertain`; they never prove that a job stopped or completed and do not confirm
 a pending stop. Each observation is generation-aware, so a delayed response
 cannot modify a relaunched task.
 
+### Background Task Concurrency
+
+`backgroundJobs.concurrency` (disabled by default, see
+[Configuration](configuration.md#background-job-management)) caps how many
+native background tasks may run at once. Admission happens in the
+`tool.execute.before` hook: a task waits for a slot before OpenCode creates
+its child session. Queued requests are admitted in order, but requests whose
+resolved cap is saturated are skipped in favor of admittable later requests.
+
+Only the most specific configured cap applies to a task: a model cap wins
+over a provider cap, which wins over the default cap. `0` means unlimited.
+So `modelConcurrency: {"openai/gpt-4o": 10}` permits 10 concurrent
+`openai/gpt-4o` tasks even when `defaultConcurrency` is lower; other OpenAI
+models fall back to `providerConcurrency` (or the default) instead.
+
+The scheduler keeps its accounting correct across two runtime events:
+- A task that switches models mid-flight (foreground model fallback or a
+  runtime `/model` change on the child session) moves its provider/model
+  accounting to the new model instead of keeping the admission-time model.
+- The scheduler is process-scoped, so a plugin re-init (the plugin factory
+  re-runs on config updates) preserves both running slots and queued
+  tickets. Deleting a parent orchestrator also releases its children's
+  admission slots, so capacity is never leaked by recursive-delete ordering.
+
+Sessions that are themselves managed tasks — a background subagent running
+its own nested `task(..., background: true)` calls — are exempt from
+admission. They already hold a slot while running, so waiting for a second
+one would self-deadlock once the queue saturates.
+
+Admission itself has no timeout. A running task that never reaches a terminal
+state keeps its slot forever, and queued tasks as well as the orchestrator's
+`task` calls block behind it. When you enable `concurrency`, pair it with the
+opt-in wall-clock supervisor below so stalled tasks are eventually forced to
+a terminal state and release their slots.
+
 ### Opt-in Wall-clock Supervisor
 
 The plugin can apply a one-shot wall-clock deadline to native background task

+ 3 - 1
docs/companion.md

@@ -36,7 +36,9 @@ You can enable the companion by adding a `companion` section to your setting con
 
 - **`companion.gifPack`**:
   - `default` (default) - the bundled companion animation set generated from
-    the MP4 sources in `companion/VIDEOS/`.
+    tracked MP4 sources in `companion/VIDEOS/`. See
+    [source-video README](../companion/VIDEOS/README.md) for mappings and
+    regeneration instructions.
 
 - **`companion.loopStyle`**:
   - `classic` (default) - forward playback that loops back to the first frame.

+ 146 - 4
docs/configuration.md

@@ -37,6 +37,10 @@ The TUI sidebar uses the compact layout by default. Set `compactSidebar` to
 }
 ```
 
+While an agent session reports `busy` or `retry`, the sidebar shows an
+animated Braille indicator after that agent's name. The indicator disappears
+after every active session for that agent becomes idle or is deleted.
+
 ---
 
 ## Prompt Overriding
@@ -116,6 +120,7 @@ Presets can also be switched at runtime without restarting using the `/preset` c
 | `presets.<name>.<agent>.temperature` | number | - | Optional temperature (0–2); when omitted, OpenCode chooses its default |
 | `presets.<name>.<agent>.variant` | string | - | Reasoning effort: `"low"`, `"medium"`, `"high"`, or `"max"` (provider-specific) |
 | `presets.<name>.<agent>.displayName` | string | - | Custom user-facing alias for the agent (e.g. `"advisor"` for `oracle`) |
+| `presets.<name>.<agent>.color` | string | - | Agent display color as `#RRGGBB` or a theme color: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, or `info` |
 | `presets.<name>.<agent>.skills` | string[] | - | Skills the agent can use (`"*"`, `"!item"`, explicit list) |
 | `presets.<name>.<agent>.mcps` | string[] | - | MCPs the agent can use (`"*"`, `"!item"`, explicit list) |
 | `presets.<name>.<agent>.options` | object | - | Provider-specific model options passed to the AI SDK (e.g., `textVerbosity`, `thinking` budget) |
@@ -124,6 +129,7 @@ Presets can also be switched at runtime without restarting using the `/preset` c
 | `agents.<customAgent>.orchestratorPrompt` | string | - | Exact `@agent` block injected into the orchestrator prompt; must start with `@<agent-name>` |
 | `agents.<agent>.permission` | object \| string | - | Tool-level permission rules enforced by the SDK. See [Agent Permissions](#agent-permissions) |
 | `agents.<agent>.displayName` | string | - | Custom user-facing alias for the agent in the active config |
+| `agents.<agent>.color` | string | - | Agent display color as `#RRGGBB` or a theme color: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, or `info` |
 | `agents.<agent>.description` | string | generated | Description shown to OpenCode and the orchestrator; defaults to `Custom subagent '<name>'` for custom agents |
 | `acpAgents.<name>.command` | string | - | Command for an external ACP-compatible agent; creates a wrapper subagent named `<name>` See [ACP-connected agents](#acp-connected-agents). |
 | `acpAgents.<name>.args` | string[] | `[]` | Arguments for the ACP agent command See [ACP-connected agents](#acp-connected-agents). |
@@ -151,14 +157,20 @@ Presets can also be switched at runtime without restarting using the `/preset` c
 | `backgroundJobs.readContextMaxFiles` | integer | `8` | Maximum number of recent read-context files shown per reusable child session (0–50) See [Background Job Management](#background-job-management). |
 | `backgroundJobs.maxRetainedSnapshots` | integer | `20` | Maximum board snapshots retained per checkpoint cache epoch (1–100). Adding a snapshot beyond the limit starts a new epoch with only the current snapshot, intentionally creating one cache miss See [Background Job Management](#background-job-management). |
 | `backgroundJobs.strategy` | `"latest"` \| `"checkpoint-compatible"` | `"latest"` | Board injection strategy. `latest` preserves the current strip-and-replace behavior; `checkpoint-compatible` appends only when the formatted board changes and uses `backgroundJobs.maxRetainedSnapshots` per cache epoch. Cache state resets on compaction/session boundaries and is lost on plugin restart See [Background Job Management](#background-job-management). |
-| `backgroundJobs.orchestratorWake.enabled` | boolean | `true` | When true, idle orchestrator sessions with incomplete todos may receive periodic internal wake prompts (default every 5 minutes of continuous parent idle). Requires host session APIs; inactive on the v2 shim. See [Background Orchestration](background-orchestration.md#orchestrator-wake-scheduler) See [Background Job Management](#background-job-management). |
+| `backgroundJobs.orchestratorWake.enabled` | boolean | `true` | When true, idle orchestrator sessions with incomplete todos may receive periodic internal wake prompts (default every 5 minutes of continuous parent idle). Requires host session APIs. See [Background Orchestration](background-orchestration.md#orchestrator-wake-scheduler) See [Background Job Management](#background-job-management). |
 | `backgroundJobs.orchestratorWake.intervalMs` | integer | `300000` | Continuous parent-idle interval between wake evaluations (`60000`–`2147483647` ms). `0` is invalid. See [Background Orchestration](background-orchestration.md#orchestrator-wake-scheduler) See [Background Job Management](#background-job-management). |
+| `backgroundJobs.orchestratorWake.mode` | string | `"auto"` | Wake-condition source: `"auto"` uses todo-gating on OpenCode v1 and children-driven degraded mode on v2 hosts; `"todo"`/`"children"` pin one mode (explicit `"todo"` degrades to children where no todo API exists). See [Background Orchestration](background-orchestration.md#orchestrator-wake-scheduler). |
 | `backgroundJobs.wallClockTimeoutMs` | integer | `0` | **Opt-in wall-clock supervisor.** `0` disables it. Otherwise, only native `task(..., background: true)` child sessions are supervised; accepted values are `60000`–`2147483647` milliseconds See [Background Job Management](#background-job-management). |
 | `backgroundJobs.abortGraceMs` | integer | `10000` | Grace period after a wall-clock deadline for a terminal confirmation. Accepted values are `1000`–`60000` milliseconds; a hanging or failed abort does not extend this grace See [Background Job Management](#background-job-management). |
+| `backgroundJobs.concurrency.defaultConcurrency` | integer | `0` | Maximum concurrently running native background tasks. `0` means unlimited; accepted values are `0`–`1000` See [Background Job Management](#background-job-management). |
+| `backgroundJobs.concurrency.providerConcurrency` | object | `{}` | Per-provider caps keyed by provider ID. Each value must be `0`–`1000`, where `0` means unlimited for that provider. The most specific configured cap wins: model > provider > default See [Background Job Management](#background-job-management). |
+| `backgroundJobs.concurrency.modelConcurrency` | object | `{}` | Per-model caps keyed by `provider/model` ID. Each value must be `0`–`1000`, where `0` means unlimited for that model. The most specific configured cap wins: model > provider > default See [Background Job Management](#background-job-management). |
 | `backgroundJobs.waitForUserGuard` | boolean | `true` | When true, intercepts `wait_for_user` calls while background tasks are still running and the orchestrator wake scheduler is enabled, returning guidance to end the turn instead of blocking on manual input. See [Background Job Management](#background-job-management). |
 | `disabled_mcps` | string[] | `[]` | MCP server IDs to disable globally |
 | `fallback.enabled` | boolean | `true` | Enable Slim's foreground model-chain failover. It does not configure OpenCode provider/AI-SDK retries. |
 | `fallback.maxRetries` | number | `3` | Consecutive retryable 429 responses allowed for the same foreground model before Slim aborts or selects the next configured fallback model. It does not cap OpenCode provider retries or background subagent retries. |
+| `fallback.initialRetryDelayMs` | number | `0` | Delay in milliseconds before triggering the first fallback on a failover-worthy error. Gives intercepting plugins time to recover the current model before the fallback chain advances. 0 disables. |
+| `fallback.retryDelayMs` | number | `500` | Delay in milliseconds between consecutive fallback attempts after the initial trigger. 0 disables. |
 | `council.presets` | object | - | **Required if using council.** Named councillor presets See [Council configuration note](#council-configuration-note). |
 | `council.presets.<name>.<councillor>.model` | string | - | Councillor model See [Council configuration note](#council-configuration-note). |
 | `council.presets.<name>.<councillor>.variant` | string | - | Councillor variant See [Council configuration note](#council-configuration-note). |
@@ -231,6 +243,10 @@ subprocess.
 
 ### Council configuration note
 
+- Councillor `model` and ACP `wrapperModel` values use `provider/model`
+  references. The provider must be nonempty and cannot contain whitespace or
+  `/`; the nonempty model remainder is retained verbatim and may contain spaces
+  and nested `/` values, such as `opencode-omniroute-live/of/MiniMax M3`.
 - The **Council agent model** is configured like any other agent, for example in
   `presets.<name>.council.model`.
 - The **councillor models** are configured separately under
@@ -303,20 +319,61 @@ The wall-clock supervisor is separately opt-in and remains disabled unless
     "maxRetainedSnapshots": 10,
     "orchestratorWake": {
       "enabled": true,
-      "intervalMs": 300000
+      "intervalMs": 300000,
+      "mode": "auto"
     },
     "wallClockTimeoutMs": 900000,
-    "abortGraceMs": 10000
+    "abortGraceMs": 10000,
+    "concurrency": {
+      "defaultConcurrency": 2,
+      "providerConcurrency": {
+        "openai": 2
+      },
+      "modelConcurrency": {
+        "openai/gpt-5.6-luna": 1
+      }
+    }
   }
 }
 ```
 
-`orchestratorWake` defaults to enabled with a 5-minute continuous-idle interval.
+`orchestratorWake` defaults to enabled with a 5-minute continuous-idle
+interval and `"auto"` mode (todo-gated on v1 hosts, children-driven on v2).
 Set `enabled: false` to keep idle reconciliation and background-job orchestration
 without periodic wake prompts. See the
 [Background Orchestration](background-orchestration.md) guide for the concept,
 defaults, and examples.
 
+`concurrency` limits only native background tasks with
+`task(..., background: true)`. Foreground tasks are unchanged. A task waits
+for admission before OpenCode creates its child session, so queued work does
+not consume a provider request. `0` means unlimited.
+
+Only the most specific configured cap applies to a task, matching the
+reference implementation's priority: a model cap for the task's model wins
+over a provider cap for its provider, which wins over the default cap. For
+example, with `defaultConcurrency: 2`, `providerConcurrency: {"openai": 5}`
+and `modelConcurrency: {"openai/gpt-4o": 10}`, up to 10 `openai/gpt-4o`
+tasks run concurrently. Queued tasks are admitted in order among tasks whose
+resolved cap has capacity. Terminal completion, cancellation, failure,
+session deletion, and plugin disposal release the slot. A task that switches
+models mid-flight (e.g. foreground model fallback) moves its accounting to
+the new model. The scheduler is process-scoped: when the plugin re-inits on a
+config update, running slots and queued tickets survive, so admission state
+is not reset mid-run.
+
+Two behaviors to know about when concurrency is enabled:
+
+- Sessions that are themselves managed tasks (a background subagent
+  orchestrating its own nested `task(..., background: true)` calls) are
+  exempt from admission. They already hold a slot while running, so waiting
+  for a second one would self-deadlock once the queue saturates.
+- Admission has no timeout of its own. A running task that never reaches a
+  terminal state keeps its slot forever, and queued tasks as well as the
+  orchestrator's `task` calls block behind it. When you enable
+  `concurrency`, pair it with an opt-in `wallClockTimeoutMs` so stalled
+  tasks are eventually forced to a terminal state and release their slots.
+
 Configurations that still use the removed `backgroundJobs.continueOnIdle` key
 emit a deprecation warning and migrate its boolean value to
 `orchestratorWake.enabled`. An `orchestratorWake.enabled` value in the same
@@ -361,6 +418,91 @@ Notes:
 - Display names must be unique
 - Display names cannot conflict with internal agent names like `oracle` or `explorer`
 
+### Independent agent model inheritance
+
+By default, the `fixer` agent inherits the `librarian` model when no fixer
+model is configured. To decouple agents, set `inheritModelFrom` on the agent
+that should follow the current session or the configured orchestrator model:
+
+```jsonc
+{
+  "agents": {
+    "librarian": {
+      "model": "ollama/qwen3.8:27B"
+    },
+    "fixer": {
+      "inheritModelFrom": "session"
+    }
+  }
+}
+```
+
+Supported values are:
+
+- `session`: omit the agent model so OpenCode uses the current session model
+- `orchestrator`: use the orchestrator model resolved during configuration; if
+  none is configured, fall back to the current session model
+
+`orchestrator` means the model resolved during plugin configuration. It does
+not dynamically follow a later foreground fallback to another model.
+Runtime fallback behavior is independent of `inheritModelFrom`.
+
+Model selection follows these rules:
+
+- If the same effective agent override contains both `model` and
+  `inheritModelFrom`, the explicit `model` wins.
+- If `model` is omitted, `inheritModelFrom` is an explicit higher-layer
+  directive: it clears a lower-layer `model` value, including a model supplied
+  by the host agent configuration, and resolves the requested source.
+- If neither field is present, the existing model precedence and the historical
+  fixer-to-librarian fallback remain unchanged.
+
+The setting works in both root `agents` overrides and preset agent overrides.
+
+### Agent Colors
+
+Built-in agents ship without a default `color`, so the OpenCode TUI assigns
+each one a distinct color from its own theme-aware palette. Set `color`
+explicitly if you want a fixed color for a built-in or custom agent, using a
+six-digit hex value or an OpenCode theme color:
+
+```jsonc
+{
+  "agents": {
+    "oracle": { "color": "#FF5733" },
+    "reviewer": {
+      "model": "openai/gpt-5.6",
+      "color": "info"
+    }
+  }
+}
+```
+
+Theme colors adapt to the active OpenCode theme. Dynamic councillors inherit
+the configured `council` color unless `agents.councillor.color` overrides it.
+`color` works in top-level `agents` overrides and inside `presets`.
+
+### Local marketplace packages
+
+Preset `marketplace` activation selects installed local packages for that
+preset. Installation and activation are separate; changes apply after the
+next OpenCode session or reload. See **[Local Marketplace](marketplace.md)**
+for the CLI, in-session `marketplace` tool, and status diagnostics.
+
+```jsonc
+{
+  "preset": "work",
+  "presets": {
+    "work": {
+      "marketplace": {
+        "agents": ["community/example"],
+        "profiles": { "oracle": "community/oracle-profile" }
+      }
+    }
+  }
+}
+```
+
 ### Per-preset agent configuration
 
 To get per-preset behavior for any agent, built-in (`council`, `oracle`,

+ 79 - 0
docs/marketplace.md

@@ -0,0 +1,79 @@
+# Local Marketplace
+
+Install, inspect, and activate local offline marketplace packages. There is
+no website, network registry, or remote package resolution in this release.
+
+Package manifests are data-only, exact-version locked, and stored under the
+XDG data directory. Installation and preset activation are separate: install
+a package, then enable it in the active preset. Activated agents and
+profiles apply after the next OpenCode session or reload. The live agent
+registry is never hot-swapped.
+
+## CLI
+
+```bash
+bunx oh-my-opencode-slim marketplace install ./package.json
+bunx oh-my-opencode-slim marketplace import ./package.json
+bunx oh-my-opencode-slim marketplace list
+bunx oh-my-opencode-slim marketplace show author/name
+bunx oh-my-opencode-slim marketplace verify [author/name]
+bunx oh-my-opencode-slim marketplace update ./package-v2.json
+bunx oh-my-opencode-slim marketplace enable author/name
+bunx oh-my-opencode-slim marketplace profile librarian author/profile
+bunx oh-my-opencode-slim marketplace profile oracle --clear
+bunx oh-my-opencode-slim marketplace disable author/name
+bunx oh-my-opencode-slim marketplace remove author/name
+bunx oh-my-opencode-slim marketplace status [--json]
+```
+
+`import` is an alias for `install` and records the canonical absolute local
+source path in the lockfile. Use `update` to select a different exact
+version. `enable` activates an installed `agent` package in the active
+preset as a separately named role-derived agent. `profile` selects at most
+one installed `profile` package per supported specialist role; `--clear`
+writes a tombstone.
+
+`status` reports installed packages, configured activation, live-session
+agents when used from the in-session tool, diagnostics, and whether a
+reload is required. Diagnostics include store problems (missing, corrupt,
+or operational read failures) and activation rejections (collision,
+missing required dependency, invalid alias). Operational failures leave
+in-session `reload_required` as `unknown` because disk identity cannot be
+compared.
+
+## In-session tool
+
+The orchestrator can use the `marketplace` tool for the same local
+lifecycle: install, import, list, show, verify, update, enable, disable,
+profile, remove, and status. Do not shell out to the CLI for these actions
+when the tool is available.
+
+Disable it with `disabled_tools: ["marketplace"]`. Specialists cannot
+invoke it.
+
+Read-only actions (list, show, verify, status) do not change activation.
+Mutating actions write the local store and plugin config only and report
+`reload_required` only when disk activation differs from this session.
+Inactive or idempotent mutations do not include a reload note. The CLI
+has no live registry, so its reload status is `unknown`. In-session
+status is also `unknown` when the store or desired activation cannot be
+read (for example EACCES).
+
+## Status fields
+
+| Field | Meaning |
+|-------|---------|
+| installed | Exact locked versions in the local store |
+| configured_agents / configured_profiles | Active-preset activation on disk |
+| live_packages | Packages already in this session's registry, with version, digest, and runtime name |
+| diagnostics | Store and activation issues (missing, corrupt, operational, collision, missing required dependency, invalid alias), labeled `disk` or `live` |
+| reload_required | `true`/`false` when live and desired identities can be compared; `unknown` for CLI and when store/desired resolution failed operationally |
+
+## Limits
+
+- Local `package.json` files only. No remote URLs or registry IDs.
+- Required skills and MCPs are preflighted against built-in capabilities
+  and on-disk host configuration. Missing required dependencies disable
+  that package for the session. Optional requirements stay unavailable
+  and are never auto-installed.
+- Startup reads only the local store and never contacts a registry.

+ 412 - 34
docs/opencode-v2-compatibility.md

@@ -1,61 +1,439 @@
-# OpenCode v2 compatibility
+# OpenCode v2 (`opencode2`) Compatibility
 
-> **Capability details:** see the [OpenCode v2 capability-gap report](opencode-v2-gap-report.md)
-> for the Oracle-verified matrix, source citations, and migration dispositions.
+oh-my-opencode-slim installs and runs on **both** OpenCode v1 (`opencode`)
+and OpenCode v2 (`opencode2`) from a single published package. This document
+describes how each host loads the plugin, what is supported where, and how to
+register it.
 
-## Current status
+## How it works
 
-OpenCode v2 support is currently a **v1-to-v2 adapter**, not a native v2
-implementation. Native v2 support has not shipped.
+The package's default export is an object:
 
-`src/v2/setup.ts` receives `default.setup(ctx)`, constructs a v1-shaped
-`PluginInput` with a client shim, invokes the existing v1 factory, and
-translates selected v1 hooks into v2 registrations. This preserves useful
-behavior but does not establish v1 parity. Registration failures are isolated,
-so the adapter can load with only part of its behavior registered.
+```ts
+export default {
+  id: 'oh-my-opencode-slim',
+  server: OhMyOpenCodeLite, // v1 plugin function (PluginInput) => Promise<Hooks>
+  setup: createV2Setup(),   // v2 promise-plugin setup (ctx) => Promise<cleanup>
+};
+```
+
+There is deliberately **no `tui` key** on this export: hosts validate a
+server plugin module's `tui` field (it must be a function and must not
+coexist with `server`), so a boolean `tui: true` marker gets the whole
+plugin rejected with "invalid tui export".
+
+- **v1 loader** (`readV1Plugin` in `packages/opencode/src/plugin/shared.ts`)
+  detects an object with a `server` field and calls `plugin.server(input)`
+  with the full v1 `PluginInput`. Extra keys (such as `setup`) are ignored on
+  this path.
+- **Embedded v2 pass on v1 hosts.** Every v1 host (≥ v1.17.10) also boots
+  the v2 core, which reads the same config (migrating `plugin:` entries to
+  `plugins:`) and calls `setup(ctx)` with a registration-only context
+  (agent/aisdk/catalog/command/integration/plugin/reference/skill — no
+  tool/session/event/mcp/generate). A dual-export plugin registered via the
+  v1 `plugin:` key therefore gets **both** invocations: full v1
+  functionality flows through `server()`, while the parallel pass produces
+  the expected `[v2] … failed` / `bridges: 4` log noise (see
+  [Environment caveats](#environment-caveats)). A v2 `plugins:` entry yields
+  the setup pass alone — v1 does not convert v2 plugin declarations into v1
+  hooks.
+- **v2 loader** (`PluginModule` schema in
+  `packages/core/src/plugin/supervisor.ts`) decodes `default` as
+  `{ id, setup }` (Effect Schema 4 rejects function defaults) and calls
+  `setup(ctx)` via the promise-plugin bridge.
+- **v2 TUI** loads the `./tui` entry unconditionally: the TUI runtime runs
+  its own `kind: "tui"` loader pass over the same plugin list and resolves
+  the entry through the package's `exports["./tui"]` map — the server-side
+  export plays no role in that discovery.
+
+Three builds are produced:
+
+| Export | File | Build | Externals |
+|---|---|---|---|
+| `.` (main) | `dist/index.js` | `build:plugin` | zod, jsdom, @opencode-ai/*, @opentui/* (shared with v1 host) |
+| `./server` | `dist/server/index.js` | `build:v2` | jsdom only (self-contained for v2) |
+| `./tui` | `dist/tui2.js` | `build:tui` | same external set as `build:plugin` (composes the v1 TUI entry; inlines zod) |
+
+v2's plugin resolver tries the `server` subpath first
+(`subpaths: ["server", ""]`), which the exports map resolves directly to
+`dist/server/index.js` — the self-contained v2 server bundle, and also the
+entrypoint v2 loads when the `dist/server` directory is registered directly
+(see [Installing on v2](#installing-on-v2)); the release artifact check
+requires it. v1 uses the main entry.
+
+Verified against opencode2 `beta-19365` (all bridges green — health check
+`bridges:11`; live mock-driven re-verification on 2026-09-09 exercised the
+event-stream bridge end-to-end, including the orchestrator-wake
+children-driven degraded mode firing a queued wake after 60 s of parent
+idle with a stalled background child). Every v2 API the adapter touches is
+capability-probed at runtime (`typeof ctx.mcp?.transform === 'function'`,
+`s.switchModel`, `ctx.generate`, …), so a host lacking one capability
+degrades that single feature with a log line instead of breaking the load.
+
+## The v2 adapter (`src/v2/setup.ts`)
+
+`setup(ctx)` wraps the existing v1 factory rather than reimplementing it:
+
+1. Builds a v1-shaped `PluginInput` from the v2 context
+   (`src/v2/client-shim.ts`): the project directory from `ctx.location`,
+   and a shim `client` that **really delegates** the v1 SDK call shapes to
+   v2 flat session calls — `session.get`, `session.abort`→`interrupt`,
+   `session.messages`→`context`, `session.prompt` (as `delivery: "steer"`),
+   `session.update`→`rename`, `session.delete`→`remove` (same
+   `DELETE /api/session/:id`; stops the smartfetch secondary-model temp
+   sessions leaking), and `session.list` (v2 `Session.Info` page → the v1
+   `{data}` envelope with `directory` derived from `location` and `outcome`
+   mapped, used by the interview dashboard's session scan and the
+   orchestrator-wake children enumeration). The shim marks the input
+   `hostFlavor: 'v2'` and never fakes success shapes: methods the host
+   lacks degrade with an honest log (or are omitted entirely, as with
+   `session.get`, so capability probes see the truth).
+2. Invokes `OhMyOpenCodeLite(pluginInput)` to reuse **all** existing build
+   logic (config, agents, tools, hooks, job board, multiplexer, companion).
+3. Runs the v1 `config()` hook against a synthesized config to resolve agent
+   models and the slash commands.
+4. Bridges the returned v1 `Hooks` into v2 registrations:
+   - `agent` → `ctx.agent.transform` (model/prompt/permission adaptation +
+     `subagent`/`execute` permission mapping + prompt rewrite `task`→`subagent`
+     + `draft.default("orchestrator")`)
+    - `tool` → `ctx.tool.transform` (zod shape → JSON schema; execute
+      shimmed; every registration carries `options: {codemode: false}` —
+      see the feature matrix note below)
+   - `mcp` → `ctx.mcp.transform` (`draft.set(name, adaptMcpServer(cfg))` for
+     the built-in MCPs)
+   - `command` → `ctx.command.transform` — v2 command drafts are add-only:
+     `draft.add({name, description, execute})`. `execute` submits a
+     `<omos-cmd-command data-name="...">` marker as a user prompt; the
+     session context hook recovers it and dispatches to the v1
+     `command.execute.before` hook (deepwork/reflect/loop)
+    - a single `ctx.session.hook("context")` handles the system/messages
+      transforms (SystemPart[]/Message.content shape conversion),
+      `chat.message` agent tracking, and interview + generic command marker
+      dispatch — mutating only the trailing message so earlier content stays
+      byte-identical (provider prompt-cache prefix reuse). While the
+      bridged messages transform runs, parts injected through
+      `cache-safe-injection` carry a v2 `ContentPart.cache`
+      `{type: "ephemeral"}` hint (CacheHint tagging) so providers that
+      honor manual breakpoints cap the injected zone's cache contribution;
+      the hint is scoped per request (an `AsyncLocalStorage` scope around
+      the bridged transform) so concurrent sessions' transforms cannot
+      interleave their set/restore, and the v1 pipeline never enters the
+      scope, so v1 payload bytes never change.
+    - a native `ctx.session.hook("prompt")` registration (capability-
+      guarded): the v2 prompt hook fires **once per admitted input** with
+      the eventual inbox User `messageID`, giving the v1 `chat.message`
+      consumers (task-session-manager / orchestrator-wake
+      `observeChatMessage`, `toolLoopGuard.observeNewUserMessage`) true
+      once-per-admission fidelity with prompt parts. The FIRST admitted
+      prompt per session is deferred until the first agent-bearing
+      context event arrives, then delivered once with parts + agent
+      together — the v1 `chat.message` handler only registers the session
+      agent when a delivery carries one, and its consumers gate on that
+      registration, so an agent-less first forward would be dropped (lost
+      input-wait latch clearing / wake-progress rearm). Bounded fallbacks
+      (next admission, or a context event whose trailing user message has
+      moved past the pending one) flush a still-pending prompt best-known
+      when no agent is ever learned. When the prompt hook registers, the
+      context hook's per-request `chat.message` emulation narrows to
+      agent/model discovery; hosts that reject the hook name keep the
+      full emulation as fallback.
+    - `tool.execute.before/after` → `ctx.tool.hook` via
+      `createToolExecuteBridges` (`src/v2/setup.ts`): the host `subagent`
+      tool is normalized to v1 `task` semantics (name mapping, `agent`→
+      `subagent_type`, `sessionID`→`task_id`, and back after the hook so
+      v2 executes the repaired input). A throwing `execute.before`
+      **rethrows** — v2 rejects the tool call, which is how the v1
+      anti-duplicate / relaunch-lease guards enforce on v2. The after
+      bridge honors v2's status discrimination: `error` events synthesize
+      the v1 after-hook output from the error text (so json-error-recovery
+      still appends its reminder to a failed call's output), and an
+      errored call never presents its result content as a success.
+    - `event` → `ctx.event.subscribe()` loop feeding `mapV2EventToV1`
+      (`src/v2/event-adapter.ts`): additive synthesis only — the raw v2 event
+      is always dispatched first (the interview bridge depends on it), then
+      synthesized v1 shapes: idle `session.status` → `session.idle`, flat
+      child `session.created` → v1 early-registration
+      `{info: {id, parentID, agent?}}`, usage telemetry
+      (`session.usage.updated`/`session.step.ended`) → a deduplicated
+      completed-assistant `message.updated` for the cache monitor, the Form
+      flow (`form.created`/`form.replied`/`form.cancelled`) → v1
+      `question.asked`/`question.replied`/`question.rejected`
+      (`form.id` → the question request id; forms owned by the `"global"`
+      sentinel are skipped), and `permission.asked` field mapping to the v1
+      names (`permission` ← `action`, `patterns` ← `resources`;
+      `permission.replied` passes through raw — v2's shape already matches
+      the v1 event). Newer hosts (verified live on beta-19365/beta-19378)
+      publish durable `session.execution.started/succeeded/failed/
+      interrupted` and no longer stream busy/idle `session.status` on the
+      event flow; those events are synthesized into the same v1 lifecycle
+      shapes (`started` → busy `session.status`; terminal subtypes → idle
+      `session.status` + `session.idle`; `failed` additionally emits a v1
+      `session.error` with the host error payload before the idle pair).
+      The `session.status` → `session.idle` path is retained for older
+      builds — hosts emitting both simply deliver idle repeatedly, which
+      the double-idle invariant tolerates. This is what keeps the
+      companion's waiting-input indicator, the task-session-manager
+      input-wait gate, orchestrator-wake suppression/arm scheduling, and
+      the foreground fallback working on v2 hosts.
+   - `generate.text` → one-shot generation channel probed on `ctx.generate`
+     and threaded as `experimental_v2.generateText`, powering the webfetch
+     secondary-model summaries without a temp session
+   - `dispose` → returned cleanup
+
+Each bridge is independently try/catch-guarded so one failure cannot disable
+the rest, and a zero-registration load logs a loud health-check warning.
+
+## Feature matrix
+
+| Capability | v1 (`opencode`) | v2 (`opencode2`) | Notes |
+|---|---|---|---|
+| Orchestrator + specialist agents, prompts & permission mapping | ✅ | ✅ `ctx.agent.transform` | — |
+| Delegation + background job board + `task_*` tools | ✅ `task` tool | ✅ host `subagent` (auto-bridged: name/args normalization in `src/v2/delegation.ts`, output parsing in the execute bridges) | — |
+| Tools (ast-grep, webfetch, task_message/task_cancel/task_revive, wait_for_user, acp_run) | ✅ | ✅ `ctx.tool.transform` | v2 requires `options: {codemode: false}` on each registration (CodeMode split): without it a tool registers cleanly but is confined to the `execute` tool's JS runtime and session catalogs yield `Unknown tool: <name>`. The plugin stamps it on every adapted tool (`adaptTool` in `src/v2/adapters.ts`; additive field, older hosts ignore it). ast-grep needs its CLI binary (package, system, or lazy download); webfetch needs `jsdom` resolvable |
+| Slash commands `/deepwork` `/reflect` `/loop` | ✅ | ✅ marker round-trip | — |
+| `/interview` | ✅ | ✅ marker command + trailing-message context bridge | — |
+| Message transforms (phase reminder, skills filter, image routing, display-name rewrite) | ✅ | ✅ via the single context hook | — |
+| Event handling (session tracking, lifecycle, cache telemetry) | ✅ | ✅ event pump + additive v2→v1 synthesis | — |
+| Tool execute hooks (apply-patch recovery, task-session, json-recovery) | ✅ | ✅ `createToolExecuteBridges` with subagent→task normalization | — |
+| Built-in MCPs (context7, gh_grep) auto-registered | ✅ | ✅ `ctx.mcp.transform` | — |
+| webfetch secondary-model summaries | ✅ | ✅ via `ctx.generate.text` | host without `ctx.generate` → summaries unavailable (logged) |
+| Foreground model fallback (rate-limit failover) | ✅ | ✅ shim translates re-prompt into `session.switchModel` + `delivery:"steer"` prompt | — |
+| `/preset` (interactive switcher) | ✅ | ✅ TUI plugin entry (`./tui` → `dist/tui2.js`): sidebar + `/preset` dialog or `/preset <name>` fast path | TUI host needs `keymap.layer` + `ui.dialog.select`; config-file `preset` still applies at load |
+| TUI default agent | ✅ orchestrator | ✅ orchestrator — `draft.default("orchestrator")`; the v2 TUI honors `default_agent` and hoists the default to the head of the agent list | — |
+| Multiplexer (tmux/zellij/herdr/cmux panes) | ✅ | ❌ host-gated off (`hostFlavor: 'v2'` → `shouldEnableMultiplexer` returns false and the session manager is forced to `type: "none"`) | by design — v2 renders subagents natively |
+| Orchestrator-wake scheduler | ✅ todo-gated (host `todo`/`children`/`status` APIs) | ✅ children-driven degraded mode (`backgroundJobs.orchestratorWake.mode`) | v2 wake enumerates children via `session.list({parentID})` with an event-tracked fallback, gates on children without a terminal `outcome` (staleness-bounded), and delivers with `queue`; v2's native subagent completion nudges still cover the happy path — the port adds a periodic watchdog for stuck children and unreconciled jobs |
+| `chat.headers` (custom request headers) | ✅ | ❌ unbridged | low value: v2 exposes a model request hook (`session.hook("model.request")`, with mutable `headers`) — will bridge only if asked for |
+| Companion app | ✅ | ⚠️ unverified | independent desktop app; test separately against v2 |
+
+## Upstream behaviors to know
+
+Behaviors of v2 itself that plugin authors should know about — none
+currently break this plugin:
 
-Read the [detailed gap report](opencode-v2-gap-report.md) before relying on any
-v2 feature beyond basic loading and smoke-tested behavior.
+- **Event payloads ride under `data`, not `properties`.** The v2
+  event stream (SSE and `ctx.event.subscribe()`) frames each event as
+  `{id, created, type, location?, durable?, data}` — the payload is the
+  `data` record, unlike the v1 SDK's `properties` (verified live on
+  beta-19365: every observed event keyed exactly
+  `["id","created","type","durable","data"]`). The adapter reads `data`
+  first with `properties` as a legacy fallback and always writes
+  `properties` on the synthesized v1 shapes, because that is the key the
+  v1 consumers read.
+- **Lifecycle keys on `session.execution.*` on newer hosts.** Verified
+  live hosts (beta-19365/beta-19378) publish durable
+  `session.execution.started/succeeded/failed/interrupted` events
+  (`{sessionID}`, plus `error` on `.failed` and `reason` on
+  `.interrupted`) and no longer publish busy/idle `session.status` on
+  the SSE event stream (`session.status` remains only in the schema).
+  The adapter synthesizes the v1 lifecycle shapes from the execution
+  events (`started` → busy `session.status`; terminal subtypes → idle
+  `session.status` + `session.idle`; `failed` → a v1 `session.error`
+  with the host error payload passed through best-effort, emitted before
+  the idle pair so the error-then-idle flow the event-router expects is
+  preserved). The `session.status` mapping is retained for older builds;
+  a host emitting both delivers idle repeatedly — the double-idle
+  invariant above covers it. Without this synthesis the
+  orchestrator-wake scheduler never arms on live v2 hosts.
+- **Transcript user messages carry no identity.** Context-hook
+  transcript user messages on live v2 hosts carry `{id, time, text,
+  type}` only — no `agent`, no `sessionID`. The v1 injection gates
+  (phase-reminder, background-job-board, post-file-tool-nudge) key on
+  user-message `info.agent`/`info.sessionID`, so every injection would
+  skip. The v2 context bridge stamps the context event's `sessionID` and
+  the session's known agent (from the event, falling back to the
+  session-prompt bridge's learned state) onto transcript user messages
+  before the bridged messages transform runs — metadata-only envelope
+  enrichment, strictly absence-gated (host-provided values never
+  overwritten), parts/content bytes untouched, idempotent across
+  context events. This also makes the CacheHint-tagged injected parts
+  observable on live v2 hosts.
+- **Runtime status reconciliation is capability-gated.** v2 has no
+  equivalent of the v1 live session-status map (`client.session.status`
+  is not a function on v2 hosts), so the task-session-manager's
+  runtime-status reconciliation poll is disabled entirely on hosts
+  without the method — a single per-instance log line notes the
+  disabled reconciliation instead of logging uncertainty every ~5s poll.
+  v1 hosts expose the method and keep the exact historical polling
+  behavior. Background job stop-confirmation was never obtainable from
+  the v2 poll anyway (the lookup failed every time).
+- **Duplicate idle delivery.** v2 favors `session.status` over
+  `session.idle`; the adapter synthesizes `session.idle` additively, so a
+  consumer watching both events sees idle twice per session. Current
+  consumers are idempotent per session (idle-reconciliation's per-session
+  timer guards); new idle consumers must tolerate duplicate delivery.
+- **Duplicate `permission.asked` delivery.** The adapter appends a
+  v1-field-mapped copy after the raw v2 `permission.asked` event (raw
+  first is a load-bearing invariant for v2-native handlers). Consumers
+  watching both see the ask twice with the same request id — safe because
+  every ask consumer is idempotent per request id (the input-wait
+  tracker's Set, the companion's status setters, wake suppression); new
+  ask consumers must tolerate it, like idle.
+- **Question flow is Form-based.** v2 replaced `question.*` with the Form
+  flow; the adapter synthesizes `question.asked/replied/rejected` from
+  `form.created/replied/cancelled` so v1 consumers keep working. Forms
+  owned by the `"global"` sentinel session (MCP elicitation) are not
+  synthesized — v1 question events are session-scoped.
+- **MCP tool-name namespaces are host-generated.** This plugin never
+  matches raw MCP tool names: MCP access is granted per server name
+  (`"mcps": ["context7", "!gh_grep"]` in agent config), and registration
+  uses its own server names via `draft.set(name, ...)`.
 
-## Installing the current adapter on v2
+## Installing on v2
 
-The OpenCode beta executable is `opencode2`; its global configuration root is
-still named `opencode`:
+Add the npm package, **pinned to an exact version** — v2 auto-refreshes
+unpinned npm plugins on every startup, so `@latest` effectively means
+"silently upgrade whenever a new version ships". The global config root is
+`~/.config/opencode/opencode.json`, shared with v1 (`~/.config/opencode2/`
+is not read for plugin config):
 
 ```json
-// ~/.config/opencode/opencode.json
 {
-  "plugins": ["oh-my-opencode-slim@latest"]
+  "plugin": ["oh-my-opencode-slim@2.2.17"]
 }
 ```
 
-For local development:
+For local development, point the config at the built `dist/server`
+**directory**:
 
 ```json
 {
-  "plugins": ["/path/to/oh-my-opencode-slim/dist/server.js"]
+  "plugin": ["/path/to/oh-my-opencode-slim/dist/server"]
 }
 ```
 
-Build both host bundles with:
+Then build:
 
 ```bash
 bun install
-bun run build
+bun run build   # produces dist/index.js (v1), dist/server/index.js (v2
+                # server bundle, also served via the ./server subpath),
+                # dist/tui2.js (v2 TUI), dist/cli/
 ```
 
-This produces `dist/index.js` for v1 and `dist/server.js` for the current v2
-adapter. Run v2 from the project directory, or use `--standalone`; the
-adapter obtains its directory from `process.cwd()`.
+Verify with `opencode2 run "list your specialist agents" --standalone` — the
+orchestrator should name explorer, librarian, oracle, designer, fixer.
+
+### Registration rules
+
+- **Directory or package entries only.** File-path entries (e.g.
+  `…/dist/server.js`) are rejected with the WARN
+  `configured plugin path must be a directory`. A directory entry's
+  `index.js` is the entrypoint — hence `dist/server` above.
+- **Single-file plugins need a wrapper dir** whose `index.js` re-exports the
+  original file, e.g. `~/.config/opencode/plugins-dev/<name>/index.js`
+  containing `export { default } from "/abs/path/to/plugin.js";`. Do not
+  use the auto-scanned dir names `plugin`/`plugins` for wrapper dirs — a
+  scanned duplicate next to an explicit registration hard-dies on duplicate
+  plugin ID.
+
+## Configuring models on v2
+
+Agent models are resolved the same way as v1 (per-agent `model` in
+`oh-my-opencode-slim.json`, or inherited from the session/host default). On
+v2, set a working provider+model in your config or the plugin's config file
+so delegated subagents can run.
+
+When the foreground model hits a rate limit, the plugin switches the
+session's model (`session.switchModel`) and steers the re-prompt through
+`delivery: "steer"`. A failing `switchModel` call degrades honestly: the
+re-prompt is still delivered (on the current model) and the plugin's logs
+record that no switch happened — the fallback chain is not aborted. On
+hosts without `session.switchModel`, the fallback replay is rejected with
+a clear error instead of silently replaying on the model that just failed
+(other prompt callers, like the orchestrator-wake scheduler, only pin the
+current model and keep steering).
+
+## Limitations
+
+### Interview
+
+`/interview` is supported on v2 through a marker command and a
+trailing-message context bridge. The bridge keeps an in-memory transcript
+projection from v2 context and streamed text events, and uses the v2 session
+methods for prompts, notifications, and renames. Interview notifications
+admit the synthetic input with `resume: false` — the interview URL lands in
+the session without waking an agent turn (the v1 `noReply` prompt
+equivalent). The markdown document
+remains the durable source of truth; completion responses without
+`<interview_state>` rewrite the current spec while retaining frontmatter and
+Q&A history.
+
+### v1-only, by design
+
+- **Multiplexer panes.** tmux/zellij/herdr integration is a v1-TUI feature;
+  v2 renders subagents natively, so the multiplexer is host-gated off on v2
+  (`shouldEnableMultiplexer` / `sessionManagerMultiplexerConfig` in
+  `src/index.ts`).
+- **`chat.headers`.** Not bridged (low value on v2 — a model request hook
+  exists, `session.hook("model.request")` with mutable `headers`, if
+  demand appears).
+
+### Orchestrator-wake on v2 (children-driven degraded mode)
+
+The wake scheduler is **active on v2** in a degraded mode, configured with
+`backgroundJobs.orchestratorWake.mode` (`"auto"` | `"todo"` | `"children"`,
+default `"auto"`: todo-gating on v1, children-driven on v2; an explicit
+`"todo"` degrades to children because v2 has no todo surface — logged once).
+
+How it differs from the v1 path:
+
+- **Gate:** v2 requires only the shim's `session.list` + `promptAsync`
+  (`session.get` is optional model enrichment). v1 keeps its exact
+  historical probe set (`get`/`todo`/`children`/`status`/`promptAsync`).
+- **Children enumeration:** `session.list({ parentID })` through the shim
+  (v2 `Session.Info` → v1 envelope; `outcome` and `time.updated` mapped).
+  When the listing is unavailable (missing/erroring/empty), an event-tracked
+  fallback uses the adapter-synthesized `session.created` parentID links plus
+  tracked busy/idle statuses. Results are scoped to the session's directory
+  when the host reports one.
+- **Wake condition:** children with `outcome === undefined` (v2 records an
+  outcome only on terminal transition: succeeded|failed|interrupted) that
+  still have fresh update evidence — host `time.updated` or a tracked status
+  change newer than 3× the wake interval (staleness bound for children that
+  crash mid-run without recording an outcome). Stopped-job recovery wakes
+  bypass the condition, as on v1.
+- **Wake delivery:** `delivery: "queue"` — v1 `prompt_async` queued, and a
+  v2 `steer` would hijack an in-flight run. The shim's `promptAsync` keeps
+  `steer` as the default so the foreground-fallback replay is unchanged.
+- **Fingerprint:** children-only (id + outcome + tracked status + update
+  evidence); the two-wake no-progress cap still bounds cost.
 
-## Native migration status
+v2's built-in `subagent` tool still posts completion notifications to the
+parent natively — that covers the happy path. What the port adds is a
+periodic watchdog: an idle parent with a stuck or unreconciled child (or a
+job that stopped without a terminal result) gets woken to assess, cancel, or
+respawn, bounded by the same no-progress cap as v1.
 
-The planned implementation is a separate native Promise plugin using
-`define({ id, setup })` and the official v2 domains. It will not invoke the v1
-factory, convert v2 values to v1 hook payloads, or silently continue after a
-required registration failure.
+### Environment caveats
 
-The migration remains planned. Its roadmap is: inventory and approve behavior
-changes, separate shared host-neutral logic from the v1 composition, implement
-and smoke-test native v2 registrations while retaining v1, then remove the
-adapter only after release-readiness review.
+- **Reduced/TUI-side hosts.** Some host processes load the plugin's `setup`
+  with a reduced, TUI-side context that lacks `agent.transform` (and other
+  domains). The adapter capability-guards `setup` and skips registration
+  gracefully for those hosts instead of crashing or retry-storming. The
+  same applies to the embedded v2 pass inside every v1 host: it invokes
+  `setup` with registration-only domains, so a v1 session's plugin log
+  shows `[v2] tool.transform failed`-style lines and
+  `health check passed {"bridges":4}` — expected noise from that parallel
+  pass, not breakage. The classic `server()` path (a separate plugin-log
+  instance a few seconds apart) carries the full v1 functionality.
+- **Local-checkout loading.** When the plugin is registered from a local
+  build, the externalized `jsdom` import must resolve from the plugin's
+  `node_modules` (webfetch imports it lazily, so the plugin still loads
+  without it — install as a package or ensure `jsdom` is resolvable to
+  enable webfetch locally). AST-grep resolves its CLI independently and
+  lazily downloads a binary when no package or system binary is available.
+- **Companion app unverified on v2.** The companion is an independent
+  desktop app; test it separately against v2 hosts.
+- **Prompt-cache rules unchanged.** The v2 bridges reuse the v1 transform
+  pipeline under the same cache-safety contract: only trailing messages are
+  mutated, earlier content stays byte-identical, and the v1 enforcement
+  suite (`src/hooks/cache-safety.property.test.ts` and friends) covers the
+  shared transform code the v2 context hook invokes. The one v2-only
+  addition is CacheHint tagging: parts injected through
+  `cache-safe-injection` while the v2 context bridge runs carry
+  `cache: {type: "ephemeral"}` (v2 `ContentPart.cache`). The hint is
+  applied via a per-request scoped default (AsyncLocalStorage — the v2
+  host serves different sessions' requests concurrently, so the scope
+  must be isolated per bridged transform) inside the v2 bridge only — v1
+  callers never set it, so the v1 payload (and its snapshots) stay
+  byte-identical.

+ 2 - 2
docs/preset-switching.md

@@ -33,8 +33,8 @@ the built-in `/models`, so it triggers no LLM turn.
    new model may have a smaller window), drift prior assistant turns under
    a changed system prompt, leave running subagents referencing stale agent
    definitions, or shift tool/skill availability. A future path to true
-   in-session switching without reset requires upgrading
-   `@opencode-ai/plugin` (tracked in #799).
+   in-session switching without reset requires a host API for atomic
+   agent-registry refresh with session compatibility checks.
 
 ### Level 3 — model and variant selection
 

+ 1 - 0
docs/quick-reference.md

@@ -26,6 +26,7 @@
 | [Skills](skills.md) | `simplify`, `codemap`, `clonedeps` - skills assignment syntax |
 | [MCPs](mcps.md) | `context7`, `gh_grep` - permissions per agent, global disable |
 | [Tools](tools.md) | Background tasks, LSP, code search (`ast_grep`), formatters |
+| [Marketplace](marketplace.md) | Local offline package install, activation, status, and the in-session tool |
 | [Configuration](configuration.md) | Config files, prompt overriding, JSONC, full option reference table |
 
 ## 💡 Author's Setup

+ 7 - 0
docs/release.md

@@ -203,6 +203,13 @@ bun test
 bun run build
 ```
 
+CI installs the Bun version pinned by `packageManager` in `package.json` and
+uses `bun ci` so lockfile drift fails instead of rewriting `bun.lock`. Required
+CI uploads JUnit and LCOV reports. A non-blocking latest-Bun canary reports
+upcoming runtime incompatibilities, package smoke runs for package-affecting
+pull requests, and the scheduled OpenCode compatibility workflow checks both
+the pinned supported host and latest host canary.
+
 ### Skill Synchronization Hashes Gate
 
 If this release changes any bundled skill content (under `src/skills/`), you must populate the `LEGACY_MANAGED_SKILL_HASHES` table in `src/hooks/auto-update-checker/skill-sync.ts` with the hashes of the previously published versions of those skills (obtained from published npm package tarballs). This ensures existing users' installations are adopted and upgraded safely. If this is a migration-only release without skill changes, confirm the table is kept as-is.

+ 26 - 0
docs/tools.md

@@ -38,6 +38,16 @@ Fast, structural code search and refactoring - more powerful than plain text gre
 
 `ast_grep` understands code structure, so it can find patterns like "all arrow functions that return a JSX element" rather than relying on exact text matching.
 
+Before the built-in `grep`/`glob` tools run, the plugin pre-checks that the
+requested `path` is valid, using the same resolution rules as the host:
+v1 `grep` joins relative paths, while v1 `glob` resolves them; v2 resolves
+relative paths for both tools. Missing paths and paths with a non-directory
+component fail fast with an actionable error instead of an opaque "ripgrep
+execution failed" message or a silent search of the parent directory.
+Resolution uses the host process's native path flavor, preserving Windows
+drive-relative behavior; if no project directory is available, the guard
+conservatively passes the path through.
+
 ---
 
 ## Background Task Control
@@ -76,6 +86,22 @@ lifecycle, cancellation, and explicit-wait edge cases behind these tools.
 
 ---
 
+## Local Marketplace
+
+| Tool | Description |
+|------|-------------|
+| `marketplace` | Install, inspect, and activate local offline marketplace packages |
+
+`marketplace` is orchestrator-only. list/show/verify/status are read-only.
+install/import/update/enable/disable/profile/remove write store/config only
+and report reload only when disk activation differs from the live session.
+The CLI reports reload status as unknown. In-session status is also
+unknown when the store or desired activation cannot be read. There is no
+network registry.
+See [Local Marketplace](marketplace.md).
+
+---
+
 ## Repeated Tool-Call Loop Guard
 
 A safety net for model-side infinite loops where a sub-agent (e.g. a model

BIN
img/council.jpg


BIN
img/council.png


BIN
img/designer.jpg


BIN
img/designer.png


BIN
img/explorer.jpg


BIN
img/explorer.png


BIN
img/fixer.jpg


BIN
img/fixer.png


BIN
img/librarian.jpg


BIN
img/librarian.png


BIN
img/observer.jpg


BIN
img/oracle.jpg


BIN
img/oracle.png


BIN
img/orchestrator.jpg


BIN
img/orchestrator.png


BIN
img/v2.jpg


BIN
img/v2.webp


Fișier diff suprimat deoarece este prea mare
+ 538 - 366
oh-my-opencode-slim.schema.json


+ 19 - 12
package.json

@@ -1,6 +1,7 @@
 {
   "name": "oh-my-opencode-slim",
-  "version": "2.2.16",
+  "version": "3.0.0-beta.0",
+  "packageManager": "bun@1.3.14",
   "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
@@ -10,11 +11,11 @@
       "types": "./dist/index.d.ts"
     },
     "./server": {
-      "import": "./dist/server.js"
+      "import": "./dist/server/index.js"
     },
     "./tui": {
-      "import": "./dist/tui.js",
-      "types": "./dist/tui.d.ts"
+      "import": "./dist/tui2.js",
+      "types": "./dist/v2/tui.d.ts"
     }
   },
   "bin": {
@@ -52,12 +53,14 @@
     "README.ko-KR.md",
     "LICENSE"
   ],
+  "// build:plugin": "Keeps bundling src/tui.ts -> dist/tui.js: required by scripts/verify-release-artifact.ts (packagedRequiredFiles) and loaded by v1 TUI hosts. The v2 TUI entry is dist/tui2.js (see build:tui / the ./tui export).",
   "scripts": {
     "clean:dist": "bun -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\"",
-    "build:plugin": "bun build src/index.ts src/tui.ts --outdir dist --target node --format esm --external @ast-grep/napi --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external @opentui/core --external @opentui/solid --external jsdom --external zod",
-    "build:v2": "bun build src/index.ts --outfile dist/server.js --target node --format esm --external @ast-grep/napi --external jsdom",
-    "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target node --format esm --external @ast-grep/napi --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external jsdom --external zod",
-    "build": "bun run clean:dist && bun run build:plugin && bun run build:v2 && bun run build:cli && tsc --emitDeclarationOnly && bun run generate-schema",
+    "build:plugin": "bun build src/index.ts src/tui.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external @opentui/core --external @opentui/solid --external solid-js --external jsdom --external zod",
+    "build:v2": "bun build src/index.ts --outfile dist/server/index.js --target node --format esm --external jsdom",
+    "build:tui": "bun build src/v2/tui.ts --outfile dist/tui2.js --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external @opentui/core --external @opentui/solid --external solid-js --external jsdom",
+    "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opencode-ai/sdk/v2 --external jsdom --external zod",
+    "build": "bun run clean:dist && bun run build:plugin && bun run build:v2 && bun run build:tui && bun run build:cli && tsc --emitDeclarationOnly && bun run generate-schema",
     "prepare": "bun run build",
     "contributors:add": "all-contributors add",
     "contributors:check": "all-contributors check",
@@ -83,21 +86,25 @@
   },
   "dependencies": {
     "@ast-grep/cli": "^0.44.1",
-    "@modelcontextprotocol/sdk": "^1.30.0",
     "@mozilla/readability": "^0.6.0",
-    "@opencode-ai/plugin": "1.18.13",
-    "@opencode-ai/sdk": "1.18.13",
+    "@opencode-ai/plugin": "1.18.23",
+    "@opencode-ai/sdk": "1.18.23",
+    "gray-matter": "4.0.3",
     "jsdom": "^29.0.0",
     "lru-cache": "^11.5.2",
+    "semver": "^7.7.4",
     "turndown": "^7.2.4"
   },
   "optionalDependencies": {
-    "@opentui/solid": "^0.1.97"
+    "@opentui/core": "0.5.8",
+    "@opentui/solid": "0.5.8",
+    "solid-js": "1.9.12"
   },
   "devDependencies": {
     "@biomejs/biome": "2.5.4",
     "@types/jsdom": "28",
     "@types/node": "^26.1.1",
+    "@types/semver": "^7.8.0",
     "@types/turndown": "^5.0.6",
     "all-contributors-cli": "^6.26.1",
     "bun-types": "^1.3.14",

+ 8 - 4
scripts/codemap.md

@@ -17,10 +17,13 @@
   - Scans `dist/**/*` for leaked machine paths (`/Users/*`, `/home/*`).
   - Validates required package payload keys (`package.json`, `dist/index.js`, `README.md`, `LICENSE`,
     `src/skills/codemap/SKILL.md`, `src/skills/simplify/SKILL.md`, etc.).
-  - Performs clean install smoke by importing the installed `dist/index.js` default export in a temp project.
+  - Performs clean install smoke by importing the installed server entry with Node and the optional OpenTUI-backed
+    `./tui` entry with Bun in a temp project.
 - `verify-opencode-host-smoke.ts`
   - Builds temporary OpenCode environment (bin from `bun add opencode-ai`), mounts the plugin tarball,
     launches `opencode serve`, and probes `http://127.0.0.1:<port>/global/health`.
+  - Uses `OPENCODE_SMOKE_VERSION` when set so scheduled CI can test the pinned supported host and latest-host canary;
+    defaults to `latest` for local and release smoke runs.
   - Captures logs and fails on `failed to load plugin` and `cannot find module` patterns.
 - All scripts are executable boundary files (`#!/usr/bin/env bun` / Node), with explicit temp-dir lifecycle management
   and defensive cleanup via `rmSync(..., { force: true, recursive: true })`.
@@ -28,7 +31,8 @@
 ## Flow
 
 - `bun run build` invokes `scripts/generate-schema.ts` through `package.json#generate-schema` after type declaration generation.
-- `bun run verify:release` runs `verify-release-artifact.ts`: sanitize dist -> pack artifact -> validate files -> install/import check.
+- `bun run verify:release` runs `verify-release-artifact.ts`: sanitize dist -> pack artifact -> validate files ->
+  install/import both server and TUI entrypoints.
 - `bun run verify:host-smoke` runs `verify-opencode-host-smoke.ts`: pack tarball -> boot isolated host -> wait for health -> verify no plugin-load errors.
 - Both verification scripts are non-interactive and designed for CI/CD pre-publish gates.
 
@@ -39,5 +43,5 @@
   `dist/index.js`, `dist/cli/index.js`, and generated schema.
 - Package integrity expectations are mirrored by tests and release scripts that assert packaged skill metadata and
   runtime files are present.
-- Smoke checks instantiate the same plugin entrypoint (`dist/index.js`) that `src/index.ts` exports,
-  catching runtime breakage before publishing.
+- Smoke checks instantiate the same server and TUI entrypoints (`dist/index.js`, `dist/tui.js`) the package exports,
+  catching runtime or optional UI dependency breakage before publishing.

+ 5 - 2
scripts/verify-opencode-host-smoke.ts

@@ -15,6 +15,7 @@ import { fileURLToPath } from 'node:url';
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 const repoRoot = path.resolve(__dirname, '..');
 const distEntry = path.join(repoRoot, 'dist', 'index.js');
+const opencodeVersion = process.env.OPENCODE_SMOKE_VERSION?.trim() || 'latest';
 
 function fail(message: string): never {
   throw new Error(message);
@@ -197,8 +198,10 @@ async function verifyHostSmoke(tarballPath: string) {
       ),
     );
 
-    console.log('Installing opencode-ai into isolated test root...');
-    run('bun', ['add', 'opencode-ai@latest'], { cwd: hostDir });
+    console.log(
+      `Installing opencode-ai@${opencodeVersion} into isolated test root...`,
+    );
+    run('bun', ['add', `opencode-ai@${opencodeVersion}`], { cwd: hostDir });
 
     const opencodeBin = path.join(hostDir, 'node_modules', '.bin', 'opencode');
     if (!existsSync(opencodeBin)) {

+ 92 - 9
scripts/verify-release-artifact.ts

@@ -28,6 +28,9 @@ const packagedRequiredFiles = [
   'LICENSE',
   'dist/index.js',
   'dist/index.d.ts',
+  'dist/server/index.js',
+  'dist/tui.js',
+  'dist/tui.d.ts',
   'dist/cli/index.js',
   'oh-my-opencode-slim.schema.json',
   'src/companion/companion-manifest.json',
@@ -62,18 +65,34 @@ function run(command: string, args: string[], options: { cwd?: string } = {}) {
   return result.stdout.trim();
 }
 
-function parsePackJson(output: string) {
-  const start = output.indexOf('[');
-  const end = output.lastIndexOf(']');
+type PackEntry = {
+  filename?: string;
+  files?: Array<{ path: string }>;
+};
 
-  if (start === -1 || end === -1 || end < start) {
-    fail(`Could not locate npm pack JSON output:\n${output}`);
+function parsePackJson(output: string): PackEntry[] {
+  // npm pack --json historically emitted an array of entries; npm >= 12
+  // emits an object keyed by package name. Accept both shapes.
+  const arrayStart = output.indexOf('[');
+  const objectStart = output.indexOf('{');
+
+  if (arrayStart !== -1 && (objectStart === -1 || arrayStart < objectStart)) {
+    const end = output.lastIndexOf(']');
+    if (end === -1 || end < arrayStart) {
+      fail(`Could not locate npm pack JSON output:\n${output}`);
+    }
+    return JSON.parse(output.slice(arrayStart, end + 1)) as PackEntry[];
   }
 
-  return JSON.parse(output.slice(start, end + 1)) as Array<{
-    filename?: string;
-    files?: Array<{ path: string }>;
-  }>;
+  const end = output.lastIndexOf('}');
+  if (objectStart === -1 || end === -1 || end < objectStart) {
+    fail(`Could not locate npm pack JSON output:\n${output}`);
+  }
+  const parsed = JSON.parse(output.slice(objectStart, end + 1)) as Record<
+    string,
+    PackEntry | PackEntry[]
+  >;
+  return Object.values(parsed).flat() as PackEntry[];
 }
 
 function walkFiles(dir: string): string[] {
@@ -181,13 +200,77 @@ function verifyFreshInstall(tarballPath: string) {
       "if (pkg?.id !== 'oh-my-opencode-slim') throw new Error('default export has an unexpected plugin id');",
       "if (typeof pkg.server !== 'function') throw new Error('default export is missing a server plugin factory');",
       "if (typeof pkg.setup !== 'function') throw new Error('default export is missing a v2 setup factory');",
+      'const asyncNoop = async () => ({});',
+      'const client = new Proxy({}, {',
+      '  get(_target, property) {',
+      "    if (property === 'app') return { log: asyncNoop };",
+      "    if (property === 'session') return { abort: asyncNoop };",
+      '    return new Proxy({}, { get: () => asyncNoop });',
+      '  },',
+      '});',
+      'globalThis.fetch = async () => new Response(',
+      "  '<!doctype html><html><head><title>Release smoke</title></head><body><main><h1>Release smoke</h1><p>packaged jsdom extraction works</p></main></body></html>',",
+      "  { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } },",
+      ');',
+      'const plugin = await pkg.server({',
+      '  client,',
+      '  directory: process.cwd(),',
+      '  worktree: process.cwd(),',
+      "  serverUrl: new URL('http://127.0.0.1:4096'),",
+      '});',
+      'const webfetch = plugin?.tool?.webfetch;',
+      "if (typeof webfetch?.execute !== 'function') throw new Error('server plugin did not register webfetch');",
+      'const result = await webfetch.execute({',
+      "  url: 'https://example.com/release-smoke',",
+      "  format: 'markdown',",
+      '  timeout: 10,',
+      '  extract_main: true,',
+      "  prefer_llms_txt: 'never',",
+      '  include_metadata: false,',
+      '  save_binary: false,',
+      '}, {',
+      '  ask: async () => undefined,',
+      '  metadata: () => undefined,',
+      '  abort: new AbortController().signal,',
+      '  directory: process.cwd(),',
+      "  sessionID: 'release-smoke',",
+      '});',
+      "if (!String(result).includes('packaged jsdom extraction works')) throw new Error('packaged webfetch did not extract the expected document');",
+      'await plugin.dispose?.();',
       "console.log('package loads');",
+      "console.log('packaged webfetch constructs and extracts a document');",
       'process.exit(0);',
     ].join('\n');
     console.log('Importing installed package entrypoint...');
     run('node', ['--input-type=module', '--eval', smokeScript], {
       cwd: installDir,
     });
+
+    const tuiSmokeScript = [
+      "import pkg from 'oh-my-opencode-slim/tui';",
+      "if (pkg?.id !== 'oh-my-opencode-slim:tui') throw new Error('TUI export has an unexpected plugin id');",
+      "if (typeof pkg.tui !== 'function') throw new Error('TUI export is missing its v1 factory');",
+      "if (typeof pkg.setup !== 'function') throw new Error('TUI export is missing its v2 setup factory');",
+      "console.log('TUI package loads');",
+      'process.exit(0);',
+    ].join('\n');
+    console.log('Importing installed TUI entrypoint...');
+    run('bun', ['--eval', tuiSmokeScript], { cwd: installDir });
+
+    // v2 hosts install this package with `subpaths: ["server", ""]`; the
+    // exports map must resolve ./server to the self-contained bundle.
+    const serverSmokeScript = [
+      "import pkg from 'oh-my-opencode-slim/server';",
+      "if (pkg?.id !== 'oh-my-opencode-slim') throw new Error('server export has an unexpected plugin id');",
+      "if (typeof pkg.server !== 'function') throw new Error('server export is missing a v1 plugin factory');",
+      "if (typeof pkg.setup !== 'function') throw new Error('server export is missing a v2 setup factory');",
+      "console.log('server package loads');",
+      'process.exit(0);',
+    ].join('\n');
+    console.log('Importing installed server subpath entrypoint...');
+    run('node', ['--input-type=module', '--eval', serverSmokeScript], {
+      cwd: installDir,
+    });
   } finally {
     rmSync(tempRoot, { recursive: true, force: true });
   }

+ 102 - 0
src/admission-runtime.test.ts

@@ -0,0 +1,102 @@
+import { describe, expect, test } from 'bun:test';
+import {
+  acquireAdmissionRuntime,
+  resetAdmissionRuntimeForTests,
+} from './admission-runtime';
+
+const config = {
+  defaultConcurrency: 1,
+  providerConcurrency: {},
+  modelConcurrency: {},
+};
+
+describe('admission runtime leases', () => {
+  test('keeps state while another owner is alive', async () => {
+    resetAdmissionRuntimeForTests();
+    const firstOwner = acquireAdmissionRuntime('project', config);
+    const secondOwner = acquireAdmissionRuntime('project', config);
+    const first = firstOwner.backgroundTaskConcurrency.acquire({
+      model: 'openai/fast',
+    });
+    await first.ready;
+    first.bind('first-task');
+    const queued = firstOwner.backgroundTaskConcurrency.acquire({
+      model: 'openai/fast',
+    });
+
+    firstOwner.release();
+    await Promise.resolve();
+    expect(secondOwner.backgroundTaskConcurrency.snapshot()).toEqual({
+      active: 1,
+      queued: 1,
+    });
+
+    secondOwner.backgroundTaskConcurrency.releaseTask('first-task');
+    await queued.ready;
+    expect(secondOwner.backgroundTaskConcurrency.snapshot()).toEqual({
+      active: 1,
+      queued: 0,
+    });
+
+    secondOwner.release();
+    resetAdmissionRuntimeForTests();
+  });
+
+  test('reacquires before the next macrotask without cancelling calls', async () => {
+    resetAdmissionRuntimeForTests();
+    const firstOwner = acquireAdmissionRuntime('project', config);
+    const first = firstOwner.backgroundTaskConcurrency.acquire({
+      model: 'openai/fast',
+    });
+    await first.ready;
+    first.bind('first-task');
+    const queued = firstOwner.backgroundTaskConcurrency.acquire({
+      model: 'openai/fast',
+    });
+
+    firstOwner.release();
+    const replacement = acquireAdmissionRuntime('project', config);
+    expect(replacement.backgroundTaskConcurrency).toBe(
+      firstOwner.backgroundTaskConcurrency,
+    );
+    replacement.backgroundTaskConcurrency.releaseTask('first-task');
+    await queued.ready;
+
+    replacement.release();
+    resetAdmissionRuntimeForTests();
+  });
+
+  test('final teardown cancels pending calls and is idempotent', async () => {
+    resetAdmissionRuntimeForTests();
+    const owner = acquireAdmissionRuntime('project', config);
+    const first = owner.backgroundTaskConcurrency.acquire({
+      model: 'openai/fast',
+    });
+    await first.ready;
+    first.bind('first-task');
+    const queued = owner.backgroundTaskConcurrency.acquire({
+      model: 'openai/fast',
+    });
+    owner.pendingCallTracker.add({
+      callId: 'queued-call',
+      parentSessionId: 'parent',
+      agentType: 'explorer',
+      label: 'queued call',
+      background: true,
+      lifecycleEpoch: 0,
+      concurrencyTicket: queued,
+    });
+
+    owner.release();
+    owner.release();
+    await new Promise((resolve) => setTimeout(resolve, 0));
+    await expect(queued.ready).rejects.toThrow(
+      'Background task concurrency queue was cancelled',
+    );
+    expect(owner.backgroundTaskConcurrency.snapshot()).toEqual({
+      active: 0,
+      queued: 0,
+    });
+    resetAdmissionRuntimeForTests();
+  });
+});

+ 139 - 0
src/admission-runtime.ts

@@ -0,0 +1,139 @@
+import {
+  createPendingCallTracker,
+  type PendingCallTracker,
+} from './hooks/task-session-manager/pending-call-tracker';
+import {
+  BackgroundTaskConcurrency,
+  type BackgroundTaskConcurrencyConfig,
+} from './utils/background-task-concurrency';
+
+export interface AdmissionRuntime {
+  readonly backgroundTaskConcurrency: BackgroundTaskConcurrency;
+  readonly pendingCallTracker: PendingCallTracker;
+}
+
+export interface AdmissionRuntimeLease extends AdmissionRuntime {
+  release(): void;
+}
+
+interface RuntimeEntry {
+  runtime: AdmissionRuntime;
+  owners: number;
+  teardownTimer?: ReturnType<typeof setTimeout>;
+}
+
+const runtimesByDirectory = new Map<string, RuntimeEntry>();
+const compatibilityLeases = new Map<string, AdmissionRuntimeLease>();
+
+function directoryKey(directory: string): string {
+  return directory || 'default';
+}
+
+function disposeEntry(key: string, entry: RuntimeEntry): void {
+  if (runtimesByDirectory.get(key) !== entry || entry.owners !== 0) {
+    return;
+  }
+  entry.runtime.pendingCallTracker.clearAll();
+  entry.runtime.backgroundTaskConcurrency.dispose();
+  runtimesByDirectory.delete(key);
+}
+
+/**
+ * Acquire the per-directory admission runtime.
+ *
+ * Plugin generations share this runtime while they overlap. Releasing the
+ * last generation is intentionally delayed by one macrotask: OpenCode
+ * disposes and recreates a plugin during config updates, and the new factory
+ * must be able to reclaim the scheduler and pending calls before teardown.
+ */
+export function acquireAdmissionRuntime(
+  directory: string,
+  config: BackgroundTaskConcurrencyConfig,
+): AdmissionRuntimeLease {
+  const key = directoryKey(directory);
+  let entry = runtimesByDirectory.get(key);
+
+  if (entry?.runtime.backgroundTaskConcurrency.isDisposed()) {
+    if (entry.teardownTimer !== undefined) {
+      clearTimeout(entry.teardownTimer);
+    }
+    entry.runtime.pendingCallTracker.clearAll();
+    runtimesByDirectory.delete(key);
+    entry = undefined;
+  }
+
+  if (!entry) {
+    entry = {
+      runtime: {
+        backgroundTaskConcurrency: new BackgroundTaskConcurrency(config),
+        pendingCallTracker: createPendingCallTracker(),
+      },
+      owners: 0,
+    };
+    runtimesByDirectory.set(key, entry);
+  } else {
+    entry.runtime.backgroundTaskConcurrency.updateConfig(config);
+  }
+
+  if (entry.teardownTimer !== undefined) {
+    clearTimeout(entry.teardownTimer);
+    entry.teardownTimer = undefined;
+  }
+  entry.owners += 1;
+  const ownedEntry = entry;
+
+  let released = false;
+  return {
+    ...ownedEntry.runtime,
+    release: () => {
+      if (released) return;
+      released = true;
+      if (ownedEntry.owners > 0) ownedEntry.owners -= 1;
+      if (ownedEntry.owners !== 0 || ownedEntry.teardownTimer !== undefined) {
+        return;
+      }
+      ownedEntry.teardownTimer = setTimeout(() => {
+        ownedEntry.teardownTimer = undefined;
+        disposeEntry(key, ownedEntry);
+      }, 0);
+    },
+  };
+}
+
+/** Test seam: synchronously dispose every directory runtime. */
+export function resetAdmissionRuntimeForTests(): void {
+  compatibilityLeases.clear();
+  for (const [key, entry] of runtimesByDirectory) {
+    if (entry.teardownTimer !== undefined) {
+      clearTimeout(entry.teardownTimer);
+    }
+    entry.owners = 0;
+    disposeEntry(key, entry);
+  }
+  runtimesByDirectory.clear();
+}
+
+/**
+ * Legacy test helper. Production code must acquire and release a lease so a
+ * plugin generation cannot keep a directory runtime alive accidentally.
+ */
+export function getBackgroundTaskConcurrency(
+  directory: string,
+  config: BackgroundTaskConcurrencyConfig,
+): BackgroundTaskConcurrency {
+  const key = directoryKey(directory);
+  const existing = compatibilityLeases.get(key);
+  if (existing && !existing.backgroundTaskConcurrency.isDisposed()) {
+    existing.backgroundTaskConcurrency.updateConfig(config);
+    return existing.backgroundTaskConcurrency;
+  }
+  existing?.release();
+  const lease = acquireAdmissionRuntime(directory, config);
+  compatibilityLeases.set(key, lease);
+  return lease.backgroundTaskConcurrency;
+}
+
+/** Legacy test helper paired with getBackgroundTaskConcurrency. */
+export function resetBackgroundTaskConcurrencyForTests(): void {
+  resetAdmissionRuntimeForTests();
+}

+ 10 - 10
src/agents/codemap.md

@@ -26,8 +26,9 @@ Each agent is a **prompt-driven specialist** with a factory function that create
 
 - **Default prompts**: Each agent factory has a base prompt defined in its file (e.g., `explorer.ts`, `oracle.ts`)
 - **User overrides**: From `~/.config/opencode/oh-my-opencode-slim.json` via `loadAgentPrompt()`
+- **Agent colors**: Optional per-agent hex or theme-color overrides; no defaults (colorless agents get the host TUI's distinct palette colors)
 - **Permission wildcards**: Applied via `applyDefaultPermissions()` in `index.ts`
-- **Model resolution**: Supports both string models and priority-ordered arrays (`_modelArray`) for runtime fallback
+- **Model resolution**: Supports string models, explicit `inheritModelFrom` policies, and priority-ordered arrays (`_modelArray`) for runtime fallback
 - **Skill permissions**: Per-agent MCP and tool access controlled via `getSkillPermissionsForAgent()`
 
 ### Agent Lifecycle
@@ -36,7 +37,7 @@ Each agent is a **prompt-driven specialist** with a factory function that create
 2. **Dynamic councillors**: `buildCouncillorAgents()` (`council-agents.ts`) creates one `councillor-<name>` subagent per council preset seat, attaching `_modelArray` fallback chains for multi-model councillors
 3. **Permission application**: `applyDefaultPermissions()` sets read/write permissions based on agent type
 4. **Task-rejection instruction**: `appendTaskRejectionInstruction()` appends the "outside your role" instruction to specialist prompts (`task-rejection.ts`)
-5. **Display name injection**: Orchestrator prompt rewrites `@agent` mentions to user-configured display names
+5. **Routing finalization**: Routing entries are built from the actual resolved agent definitions, with display names and custom/ACP guidance applied before sorting and orchestrator construction
 6. **Configuration export**: `getAgentConfigs()` converts `AgentDefinition` to OpenCode SDK format with classification metadata
 
 ## Flow
@@ -69,13 +70,11 @@ const orchestrator = createOrchestratorAgent(
 );
 applyDefaultPermissions(orchestrator, orchestratorOverride?.skills, config?.disabled_skills);
 
-// 4. Collect display names and inject into orchestrator prompt
-const displayNameMap = new Map<string, string>();
-// ... populate from orchestrator and all subagents ...
-injectDisplayNames(orchestrator, displayNameMap);
-
-// 5. Inject council-dispatch instructions when dynamic councillors exist
-// 6. Return agents array [orchestrator, ...allSubAgents]
+// 4. Build deterministic routes from the actual resolved subagents
+const routing = buildRoutingEntriesForResolvedAgents(runtime, allSubAgents);
+// 5. Construct the orchestrator from those finalized routes
+// 6. Inject council-dispatch instructions when dynamic councillors exist
+// 7. Return agents array [orchestrator, ...allSubAgents]
 return [orchestrator, ...allSubAgents];
 ```
 
@@ -120,6 +119,7 @@ export function getAgentConfigs(config?: PluginConfig): Record<string, SDKAgentC
 ### Model Resolution and Fallback
 
 - **Priority arrays**: When `model` is configured as an array in user config, it's stored as `_modelArray`
+- **Explicit inheritance**: `inheritModelFrom: "session"` leaves the agent model unset so OpenCode uses the parent session model; `"orchestrator"` follows the model resolved during configuration, not later runtime fallback
 - **Runtime fallback**: ForegroundFallbackManager resolves models at runtime when API errors occur
 - **Preset overrides**: Runtime presets can override model/variant/temperature per agent
 
@@ -192,4 +192,4 @@ These rules are filtered based on disabled agents and injected into the orchestr
 - **Strategy Pattern**: Different agents implement different strategies for different tasks
 - **Decorator Pattern**: Configuration decorators (overrides, permissions, display names) wrap agent definitions
 - **Observer Pattern**: Session tracking via `sessionAgentMap` and event handlers
-- **Chain of Responsibility**: Task delegation flows from orchestrator to specialists
+- **Chain of Responsibility**: Task delegation flows from orchestrator to specialists

+ 8 - 9
src/agents/council.ts

@@ -4,7 +4,7 @@ import { createSynthesisOnlyPermission } from './permissions';
 // NOTE: Councillor system prompts live in the councillor agent factory.
 // The council agent synthesizes councillor responses passed by the orchestrator.
 
-const COUNCIL_SYNTHESIS_REINFORCEMENT = `\n\n---\n\nYou MUST follow the Synthesis Process steps before producing output: review each councillor response individually by name, then produce the required output with a synthesized Council Response, a Per-Councillor Details section using each councillor's exact seat name (e.g. "alpha", not the model label), and a Council Summary with Consensus Level (unanimous|majority|split), Agreed Points, Disagreements + resolution, Remaining Uncertainty, and Recommended Action.`;
+export const COUNCIL_SYNTHESIS_REINFORCEMENT = `\n\n---\n\nYou MUST follow the Synthesis Process steps before producing output: review each councillor response individually by name, then produce the required output with a synthesized Council Response, a Per-Councillor Details section using each councillor's exact seat name (e.g. "alpha", not the model label), and a Council Summary with Consensus Level (unanimous|majority|split), Agreed Points, Disagreements + resolution, Remaining Uncertainty, and Recommended Action.`;
 
 const COUNCIL_AGENT_PROMPT = `You are the Council agent - a \
 synthesizer for multi-model consensus.
@@ -62,14 +62,13 @@ export function createCouncilAgent(
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  const prompt =
-    resolvePrompt(
-      'council',
-      customPrompt,
-      undefined,
-      COUNCIL_AGENT_PROMPT,
-      customAppendPrompt,
-    ) + COUNCIL_SYNTHESIS_REINFORCEMENT;
+  const prompt = resolvePrompt(
+    'council',
+    customPrompt,
+    undefined,
+    COUNCIL_AGENT_PROMPT,
+    customAppendPrompt,
+  );
 
   return {
     name: 'council',

+ 52 - 20
src/agents/custom.test.ts

@@ -1,7 +1,11 @@
 import { describe, expect, spyOn, test } from 'bun:test';
 import { DEFAULT_MODELS, type PluginConfig } from '../config';
 import { RuntimeConfig } from '../config/runtime';
-import { createAgents, getAgentConfigs } from './index';
+import {
+  buildResolvedAgentRegistry,
+  createAgents,
+  getAgentConfigs,
+} from './index';
 
 const TEST_DIRECTORY = 'runtime-test-agents-custom';
 function runtimeFor(config: PluginConfig | undefined = {}) {
@@ -31,7 +35,7 @@ describe('custom-agent creation', () => {
     expect(customAgent).toBeDefined();
     expect(customAgent?.config.model).toBe('openai/gpt-5.6');
     expect(customAgent?.config.prompt).toBe(
-      'You are the custom reviewer agent.',
+      'You are the custom reviewer agent.\n\nIf a task is outside your role, do not attempt partial work. Return a brief reason to the orchestrator.',
     );
   });
 
@@ -52,7 +56,7 @@ describe('custom-agent creation', () => {
 
     expect(customAgent).toBeDefined();
     expect(customAgent?.config.prompt).toBe(
-      'You are a custom subagent for auditing.',
+      'You are a custom subagent for auditing.\n\nIf a task is outside your role, do not attempt partial work. Return a brief reason to the orchestrator.',
     );
 
     const orchestrator = agents.find((agent) => agent.name === 'orchestrator');
@@ -134,6 +138,34 @@ describe('custom-agent creation', () => {
     );
   });
 
+  test('role-derived custom agents inherit the canonical role policy', () => {
+    const config: PluginConfig = {
+      agents: {
+        'oracle-derived': {
+          baseRole: 'oracle',
+          model: 'provider/oracle-derived',
+        },
+        'librarian-derived': {
+          baseRole: 'librarian',
+          model: 'provider/librarian-derived',
+        },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const derived = agents.find((agent) => agent.name === 'oracle-derived');
+
+    expect(derived?.baseRole).toBe('oracle');
+    expect(derived?.config.prompt).toContain('You are Oracle');
+    expect(derived?.description).toContain('Strategic technical advisor');
+    const registry = buildResolvedAgentRegistry(runtimeFor(config));
+    expect(registry.mcpLists['oracle-derived']).toEqual([]);
+    expect(registry.skillPermissions['oracle-derived']?.simplify).toBe('allow');
+    expect(registry.mcpLists['librarian-derived']).toEqual([
+      'context7',
+      'gh_grep',
+    ]);
+  });
+
   test('creates wrapper agents from acpAgents config', () => {
     const config: PluginConfig = {
       acpAgents: {
@@ -165,7 +197,9 @@ describe('custom-agent creation', () => {
       preset: 'opencode-go',
       presets: {
         'opencode-go': {
-          orchestrator: { model: 'opencode-go/glm-5.2' },
+          agents: {
+            orchestrator: { model: 'opencode-go/glm-5.2' },
+          },
         },
       },
       agents: {
@@ -261,7 +295,7 @@ describe('custom-agent creation', () => {
     );
   });
 
-  test('appends ACP routing prompts separately without heading, and preserves rewriting', () => {
+  test('folds custom and ACP routing prompts into their route entries', () => {
     const config: PluginConfig = {
       agents: {
         explorer: {
@@ -291,19 +325,16 @@ describe('custom-agent creation', () => {
     const orchestrator = agents.find((agent) => agent.name === 'orchestrator');
     const prompt = orchestrator?.config.prompt ?? '';
 
-    // Verify Project-specific routing guidance exists and has the custom agent override prompt rewritten
-    expect(prompt).toContain('# Project-specific routing guidance');
+    // Both guidance blocks are part of their agent's single route entry.
+    expect(prompt).not.toContain('# Project-specific routing guidance');
     expect(prompt).toContain(
       'Please use @janitor to clean up after @fancy-explorer has completed.',
     );
 
-    // Verify ACP routing prompt is appended but NOT under the Project-specific routing guidance section
+    // Verify ACP routing guidance is embedded in its route entry.
     // (i.e. it comes after or is separate, let's verify exact substring sequence or that the ACP test isn't inside the heading section)
-    const pieces = prompt.split('# Project-specific routing guidance');
-    expect(pieces.length).toBe(2);
-
-    const headingContent = pieces[1];
-    // The headingContent should contain the custom prompt but not contain the ACP prompt if ACP prompt is appended after/before.
+    expect(prompt.match(/Please use @janitor/g)?.length).toBe(1);
+    // Both guidance snippets should be present exactly once in the prompt.
     // Wait, in our implementation, order of appending is:
     // 1) overridden/custom prompts under # Project-specific routing guidance.
     // 2) ACP routing prompts (without the header).
@@ -311,12 +342,14 @@ describe('custom-agent creation', () => {
     // But the ACP prompt is not under that header's specific block if it's appended separately.
     // Wait, is there a way to verify they are separated? Yes, we can verify that the custom prompt block and ACP prompt block are two separate parts,
     // and that ACP prompt is appended at the very end of the string, outside of the heading's contiguous text block or that if only ACP is present, the heading doesn't show up.
-    expect(headingContent).toContain(
-      'Please use @janitor to clean up after @fancy-explorer has completed.',
-    );
-    expect(headingContent).toContain(
+    expect(prompt).toContain(
       'Please delegate research tasks to @claude-research or @fancy-explorer.',
     );
+    expect(
+      prompt.match(
+        /Please delegate research tasks to @claude-research or @fancy-explorer\./g,
+      )?.length,
+    ).toBe(1);
 
     // Let's also check a scenario where only ACP routing prompt is present. There should be NO heading at all!
     const configOnlyAcp: PluginConfig = {
@@ -476,9 +509,8 @@ describe('permission edge cases', () => {
     const planner = agents.find((a) => a.name === 'planner');
 
     expect(planner).toBeDefined();
-    // The shorthand string should be preserved as-is, not spread into
-    // character keys like { "0": "a", "1": "s", "2": "k" }
-    expect(planner?.config.permission).toBe('ask');
+    // Shorthand permissions are normalized before defaults and gates apply.
+    expect(planner?.config.permission).toMatchObject({ '*': 'ask' });
   });
 
   test('orchestrator permission override does not replace plugin gates', () => {

+ 7 - 82
src/agents/designer.ts

@@ -1,90 +1,15 @@
-import { WRITABLE_FILE_OPERATIONS_RULES } from '../config';
 import type { AgentDefinition } from './orchestrator';
-
-const DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates and reviews intentional, polished experiences.
-
-**Role**: Craft and review cohesive UI/UX that balances visual impact with usability.
-
-## Design Principles
-
-**Typography**
-- Choose distinctive, characterful fonts that elevate aesthetics
-- Avoid generic defaults (Arial, Inter)-opt for unexpected, beautiful choices
-- Pair display fonts with refined body fonts for hierarchy
-
-**Color & Theme**
-- Commit to a cohesive aesthetic with clear color variables
-- Dominant colors with sharp accents > timid, evenly-distributed palettes
-- Create atmosphere through intentional color relationships
-
-**Motion & Interaction**
-- Leverage framework animation utilities when available (Tailwind's transition/animation classes)
-- Focus on high-impact moments: orchestrated page loads with staggered reveals
-- Use scroll-triggers and hover states that surprise and delight
-- One well-timed animation > scattered micro-interactions
-- Drop to custom CSS/JS only when utilities can't achieve the vision
-
-**Spatial Composition**
-- Break conventions: asymmetry, overlap, diagonal flow, grid-breaking
-- Generous negative space OR controlled density-commit to the choice
-- Unexpected layouts that guide the eye
-
-**Visual Depth**
-- Create atmosphere beyond solid colors: gradient meshes, noise textures, geometric patterns
-- Layer transparencies, dramatic shadows, decorative borders
-- Contextual effects that match the aesthetic (grain overlays, custom cursors)
-
-**Styling Approach**
-- Default to Tailwind CSS utility classes when available-fast, maintainable, consistent
-- Use custom CSS when the vision requires it: complex animations, unique effects, advanced compositions
-- Balance utility-first speed with creative freedom where it matters
-
-**Match Vision to Execution**
-- Maximalist designs → elaborate implementation, extensive animations, rich effects
-- Minimalist designs → restraint, precision, careful spacing and typography
-- Elegance comes from executing the chosen vision fully, not halfway
-
-## Constraints
-- Respect existing design systems when present
-- Leverage component libraries where available
-- Prioritize visual excellence-code perfection comes second
-- Use grounded, normal, regular english - don't use jargon or overly technical language
-
-${WRITABLE_FILE_OPERATIONS_RULES}
-
-## Review Responsibilities
-- Review existing UI for usability, responsiveness, visual consistency, and polish when asked
-- Call out concrete UX issues and improvements, not just abstract design advice
-
-## Verification
-- Run only validation assigned by the Orchestrator; do not broaden it
-  automatically.
-- Report validation results and skips accurately.
-- Assigned validation should be user-visible.
-
-## Output Quality
-You're capable of extraordinary creative work. Commit fully to distinctive visions and show what's possible when breaking conventions thoughtfully.`;
+import { createRoleAgent, ROLE_DEFINITIONS } from './role-definitions';
 
 export function createDesignerAgent(
   model: string,
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  let prompt = DESIGNER_PROMPT;
-
-  if (customPrompt) {
-    prompt = customPrompt;
-  } else if (customAppendPrompt) {
-    prompt = `${DESIGNER_PROMPT}\n\n${customAppendPrompt}`;
-  }
-
-  return {
-    name: 'designer',
-    description:
-      'UI/UX design, review, and implementation. Use for styling, responsive design, component architecture and visual polish.',
-    config: {
-      model,
-      prompt,
-    },
-  };
+  return createRoleAgent(
+    ROLE_DEFINITIONS.designer,
+    model,
+    customPrompt,
+    customAppendPrompt,
+  );
 }

+ 3 - 0
src/agents/display-name.test.ts

@@ -208,6 +208,9 @@ describe('displayName', () => {
   test('keeps internal-only council agents hidden even with displayName configured', () => {
     const config: PluginConfig = {
       disabled_agents: [],
+      council: CouncilConfigSchema.parse({
+        presets: { default: { alpha: { model: 'test/councillor' } } },
+      }),
       agents: {
         councillor: { displayName: 'reviewer' },
       },

+ 7 - 50
src/agents/explorer.ts

@@ -1,58 +1,15 @@
-import { READONLY_FILE_OPERATIONS_RULES } from '../config';
 import type { AgentDefinition } from './orchestrator';
-
-const EXPLORER_PROMPT = `You are Explorer - a fast codebase navigation specialist.
-
-**Role**: Quick contextual grep for codebases. Answer "Where is X?", "Find Y", "Which file has Z".
-
-**When to use which tools**:
-- **Text/regex patterns** (strings, comments, variable names): grep
-- **Structural patterns** (function shapes, class structures): ast_grep_search
-- **File discovery** (find by name/extension): glob
-
-${READONLY_FILE_OPERATIONS_RULES}
-
-**Behavior**:
-- Be fast and thorough
-- Fire multiple searches in parallel if needed
-- Return file paths with relevant snippets
-
-**Output Format**:
-<results>
-<files>
-- /path/to/file.ts:42 - Brief description of what's there
-</files>
-<answer>
-Concise answer to the question
-</answer>
-</results>
-
-**Constraints**:
-- READ-ONLY: Search and report, don't modify
-- Be exhaustive but concise
-- Include line numbers when relevant
-`;
+import { createRoleAgent, ROLE_DEFINITIONS } from './role-definitions';
 
 export function createExplorerAgent(
   model: string,
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  let prompt = EXPLORER_PROMPT;
-
-  if (customPrompt) {
-    prompt = customPrompt;
-  } else if (customAppendPrompt) {
-    prompt = `${EXPLORER_PROMPT}\n\n${customAppendPrompt}`;
-  }
-
-  return {
-    name: 'explorer',
-    description:
-      "Fast codebase search and pattern matching. Use for finding files, locating code patterns, and answering 'where is X?' questions.",
-    config: {
-      model,
-      prompt,
-    },
-  };
+  return createRoleAgent(
+    ROLE_DEFINITIONS.explorer,
+    model,
+    customPrompt,
+    customAppendPrompt,
+  );
 }

+ 7 - 57
src/agents/fixer.ts

@@ -1,65 +1,15 @@
-import { WRITABLE_FILE_OPERATIONS_RULES } from '../config';
 import type { AgentDefinition } from './orchestrator';
-
-const FIXER_PROMPT = `You are Fixer - a fast, focused implementation specialist.
-
-**Role**: Execute code changes efficiently. You receive complete context from research agents and clear task specifications from the Orchestrator. Your job is to implement, not plan or research.
-
-**Behavior**:
-- Execute the task specification provided by the Orchestrator
-- Report completion with summary of changes
-
-${WRITABLE_FILE_OPERATIONS_RULES}
-
-**Constraints**:
-- NO external research (no context7, gh_grep)
-- NO spawning subagents; telling the caller which specialist to use is fine
-- No multi-step research/planning; minimal execution sequence ok
-- If context is insufficient: use grep/glob/read directly - do not delegate
-- Only ask for missing inputs you truly cannot retrieve yourself
-- Do not act as the primary reviewer; implement requested changes and surface obvious issues briefly
-- No design work — layout, styling, visual hierarchy, responsive behavior, animation, component feel. Refuse and tell the caller to use @designer.
-
-**Verification**:
-- Run only validation assigned by the Orchestrator; do not broaden it
-  automatically.
-- Report validation results and skips accurately.
-
-**Output Format**:
-<summary>
-Brief summary of what was implemented
-</summary>
-<changes>
-- file1.ts: Changed X to Y
-- file2.ts: Added Z function
-</changes>
-<verification>
-- Performed: [command/check, or skipped with reason]
-- Result: [passed/failed/unknown]
-</verification>
-
-`;
+import { createRoleAgent, ROLE_DEFINITIONS } from './role-definitions';
 
 export function createFixerAgent(
   model: string,
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  let prompt = FIXER_PROMPT;
-
-  if (customPrompt) {
-    prompt = customPrompt;
-  } else if (customAppendPrompt) {
-    prompt = `${FIXER_PROMPT}\n\n${customAppendPrompt}`;
-  }
-
-  return {
-    name: 'fixer',
-    description:
-      'Fast implementation specialist. Receives complete context and task spec, executes code changes efficiently.',
-    config: {
-      model,
-      prompt,
-    },
-  };
+  return createRoleAgent(
+    ROLE_DEFINITIONS.fixer,
+    model,
+    customPrompt,
+    customAppendPrompt,
+  );
 }

+ 702 - 23
src/agents/index.test.ts

@@ -2,6 +2,7 @@ import { describe, expect, test } from 'bun:test';
 import type { PluginConfig } from '../config';
 import {
   AgentOverrideConfigSchema,
+  ALL_AGENT_NAMES,
   CouncilConfigSchema,
   DEFAULT_DISABLED_AGENTS,
   DEFAULT_MODELS,
@@ -9,11 +10,15 @@ import {
   SUBAGENT_NAMES,
 } from '../config';
 import { RuntimeConfig } from '../config/runtime';
+import { COUNCIL_SYNTHESIS_REINFORCEMENT } from './council';
 import {
+  applyModelInheritanceToConfig,
   createAgents,
   getAgentConfigs,
   getDisabledAgents,
+  isMarketplacePermissionDenied,
   isSubagent,
+  resolveAgentConfigModel,
 } from './index';
 import { TASK_REJECTION_INSTRUCTION } from './task-rejection';
 
@@ -113,7 +118,9 @@ describe('built-in subagent preset fallback', () => {
       preset: 'opencode-go',
       presets: {
         'opencode-go': {
-          orchestrator: { model: 'opencode-go/glm-5.2' },
+          agents: {
+            orchestrator: { model: 'opencode-go/glm-5.2' },
+          },
         },
       },
       council: councilConfig(),
@@ -134,7 +141,9 @@ describe('built-in subagent preset fallback', () => {
       preset: 'minimal',
       presets: {
         minimal: {
-          oracle: { model: 'anthropic/claude-sonnet-4-6' },
+          agents: {
+            oracle: { model: 'anthropic/claude-sonnet-4-6' },
+          },
         },
       },
       agents: {
@@ -162,6 +171,114 @@ describe('fixer agent fallback', () => {
     expect(fixer?.config.model).toBe(librarian?.config.model);
   });
 
+  test('fixer can follow the session model independently of librarian', () => {
+    const config: PluginConfig = {
+      preset: 'balanced',
+      presets: {
+        balanced: {
+          agents: {
+            orchestrator: { model: 'orchestrator-model' },
+          },
+        },
+      },
+      agents: {
+        librarian: { model: 'librarian-local-model' },
+        fixer: { inheritModelFrom: 'session' },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const fixer = agents.find((a) => a.name === 'fixer');
+    const librarian = agents.find((a) => a.name === 'librarian');
+
+    expect(librarian?.config.model).toBe('librarian-local-model');
+    expect(fixer?.config.model).toBeUndefined();
+  });
+
+  test('librarian can follow the orchestrator model independently of fixer', () => {
+    const config: PluginConfig = {
+      preset: 'balanced',
+      presets: {
+        balanced: {
+          agents: {
+            orchestrator: { model: 'orchestrator-model' },
+          },
+        },
+      },
+      agents: {
+        librarian: { inheritModelFrom: 'orchestrator' },
+        fixer: { model: 'fixer-local-model' },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const librarian = agents.find((a) => a.name === 'librarian');
+    const fixer = agents.find((a) => a.name === 'fixer');
+
+    expect(librarian?.config.model).toBe('orchestrator-model');
+    expect(fixer?.config.model).toBe('fixer-local-model');
+  });
+
+  test('model inheritance works when configured inside a preset', () => {
+    const config: PluginConfig = {
+      preset: 'split',
+      presets: {
+        split: {
+          agents: {
+            orchestrator: { model: 'orchestrator-model' },
+            librarian: { model: 'librarian-local-model' },
+            fixer: { inheritModelFrom: 'session' },
+          },
+        },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const fixer = agents.find((a) => a.name === 'fixer');
+    const librarian = agents.find((a) => a.name === 'librarian');
+
+    expect(librarian?.config.model).toBe('librarian-local-model');
+    expect(fixer?.config.model).toBeUndefined();
+  });
+
+  test('root inheritance clears a preset model for the same agent', () => {
+    const config: PluginConfig = {
+      preset: 'split',
+      presets: {
+        split: {
+          agents: {
+            orchestrator: { model: 'orchestrator-model' },
+            fixer: { model: 'preset-fixer-model' },
+          },
+        },
+      },
+      agents: {
+        fixer: { inheritModelFrom: 'session' },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const fixer = agents.find((a) => a.name === 'fixer');
+
+    expect(fixer?.config.model).toBeUndefined();
+  });
+
+  test('legacy alias inheritance clears a canonical lower-layer model', () => {
+    const config: PluginConfig = {
+      preset: 'split',
+      presets: {
+        split: {
+          agents: {
+            explorer: { model: 'preset/explorer' },
+          },
+        },
+      },
+      agents: {
+        explore: { inheritModelFrom: 'session' },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const explorer = agents.find((a) => a.name === 'explorer');
+
+    expect(explorer?.config.model).toBeUndefined();
+  });
+
   test('fixer uses its own model when explicitly configured', () => {
     const config: PluginConfig = {
       agents: {
@@ -173,6 +290,85 @@ describe('fixer agent fallback', () => {
     const fixer = agents.find((a) => a.name === 'fixer');
     expect(fixer?.config.model).toBe('fixer-specific-model');
   });
+
+  test('explicit fixer model takes precedence over inheritance policy', () => {
+    const config: PluginConfig = {
+      agents: {
+        librarian: { model: 'librarian-model' },
+        fixer: {
+          model: 'fixer-specific-model',
+          inheritModelFrom: 'session',
+        },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const fixer = agents.find((a) => a.name === 'fixer');
+
+    expect(fixer?.config.model).toBe('fixer-specific-model');
+  });
+
+  test('custom agents can follow the session model', () => {
+    const config: PluginConfig = {
+      agents: {
+        reviewer: { inheritModelFrom: 'session' },
+      },
+    };
+    const agents = createAgents(runtimeFor(config));
+    const reviewer = agents.find((a) => a.name === 'reviewer');
+
+    expect(reviewer).toBeDefined();
+    expect(reviewer?.config.model).toBeUndefined();
+  });
+
+  test('session inheritance clears a stale host model after config merging', () => {
+    const runtime = runtimeFor({
+      agents: {
+        librarian: { model: 'librarian-local-model' },
+        fixer: { inheritModelFrom: 'session' },
+      },
+    });
+    const configAgent: Record<string, unknown> = {
+      fixer: { model: 'stale-host-model', temperature: 0.2 },
+    };
+
+    applyModelInheritanceToConfig(configAgent, runtime);
+
+    expect(configAgent.fixer).toEqual({ temperature: 0.2 });
+  });
+
+  test('orchestrator inheritance replaces a stale host model', () => {
+    const runtime = runtimeFor({
+      agents: {
+        orchestrator: { model: 'orchestrator-model' },
+        librarian: { inheritModelFrom: 'orchestrator' },
+      },
+    });
+    const configAgent: Record<string, unknown> = {
+      librarian: { model: 'stale-host-model' },
+    };
+
+    applyModelInheritanceToConfig(configAgent, runtime);
+
+    expect(configAgent.librarian).toEqual({ model: 'orchestrator-model' });
+  });
+
+  test('orchestrator inheritance follows the host orchestrator model', () => {
+    const runtime = runtimeFor({
+      agents: {
+        librarian: { inheritModelFrom: 'orchestrator' },
+      },
+    });
+    runtime.captureHostConfig({
+      agent: { orchestrator: { model: 'host-orchestrator-model' } },
+    });
+    const configAgent: Record<string, unknown> = {
+      librarian: { model: 'stale-host-model' },
+    };
+
+    applyModelInheritanceToConfig(configAgent, runtime);
+
+    expect(configAgent.librarian).toEqual({ model: 'host-orchestrator-model' });
+  });
 });
 
 describe('orchestrator agent', () => {
@@ -218,6 +414,15 @@ describe('orchestrator agent', () => {
     ).toBe('allow');
   });
 
+  test('orchestrator is allowed to invoke marketplace', () => {
+    const agents = createAgents(runtimeFor());
+    const orchestrator = agents.find((a) => a.name === 'orchestrator');
+    expect(
+      (orchestrator as { config: { permission: Record<string, unknown> } })
+        .config.permission.marketplace,
+    ).toBe('allow');
+  });
+
   test('orchestrator accepts overrides', () => {
     const config: PluginConfig = {
       agents: {
@@ -354,6 +559,128 @@ describe('per-model variant in array config', () => {
   });
 });
 
+describe('spaced model ID registrations', () => {
+  const primary = 'opencode-omniroute-live/of/MiniMax M3';
+  const secondary = 'of/Kimi K2.6';
+  const fallback = 'opencode-omniroute-live/of/Qwen3.8 27b';
+
+  test('preserves named-preset arrays for built-in and custom subagents', () => {
+    const config = PluginConfigSchema.parse({
+      preset: 'spaced',
+      presets: {
+        spaced: {
+          agents: {
+            explorer: {
+              model: [
+                { id: primary, variant: 'fast' },
+                { id: secondary, variant: 'balanced' },
+              ],
+            },
+            librarian: { model: primary, variant: 'direct' },
+            reviewer: {
+              model: [
+                { id: secondary, variant: 'precise' },
+                { id: fallback, variant: 'economy' },
+              ],
+            },
+          },
+        },
+      },
+      // Declaring the custom agent at the root lets the named preset supply
+      // its model plan while retaining the custom agent registration.
+      agents: { reviewer: { temperature: 0.2 } },
+    });
+    const runtime = runtimeFor(config);
+    const agents = createAgents(runtime);
+    const configs = getAgentConfigs(runtime);
+
+    expect(runtime.agent('explorer')?.model).toEqual([
+      { id: primary, variant: 'fast' },
+      { id: secondary, variant: 'balanced' },
+    ]);
+    expect(runtime.agent('reviewer')?.model).toEqual([
+      { id: secondary, variant: 'precise' },
+      { id: fallback, variant: 'economy' },
+    ]);
+    expect(runtime.agent('librarian')).toMatchObject({
+      model: primary,
+      variant: 'direct',
+    });
+
+    expect(
+      agents.find((agent) => agent.name === 'explorer')?._modelArray,
+    ).toEqual([
+      { id: primary, variant: 'fast' },
+      { id: secondary, variant: 'balanced' },
+    ]);
+    expect(
+      agents.find((agent) => agent.name === 'reviewer')?._modelArray,
+    ).toEqual([
+      { id: secondary, variant: 'precise' },
+      { id: fallback, variant: 'economy' },
+    ]);
+    expect(configs.explorer).toMatchObject({
+      model: primary,
+      variant: 'fast',
+      mode: 'subagent',
+    });
+    expect(configs.librarian).toMatchObject({
+      model: primary,
+      variant: 'direct',
+      mode: 'subagent',
+    });
+    expect(configs.reviewer).toMatchObject({
+      model: secondary,
+      variant: 'precise',
+      mode: 'subagent',
+    });
+  });
+
+  test('registers parsed council seats and ACP wrappers with spaced model IDs', () => {
+    const config = PluginConfigSchema.parse({
+      council: {
+        default_preset: 'spaced',
+        presets: {
+          spaced: {
+            alpha: {
+              model: [
+                { id: primary, variant: 'reasoning' },
+                { id: secondary, variant: 'fast' },
+              ],
+            },
+          },
+        },
+      },
+      acpAgents: {
+        bridge: {
+          command: 'bridge-acp',
+          wrapperModel: fallback,
+        },
+      },
+    });
+    const runtime = runtimeFor(config);
+    const agents = createAgents(runtime);
+    const configs = getAgentConfigs(runtime);
+    const councillor = agents.find(
+      (agent) => agent.name === 'councillor-alpha',
+    );
+
+    expect(councillor?._modelArray).toEqual([
+      { id: primary, variant: 'reasoning' },
+      { id: secondary, variant: 'fast' },
+    ]);
+    expect(councillor?.config.model).toBeUndefined();
+    expect(configs['councillor-alpha']).toMatchObject({
+      mode: 'subagent',
+      hidden: true,
+    });
+    expect(configs.bridge).toMatchObject({
+      model: fallback,
+      mode: 'subagent',
+    });
+  });
+});
+
 describe('skill permissions', () => {
   test('orchestrator gets command-style bundled skills allowed by default', () => {
     const agents = createAgents(runtimeFor());
@@ -452,6 +779,122 @@ describe('tool permissions', () => {
     }
   });
 
+  test('subagents are denied access to marketplace', () => {
+    const agents = createAgents(runtimeFor());
+
+    for (const name of ['oracle', 'explorer', 'fixer']) {
+      const agent = agents.find((candidate) => candidate.name === name);
+      expect(
+        (agent as { config: { permission: Record<string, unknown> } }).config
+          .permission.marketplace,
+      ).toBe('deny');
+    }
+  });
+
+  test('hostile marketplace allow rules cannot open the tool on non-orchestrators', () => {
+    const agents = createAgents(
+      runtimeFor({
+        disabled_agents: [],
+        agents: {
+          explorer: {
+            displayName: 'Scout',
+            permission: { marketplace: 'allow' },
+          },
+          oracle: { permission: { marketplace: 'allow' } },
+          researcher: {
+            baseRole: 'librarian',
+            model: 'test/researcher',
+            permission: { marketplace: 'allow' },
+          },
+        },
+        council: councilConfig(),
+      }),
+    );
+    for (const name of [
+      'explorer',
+      'oracle',
+      'researcher',
+      'council',
+      'councillor-alpha',
+    ]) {
+      const agent = agents.find((candidate) => candidate.name === name);
+      expect(
+        (agent as { config: { permission: Record<string, unknown> } }).config
+          .permission.marketplace,
+      ).toBe('deny');
+    }
+    expect(
+      (
+        agents.find((agent) => agent.name === 'explorer') as {
+          config: { permission: Record<string, unknown> };
+        }
+      ).config.permission.marketplace,
+    ).toBe('deny');
+  });
+
+  test('omits marketplace prompt guidance when orchestrator permission denies it', () => {
+    const agents = createAgents(
+      runtimeFor({
+        agents: {
+          orchestrator: { permission: { marketplace: 'deny' } },
+        },
+      }),
+    );
+    const prompt = agents.find((agent) => agent.name === 'orchestrator')?.config
+      .prompt;
+    expect(prompt).not.toContain('Use the `marketplace` tool');
+  });
+
+  test('omits marketplace prompt guidance when host permission denies it', () => {
+    const runtime = runtimeFor({ disabled_agents: [] });
+    runtime.captureHostConfig({
+      agent: { orchestrator: { permission: { marketplace: 'deny' } } },
+    });
+    const agents = createAgents(runtime);
+    const prompt = agents.find((agent) => agent.name === 'orchestrator')?.config
+      .prompt;
+    expect(prompt).not.toContain('Use the `marketplace` tool');
+  });
+
+  test('omits marketplace prompt guidance for nested pattern-map deny', () => {
+    const agents = createAgents(
+      runtimeFor({
+        agents: {
+          orchestrator: { permission: { marketplace: { '*': 'deny' } } },
+        },
+      }),
+    );
+    const prompt = agents.find((agent) => agent.name === 'orchestrator')?.config
+      .prompt;
+    expect(prompt).not.toContain('Use the `marketplace` tool');
+    expect(
+      isMarketplacePermissionDenied({ marketplace: { '*': 'deny' } }),
+    ).toBe(true);
+    expect(isMarketplacePermissionDenied({ marketplace: 'allow' })).toBe(false);
+  });
+
+  test('ACP wrappers are denied marketplace', () => {
+    const configs = getAgentConfigs(
+      runtimeFor({
+        acpAgents: {
+          bridge: { command: 'bridge-acp' },
+        },
+      }),
+    );
+    expect(
+      (configs.bridge.permission as Record<string, unknown>).marketplace,
+    ).toBe('deny');
+  });
+
+  test('omits marketplace prompt guidance when the tool is disabled', () => {
+    const agents = createAgents(
+      runtimeFor({ disabled_tools: ['marketplace'] }),
+    );
+    const prompt = agents.find((agent) => agent.name === 'orchestrator')?.config
+      .prompt;
+    expect(prompt).not.toContain('Use the `marketplace` tool');
+  });
+
   test('council agent has synthesis-only (deny-all) permissions', () => {
     const agents = createAgents(
       runtimeFor({
@@ -514,6 +957,13 @@ test('orchestrator prompt excludes Council Mode when no councillors', () => {
   expect(prompt).not.toContain('## Council Mode');
 });
 
+test('council synthesis reinforcement is appended exactly once', () => {
+  const agents = createAgents(runtimeFor({ council: councilConfig() }));
+  const council = agents.find((agent) => agent.name === 'council');
+  const prompt = council?.config.prompt as string;
+  expect(prompt.split(COUNCIL_SYNTHESIS_REINFORCEMENT).length - 1).toBe(1);
+});
+
 describe('isSubagent type guard', () => {
   test('returns true for valid subagent names', () => {
     expect(isSubagent('explorer')).toBe(true);
@@ -550,8 +1000,8 @@ describe('agent classification', () => {
 
     // Subagents
     for (const name of SUBAGENT_NAMES) {
-      // Council is a dual-mode agent ("all"), rest are subagents
-      if (name === 'council') {
+      // Council agents require council configuration; the rest are subagents.
+      if (name === 'council' || name === 'councillor') {
         expect(configs[name]).toBeUndefined();
       } else {
         expect(configs[name].mode).toBe('subagent');
@@ -591,7 +1041,9 @@ describe('createAgents', () => {
     const orchestrator = agents.find((agent) => agent.name === 'orchestrator');
     const explorer = agents.find((agent) => agent.name === 'explorer');
 
-    expect(explorer?.config.prompt).toBe('Replacement explorer prompt.');
+    expect(explorer?.config.prompt).toBe(
+      `Replacement explorer prompt.\n\n${TASK_REJECTION_INSTRUCTION}`,
+    );
     expect(orchestrator?.config.prompt).not.toContain(
       TASK_REJECTION_INSTRUCTION,
     );
@@ -630,31 +1082,49 @@ describe('createAgents', () => {
     expect(names).toContain('fixer');
   });
 
-  test('creates exactly 7 agents by default (observer disabled, council unconfigured)', () => {
+  test('creates exactly 6 agents by default (observer and council disabled)', () => {
     const agents = createAgents(runtimeFor());
-    expect(agents.length).toBe(7);
+    expect(agents.length).toBe(6);
   });
 
-  test('does not create council when council is not configured', () => {
-    const agents = createAgents(runtimeFor());
+  test('does not create or register council agents without council config', () => {
+    const runtime = runtimeFor();
+    const agents = createAgents(runtime);
     const names = agents.map((a) => a.name);
     const orchestrator = agents.find((a) => a.name === 'orchestrator');
+    const configs = getAgentConfigs(runtime);
 
     expect(names).not.toContain('council');
+    expect(names).not.toContain('councillor');
+    expect(names.some((name) => name.startsWith('councillor-'))).toBe(false);
     expect(orchestrator?.config.prompt).not.toContain('@council');
+    expect(Object.hasOwn(configs, 'council')).toBe(false);
+    expect(Object.hasOwn(configs, 'councillor')).toBe(false);
+    expect(
+      Object.keys(configs).some((name) => name.startsWith('councillor-')),
+    ).toBe(false);
   });
 
-  test('creates council when council is configured', () => {
-    const agents = createAgents(
-      runtimeFor({
-        council: councilConfig(),
-      }),
-    );
+  test('creates council and councillors when council is configured', () => {
+    const runtime = runtimeFor({ council: councilConfig() });
+    const agents = createAgents(runtime);
     const names = agents.map((a) => a.name);
     const orchestrator = agents.find((a) => a.name === 'orchestrator');
+    const configs = getAgentConfigs(runtime);
 
     expect(names).toContain('council');
+    expect(names).toContain('councillor');
+    expect(names).toContain('councillor-alpha');
     expect(orchestrator?.config.prompt).toContain('@council');
+    expect(configs.council).toBeDefined();
+    expect(configs.councillor).toMatchObject({
+      mode: 'subagent',
+      hidden: true,
+    });
+    expect(configs['councillor-alpha']).toMatchObject({
+      mode: 'subagent',
+      hidden: true,
+    });
   });
 });
 
@@ -725,6 +1195,74 @@ describe('getAgentConfigs', () => {
     expect(configs.explorer.temperature).toBe(0.5);
     expect(configs.fixer.temperature).toBe(0);
   });
+
+  test('built-in agents get no default color', () => {
+    // No default pinning: colorless agents keep the distinct palette colors
+    // the OpenCode TUI assigns by round-robin (see issue #1116).
+    const configs = getAgentConfigs(
+      runtimeFor({ disabled_agents: [], council: councilConfig() }),
+    );
+
+    for (const name of ALL_AGENT_NAMES) {
+      expect(configs[name]?.color).toBeUndefined();
+    }
+    expect(configs['councillor-alpha']?.color).toBeUndefined();
+  });
+
+  test('configured colors flow to built-in and custom agents', () => {
+    const configs = getAgentConfigs(
+      runtimeFor({
+        agents: {
+          oracle: { color: '#A1b2C3' },
+          reviewer: {
+            model: 'openai/gpt-5.6',
+            color: 'warning',
+          },
+        },
+      }),
+    );
+
+    expect(configs.oracle.color).toBe('#A1b2C3');
+    expect(configs.reviewer.color).toBe('warning');
+  });
+
+  test('dynamic councillors inherit configured council color', () => {
+    const configs = getAgentConfigs(
+      runtimeFor({
+        council: councilConfig(),
+        agents: { council: { color: '#123ABC' } },
+      }),
+    );
+
+    expect(configs.council.color).toBe('#123ABC');
+    expect(configs['councillor-alpha']?.color).toBe('#123ABC');
+  });
+});
+
+describe('AgentOverrideConfigSchema color validation', () => {
+  test('accepts OpenCode theme colors and six-digit hex colors', () => {
+    for (const color of [
+      'primary',
+      'secondary',
+      'accent',
+      'success',
+      'warning',
+      'error',
+      'info',
+      '#FF5733',
+      '#a1B2c3',
+    ]) {
+      expect(AgentOverrideConfigSchema.safeParse({ color }).success).toBe(true);
+    }
+  });
+
+  test('rejects unsupported color formats', () => {
+    for (const color of ['red', '#FFF', '#GG5733', 'FF5733']) {
+      expect(AgentOverrideConfigSchema.safeParse({ color }).success).toBe(
+        false,
+      );
+    }
+  });
 });
 
 describe('council agent model resolution', () => {
@@ -739,7 +1277,7 @@ describe('council agent model resolution', () => {
   });
 
   test('councillor agent uses default model', () => {
-    const agents = createAgents(runtimeFor());
+    const agents = createAgents(runtimeFor({ council: councilConfig() }));
     const councillor = agents.find((a) => a.name === 'councillor');
     expect(councillor?.config.model).toBe(DEFAULT_MODELS.councillor);
   });
@@ -877,6 +1415,27 @@ describe('options passthrough', () => {
 });
 
 describe('AgentOverrideConfigSchema options validation', () => {
+  test('accepts supported model inheritance sources', () => {
+    expect(
+      AgentOverrideConfigSchema.safeParse({
+        inheritModelFrom: 'session',
+      }).success,
+    ).toBe(true);
+    expect(
+      AgentOverrideConfigSchema.safeParse({
+        inheritModelFrom: 'orchestrator',
+      }).success,
+    ).toBe(true);
+  });
+
+  test('rejects unsupported model inheritance sources', () => {
+    expect(
+      AgentOverrideConfigSchema.safeParse({
+        inheritModelFrom: 'librarian',
+      }).success,
+    ).toBe(false);
+  });
+
   test('accepts valid options object', () => {
     const result = AgentOverrideConfigSchema.safeParse({
       options: { textVerbosity: 'low' },
@@ -1072,9 +1631,11 @@ describe('PluginConfigSchema custom-agent-only prompt fields', () => {
     const result = PluginConfigSchema.safeParse({
       presets: {
         openai: {
-          oracle: {
-            model: 'openai/gpt-5.6',
-            prompt: 'ignored preset built-in prompt override',
+          agents: {
+            oracle: {
+              model: 'openai/gpt-5.6',
+              prompt: 'ignored preset built-in prompt override',
+            },
           },
         },
       },
@@ -1181,9 +1742,13 @@ describe('disabled_agents', () => {
   test('protected agents cannot be disabled', () => {
     const config: PluginConfig = {
       disabled_agents: ['orchestrator', 'councillor'],
+      council: councilConfig(),
     };
-    const agents = createAgents(runtimeFor(config));
+    const runtime = runtimeFor(config);
+    const agents = createAgents(runtime);
     const names = agents.map((a) => a.name);
+    expect(runtime.disabledAgents.has('orchestrator')).toBe(false);
+    expect(runtime.disabledAgents.has('councillor')).toBe(false);
     expect(names).toContain('orchestrator');
     expect(names).toContain('councillor');
   });
@@ -1191,6 +1756,7 @@ describe('disabled_agents', () => {
   test('disabling council disables council agent', () => {
     const config: PluginConfig = {
       disabled_agents: ['council'],
+      council: councilConfig(),
     };
     const agents = createAgents(runtimeFor(config));
     const names = agents.map((a) => a.name);
@@ -1201,13 +1767,13 @@ describe('disabled_agents', () => {
 
   test('agent count decreases when agents are disabled', () => {
     const agents = createAgents(runtimeFor());
-    expect(agents.length).toBe(7); // observer disabled, council unconfigured
+    expect(agents.length).toBe(6); // observer and council disabled
 
     const disabledConfig: PluginConfig = {
       disabled_agents: ['observer', 'designer'],
     };
     const disabledAgents = createAgents(runtimeFor(disabledConfig));
-    expect(disabledAgents.length).toBe(6);
+    expect(disabledAgents.length).toBe(5);
   });
 
   test('getDisabledAgents respects protection rules', () => {
@@ -1226,9 +1792,10 @@ describe('disabled_agents', () => {
     };
     const agents = createAgents(runtimeFor(config));
     const names = agents.map((a) => a.name);
-    expect(agents.length).toBe(8);
+    expect(agents.length).toBe(7);
     expect(names).toContain('observer');
     expect(names).not.toContain('council');
+    expect(names).not.toContain('councillor');
   });
 });
 
@@ -1423,3 +1990,115 @@ describe('createAgents with malformed disabled_tools', () => {
     );
   });
 });
+
+describe('resolveAgentConfigModel', () => {
+  test('returns the explicit model when configured', () => {
+    const config: PluginConfig = {
+      agents: { oracle: { model: 'test/oracle-explicit' } },
+    };
+    expect(resolveAgentConfigModel(runtimeFor(config), 'oracle')).toBe(
+      'test/oracle-explicit',
+    );
+  });
+
+  test('returns the primary model of an explicit model array', () => {
+    const config: PluginConfig = {
+      agents: {
+        oracle: { model: ['test/primary', 'test/fallback'] },
+      },
+    };
+    expect(resolveAgentConfigModel(runtimeFor(config), 'oracle')).toBe(
+      'test/primary',
+    );
+  });
+
+  test('session inheritance resolves to no config model (parent session serves)', () => {
+    const config: PluginConfig = {
+      agents: { oracle: { inheritModelFrom: 'session' } },
+    };
+    expect(
+      resolveAgentConfigModel(runtimeFor(config), 'oracle'),
+    ).toBeUndefined();
+  });
+
+  test('orchestrator inheritance uses the configured orchestrator model', () => {
+    const config: PluginConfig = {
+      agents: {
+        orchestrator: { model: 'test/orch' },
+        oracle: { inheritModelFrom: 'orchestrator' },
+      },
+    };
+    expect(resolveAgentConfigModel(runtimeFor(config), 'oracle')).toBe(
+      'test/orch',
+    );
+  });
+
+  test('orchestrator inheritance without an orchestrator model leaves the config model-less', () => {
+    const config: PluginConfig = {
+      agents: { oracle: { inheritModelFrom: 'orchestrator' } },
+    };
+    expect(
+      resolveAgentConfigModel(runtimeFor(config), 'oracle'),
+    ).toBeUndefined();
+  });
+
+  test('fixer with no model inherits the librarian model', () => {
+    const config: PluginConfig = {
+      agents: { librarian: { model: 'anthropic/lib' } },
+    };
+    expect(resolveAgentConfigModel(runtimeFor(config), 'fixer')).toBe(
+      'anthropic/lib',
+    );
+  });
+
+  test('fixer without librarian falls back to the preset primary model', () => {
+    const config: PluginConfig = {
+      preset: 'default',
+      presets: {
+        default: { agents: { oracle: { model: 'test/primary' } } },
+      },
+    };
+    expect(resolveAgentConfigModel(runtimeFor(config), 'fixer')).toBe(
+      'test/primary',
+    );
+  });
+
+  test('matches the final config model createAgents produces for the fixer case', () => {
+    const config: PluginConfig = {
+      agents: { librarian: { model: 'anthropic/lib' } },
+    };
+    const runtime = runtimeFor(config);
+    const fixer = createAgents(runtime).find((a) => a.name === 'fixer');
+    expect(fixer?.config.model).toBe('anthropic/lib');
+    expect(resolveAgentConfigModel(runtime, 'fixer')).toBe(fixer?.config.model);
+  });
+
+  test('resolves a dynamic councillor primary model from the active council preset', () => {
+    const config: PluginConfig = {
+      council: CouncilConfigSchema.parse({
+        presets: {
+          default: {
+            alpha: { model: ['openai/primary', 'google/fallback'] },
+          },
+        },
+      }),
+    };
+    expect(
+      resolveAgentConfigModel(runtimeFor(config), 'councillor-alpha'),
+    ).toBe('openai/primary');
+  });
+
+  test('resolves an ACP wrapper model for background admission', () => {
+    const config: PluginConfig = {
+      acpAgents: {
+        research: {
+          command: 'research-agent',
+          wrapperModel: 'anthropic/sonnet',
+        },
+      },
+    };
+    expect(resolveAgentConfigModel(runtimeFor(config), 'research')).toBe(
+      'anthropic/sonnet',
+    );
+  });
+});

Fișier diff suprimat deoarece este prea mare
+ 785 - 79
src/agents/index.ts


+ 7 - 41
src/agents/librarian.ts

@@ -1,49 +1,15 @@
-import { READONLY_FILE_OPERATIONS_RULES } from '../config';
 import type { AgentDefinition } from './orchestrator';
-
-const LIBRARIAN_PROMPT = `You are Librarian - a research specialist for codebases and documentation.
-
-**Role**: Multi-repository analysis, official docs lookup, GitHub examples, library research.
-
-**Capabilities**:
-- Search and analyze external repositories
-- Find official documentation for libraries
-- Locate implementation examples in open source
-- Understand library internals and best practices
-
-**Tools to Use**:
-- context7: Official documentation lookup
-- gh_grep: Search GitHub repositories
-
-${READONLY_FILE_OPERATIONS_RULES}
-
-**Behavior**:
-- Provide evidence-based answers with sources
-- Quote relevant code snippets
-- Link to official docs when available
-- Distinguish between official and community patterns
-`;
+import { createRoleAgent, ROLE_DEFINITIONS } from './role-definitions';
 
 export function createLibrarianAgent(
   model: string,
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  let prompt = LIBRARIAN_PROMPT;
-
-  if (customPrompt) {
-    prompt = customPrompt;
-  } else if (customAppendPrompt) {
-    prompt = `${LIBRARIAN_PROMPT}\n\n${customAppendPrompt}`;
-  }
-
-  return {
-    name: 'librarian',
-    description:
-      'External documentation and library research. Use for official docs lookup, GitHub examples, and understanding library internals.',
-    config: {
-      model,
-      prompt,
-    },
-  };
+  return createRoleAgent(
+    ROLE_DEFINITIONS.librarian,
+    model,
+    customPrompt,
+    customAppendPrompt,
+  );
 }

+ 7 - 39
src/agents/observer.ts

@@ -1,47 +1,15 @@
-import { READONLY_FILE_OPERATIONS_RULES } from '../config';
 import type { AgentDefinition } from './orchestrator';
-
-const OBSERVER_PROMPT = `You are Observer - a visual analysis specialist.
-
-**Role**: Interpret images, screenshots, PDFs, and diagrams. Extract structured observations for the Orchestrator to act on.
-
-**Behavior**:
-- Read the file(s) specified in the prompt
-- Analyze visual content - layouts, UI elements, text, relationships, flows
-- For screenshots with text/code/errors: extract the **exact text** via OCR - never paraphrase error messages or code
-- For multiple files: analyze each, then compare or relate as requested
-- Return ONLY the extracted information relevant to the goal
-- If the image is unclear, blurry, or partially visible: state what you CAN see and explicitly note what is uncertain - never guess or fabricate details
-
-**Constraints**:
-- READ-ONLY: Analyze and report, don't modify files
-- Save context tokens - the Orchestrator never processes the raw file
-- Match the language of the request
-- If info not found, state clearly what's missing
-
-${READONLY_FILE_OPERATIONS_RULES}
-`;
+import { createRoleAgent, ROLE_DEFINITIONS } from './role-definitions';
 
 export function createObserverAgent(
   model: string,
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  let prompt = OBSERVER_PROMPT;
-
-  if (customPrompt) {
-    prompt = customPrompt;
-  } else if (customAppendPrompt) {
-    prompt = `${OBSERVER_PROMPT}\n\n${customAppendPrompt}`;
-  }
-
-  return {
-    name: 'observer',
-    description:
-      'Visual analysis. Use for interpreting images, screenshots, PDFs, and diagrams - extracts structured observations without loading raw files into main context. Requires a vision-capable model.',
-    config: {
-      model,
-      prompt,
-    },
-  };
+  return createRoleAgent(
+    ROLE_DEFINITIONS.observer,
+    model,
+    customPrompt,
+    customAppendPrompt,
+  );
 }

+ 7 - 44
src/agents/oracle.ts

@@ -1,52 +1,15 @@
-import { READONLY_FILE_OPERATIONS_RULES } from '../config';
 import type { AgentDefinition } from './orchestrator';
-
-const ORACLE_PROMPT = `You are Oracle - a strategic technical advisor and code reviewer.
-
-**Role**: High-IQ debugging, architecture decisions, code review, simplification, and engineering guidance.
-
-**Capabilities**:
-- Analyze complex codebases and identify root causes
-- Propose architectural solutions with tradeoffs
-- Review code for correctness, performance, maintainability, and unnecessary complexity
-- Enforce YAGNI and suggest simpler designs when abstractions are not pulling their weight
-- Guide debugging when standard approaches fail
-
-**Behavior**:
-- Be direct and concise
-- Provide actionable recommendations
-- Explain reasoning briefly
-- Acknowledge uncertainty when present
-- Prefer simpler designs unless complexity clearly earns its keep
-
-**Constraints**:
-- READ-ONLY: You advise, you don't implement
-- Focus on strategy, not execution
-- Point to specific files/lines when relevant
-
-${READONLY_FILE_OPERATIONS_RULES}
-`;
+import { createRoleAgent, ROLE_DEFINITIONS } from './role-definitions';
 
 export function createOracleAgent(
   model: string,
   customPrompt?: string,
   customAppendPrompt?: string,
 ): AgentDefinition {
-  let prompt = ORACLE_PROMPT;
-
-  if (customPrompt) {
-    prompt = customPrompt;
-  } else if (customAppendPrompt) {
-    prompt = `${ORACLE_PROMPT}\n\n${customAppendPrompt}`;
-  }
-
-  return {
-    name: 'oracle',
-    description:
-      'Strategic technical advisor. Use for architecture decisions, complex debugging, code review, simplification, and engineering guidance.',
-    config: {
-      model,
-      prompt,
-    },
-  };
+  return createRoleAgent(
+    ROLE_DEFINITIONS.oracle,
+    model,
+    customPrompt,
+    customAppendPrompt,
+  );
 }

+ 24 - 0
src/agents/orchestrator.test.ts

@@ -41,4 +41,28 @@ describe('orchestrator prompt', () => {
     expect(prompt).not.toContain('End Turn After Background Tasks');
     expect(prompt).toContain('Do not immediately wait after spawning');
   });
+
+  test('instructs the orchestrator to use the marketplace tool', () => {
+    const prompt = buildOrchestratorPrompt();
+
+    expect(prompt).toContain('Use the `marketplace` tool');
+    expect(prompt).toContain('list, show, verify, and status are read-only');
+    expect(prompt).toContain(
+      'report reload_required only when disk activation differs',
+    );
+  });
+
+  test('omits marketplace instructions when the tool is disabled', () => {
+    const prompt = buildOrchestratorPrompt(
+      undefined,
+      undefined,
+      true,
+      true,
+      undefined,
+      undefined,
+      false,
+    );
+
+    expect(prompt).not.toContain('Use the `marketplace` tool');
+  });
 });

+ 104 - 83
src/agents/orchestrator.ts

@@ -1,8 +1,14 @@
 import type { AgentConfig } from '@opencode-ai/sdk/v2';
 import { WRITABLE_FILE_OPERATIONS_RULES } from '../config';
+import {
+  ROLE_DEFINITIONS,
+  renderRoleRoutingBlock,
+  type SpecialistRole,
+} from './role-definitions';
 
 export interface AgentDefinition {
   name: string;
+  baseRole?: SpecialistRole;
   displayName?: string;
   description?: string;
   config: AgentConfig;
@@ -18,77 +24,63 @@ export interface AgentDefinition {
  * shared default. `customAppendPrompt` always appends after whichever base
  * won. Deterministic per session (construction-time only) — cache-safe.
  */
+export interface PromptComposition {
+  agentName: string;
+  replacement?: string;
+  filePrompt?: string;
+  fallback: string;
+  appendPrompt?: string;
+  requiredSuffixes?: readonly string[];
+}
+
+/** Pure prompt composition. Replacement and append layers are intentionally
+ * explicit so every caller has the same precedence and suffix ordering. */
+export function composePrompt(input: PromptComposition): string {
+  const effectiveBase = input.replacement ?? input.filePrompt ?? input.fallback;
+  const parts = [effectiveBase, input.appendPrompt].filter(
+    (part): part is string => part !== undefined && part.length > 0,
+  );
+  for (const suffix of input.requiredSuffixes ?? []) {
+    const normalized = suffix.trim();
+    if (normalized && !parts[parts.length - 1].endsWith(normalized)) {
+      parts.push(normalized);
+    }
+  }
+  return parts.join('\n\n');
+}
+
 export function resolvePrompt(
   agentName: string,
   inlinePrompt: string | undefined,
   filePrompt: string | undefined,
   fallback: string,
   customAppendPrompt?: string,
+  requiredSuffixes?: readonly string[],
 ): string {
   if (inlinePrompt !== undefined && filePrompt !== undefined) {
     console.warn(
       `[oh-my-opencode] Agent '${agentName}': inline prompt overrides prompt file (${agentName}.md). Remove the inline prompt to use the file.`,
     );
   }
-  const effectiveBase = inlinePrompt ?? filePrompt ?? fallback;
-  return customAppendPrompt !== undefined
-    ? `${effectiveBase}\n\n${customAppendPrompt}`
-    : effectiveBase;
+  return composePrompt({
+    agentName,
+    replacement: inlinePrompt,
+    filePrompt,
+    fallback,
+    appendPrompt: customAppendPrompt,
+    requiredSuffixes,
+  });
 }
 
-// Agent descriptions for the orchestrator prompt
-const AGENT_DESCRIPTIONS: Record<string, string> = {
-  explorer: `@explorer
-- Lane: Fast codebase recon that returns compressed context
-- Permissions: read_files
-- Stats: 2x faster codebase search than orchestrator, 1/2 cost of orchestrator
-- Capabilities: Glob, grep, AST queries to locate files, symbols, patterns
-- **Delegate when:** Need to discover what exists before planning • Parallel searches speed discovery • Need summarized map vs full contents • Broad/uncertain scope
-- **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file`,
-
-  librarian: `@librarian
-- Lane: External knowledge and library research, fast web research
-- Role: Authoritative source for current library docs, API references, examples, bug investigations, and web retrieval
-- Stats: 2x faster web research than orchestrator, 1/2 cost of orchestrator
-- **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) • Complex APIs needing official examples (ORMs, auth) • Version-specific behavior matters • Unfamiliar library • Edge cases or advanced features • Nuanced best practices • Working on fixing tricky bug or problem and need latest web research information
-- **Don't delegate when:** Standard usage you're confident • Simple stable APIs • General programming knowledge • Info already in conversation • Built-in language features
-- **Rule of thumb:** "How does this library work?" → @librarian. "How does programming work?" → answer directly. "How do others solve or workaround this tricky issue?" → @librarian.`,
-
-  oracle: `@oracle
-- Lane: Architecture, risk, debugging strategy, and review
-- Role: Strategic advisor for high-stakes decisions and persistent problems, code reviewer
-- Permissions: read_files
-- Stats: 5x better decision maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
-- Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review, simplification, maintainability review
-- **Delegate when:** Major architectural decisions with long-term impact • Problems persisting after 2+ fix attempts • High-risk multi-system refactors • Costly trade-offs (performance vs maintainability) • Complex debugging with unclear root cause • Security/scalability/data integrity decisions • Genuinely uncertain and cost of wrong choice is high • Code needs simplification or YAGNI scrutiny
-- **Review use:** @oracle is an escalation, not a default verification step. Request independent @oracle review only when its analysis is expected to materially reduce risk or uncertainty.
-- **Don't delegate when:** Routine decisions you're confident about • First bug fix attempt • Straightforward trade-offs • Tactical "how" vs strategic "should" • Time-sensitive good-enough decisions • Quick research/testing can answer
-- **Rule of thumb:** Need senior architect review? → @oracle. Need code review or simplification? → @oracle. Routine coordination or final synthesis? → handle directly.`,
-
-  designer: `@designer
-- Lane: UI/UX design, related edits, design polish and review
-- Permissions: read_files, write_files
-- Stats: 10x better UI/UX than orchestrator
-- Capabilities: Good design taste, visual relevant edits, interactions, responsive layouts, design systems with aesthetic intent, deep UI/UX knowledge.
-- Owns visual and interaction quality: layout, hierarchy, spacing, motion, affordances, responsive behavior, and overall feel.
-- Weakness: copywriting. Ask @designer to use grounded, normal wording, then have orchestrator review/fix copy after design work without changing visual or interaction intent.
-- Avoid: "Let me ask @designer how it should look and implement yourself" → instead: "Let me ask @designer to design and implement the UI/UX changes for me"
-- **Delegate when:** User-facing interfaces needing polish • Responsive layouts • UX-critical components (forms, nav, dashboards) • Visual consistency systems • Animations/micro-interactions • Landing/marketing pages • Refining functional→delightful • Reviewing existing UI/UX quality
-- **Don't delegate when:** Backend/logic with no visual • Quick prototypes where design doesn't matter yet.
-- **Rule of thumb:** Users see it and polish matters? → @designer. Headless/functional implementation? → schedule @fixer.`,
-
-  fixer: `@fixer
-- Lane: Bounded implementation and executioner
-- Role: Fast execution specialist for well-defined tasks
-- Permissions: read_files, write_files
-- Stats: 2x faster code edits, 1/2 cost of orchestrator
-- Weakness: design, taste
-- Tools/Constraints: Execution-focused-no research, no architectural decisions
-- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Parallelization benefits: Task involves multiple folders and multiple files modification, scoping work per folder and spawning parallel @fixer instances for each folder.
-- **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining to @fixer > doing • Tight integration with your current work • Requires design taste, visual hierarchy, interaction polish, responsive layout decisions, animation/motion, component feel, or UI copy/design trade-offs
-- **Rule of thumb:** Headless/mechanical implementation → @fixer. User-visible design or polish → @designer. If @designer already set direction, @fixer may only do bounded mechanical follow-up that preserves that design exactly.`,
-
-  council: `@council
+// Parallel delegation examples
+const PARALLEL_DELEGATION_EXAMPLES = [
+  '- Multiple @explorer searches across different domains?',
+  '- @explorer + @librarian research in parallel?',
+  '- Multiple @fixer instances for faster, scoped implementation?',
+  '- @observer + @explorer in parallel (visual analysis + code search)?',
+];
+
+export const COUNCIL_ROUTING_BLOCK = `@council
 - Lane: High-stakes multi-model decision support
 - Role: Multi-LLM consensus engine that receives raw councillor responses and synthesizes them into a structured council report.
 - Permissions: Read files
@@ -98,33 +90,23 @@ const AGENT_DESCRIPTIONS: Record<string, string> = {
 - **Don't delegate when:** Straightforward tasks you're confident about • Speed matters more than confidence • Routine implementation/debugging • A single specialist is clearly the right tool • You only need current docs/search/code review rather than multi-model consensus.
 - **How to call:** Send the full question/task and relevant context. Be explicit about what decision, trade-off, or answer the council should resolve. Do not ask council to do routine code edits.
 - **Result handling:** Council returns a structured response that may include: synthesized Council Response, individual Per-Councillor Details, and Council Summary/confidence. Preserve that structure when the user asked for council output. Do not pretend the council only returned a final answer. If you need to act on the council result, first briefly state the council's recommendation, then proceed.
-- **Rule of thumb:** Need second/third opinions from different models? → @council. Need one expert lane? → use the specialist. Need final synthesis? → handle directly.`,
+- **Rule of thumb:** Need second/third opinions from different models? → @council. Need one expert lane? → use the specialist. Need final synthesis? → handle directly.`;
 
-  observer: `@observer
-- Lane: Visual/media analysis isolated from orchestrator context
-- Role: Visual analysis specialist for images, PDFs, and diagrams
-- Permissions: Read files
-- Stats: Saves main context tokens - @observer processes raw files, returns structured observations
-- Capabilities: Interprets images, screenshots, PDFs, and diagrams via native read tool; extracts UI elements, layouts, text, relationships
-- **Delegate when:** Need to analyze a multimedia file• Extract information
-- **Don't delegate when:** Plain text files that Read can handle directly • Files that need editing afterward (need literal content from Read)
-- **Rule of thumb:** Even if your model supports vision, delegate visual analysis to @observer - it isolates large image/PDF bytes from your context window, returning only concise structured text. Need exact file contents for routing? → Read only the minimal context yourself.
-- **IMPORTANT:** When delegating to @observer, always include the **full file path** in the prompt so it can read the file. Example: "Analyze the screenshot at /path/to/file.png - describe the UI elements and error messages."`,
-};
+export function renderCouncilRoutingBlock(runtimeName: string): string {
+  return COUNCIL_ROUTING_BLOCK.replaceAll('@council', `@${runtimeName}`);
+}
 
-// Parallel delegation examples
-const PARALLEL_DELEGATION_EXAMPLES = [
-  '- Multiple @explorer searches across different domains?',
-  '- @explorer + @librarian research in parallel?',
-  '- Multiple @fixer instances for faster, scoped implementation?',
-  '- @observer + @explorer in parallel (visual analysis + code search)?',
-];
+export interface RoutingEntry {
+  readonly agentName: string;
+  readonly routingBlock: string;
+}
 
 /**
  * Build the orchestrator prompt with dynamic agent filtering.
  * @param disabledAgents - Set of disabled agent names to exclude from the prompt
  * @param waitForUserEnabled - Whether explicit text-only HITL waiting is available
  * @param wakeSchedulerEnabled - Whether the orchestrator wake scheduler can resume the session after idle
+ * @param marketplaceEnabled - Whether the local marketplace tool is available
  * @returns The complete orchestrator prompt string
  */
 export function buildOrchestratorPrompt(
@@ -132,12 +114,28 @@ export function buildOrchestratorPrompt(
   excludeDescriptions?: string[],
   waitForUserEnabled = true,
   wakeSchedulerEnabled = true,
+  routingEntries?: readonly RoutingEntry[],
+  runtimeNameByCanonicalId?: Readonly<Record<string, string>>,
+  marketplaceEnabled = true,
 ): string {
-  // Filter agent descriptions
-  const enabledAgents = Object.entries(AGENT_DESCRIPTIONS)
-    .filter(([name]) => !disabledAgents?.has(name))
-    .filter(([name]) => !excludeDescriptions?.includes(name))
-    .map(([, desc]) => desc)
+  const entries = routingEntries ?? [
+    ...Object.entries(ROLE_DEFINITIONS).map(([name, role]) => ({
+      agentName: name,
+      routingBlock: renderRoleRoutingBlock(role, name),
+    })),
+    { agentName: 'council', routingBlock: COUNCIL_ROUTING_BLOCK },
+  ];
+  const enabledAgents = entries
+    .filter(({ agentName }) => !disabledAgents?.has(agentName))
+    .filter(({ agentName }) => !excludeDescriptions?.includes(agentName))
+    .sort((left, right) =>
+      left.agentName < right.agentName
+        ? -1
+        : left.agentName > right.agentName
+          ? 1
+          : 0,
+    )
+    .map(({ routingBlock }) => routingBlock)
     .join('\n\n');
 
   // Filter parallel delegation examples - remove lines mentioning any disabled agent
@@ -147,12 +145,29 @@ export function buildOrchestratorPrompt(
       if (mentions.length === 0) return true;
       return mentions.every((name) => !disabledAgents?.has(name));
     },
-  ).join('\n');
+  )
+    .map((line) => {
+      let rendered = line;
+      for (const [canonicalName, runtimeName] of Object.entries(
+        runtimeNameByCanonicalId ?? {},
+      )) {
+        rendered = rendered.replace(
+          new RegExp(`@${canonicalName}\\b`, 'g'),
+          `@${runtimeName}`,
+        );
+      }
+      return rendered;
+    })
+    .join('\n');
 
   const externalManualWaitInstruction = waitForUserEnabled
     ? '- When work must pause while the user completes an external manual operation, first give the user concrete manual steps, then call `wait_for_user` as your final tool action and end the turn. Do not rely on ordinary text alone to mark this waiting state, and do not call more tools after `wait_for_user`. Background tasks are not external manual work — never use `wait_for_user` to await them; the system resumes automatically via the Background Job Board and orchestrator wake scheduler.'
     : '- When work must pause while the user completes an external manual operation, first give the user concrete manual steps, then use the `question` tool as the blocking boundary and ask them to respond when finished. `wait_for_user` is disabled, so do not reference or call it.';
 
+  const marketplaceInstruction = marketplaceEnabled
+    ? '- Use the `marketplace` tool for local offline package lifecycle and status. list, show, verify, and status are read-only. install, import, update, enable, disable, profile, and remove write store/config only and never hot-swap the live registry; they report reload_required only when disk activation differs from this session. Do not shell out to the CLI for these actions.'
+    : '';
+
   return `<Role>
 You are a workflow manager for coding work. Your job is to plan, schedule, delegate, monitor, reconcile, and verify specialist-agent work. You are not the default implementation worker.
 
@@ -277,7 +292,7 @@ After spawning all independent background tasks and any remaining non-overlappin
 - Don't guess at critical details (file paths, API choices, architectural decisions)
 - Do make reasonable assumptions for minor details and state them briefly
 - When user input is required before work can continue and the user can answer immediately—including clarification, permission, a choice, or pasted command output—use the \`question\` tool. Enable custom input, request a concise pasted response or command output, and provide a small bounded set of options whenever the tool schema requires options.
-${externalManualWaitInstruction}
+${[externalManualWaitInstruction, marketplaceInstruction].filter(Boolean).join('\n')}
 - For ordinary dialogue that does not block work, answer normally and do not use the question tool gratuitously.
 
 ## Concise Execution
@@ -316,12 +331,18 @@ export function createOrchestratorAgent(
   excludeDescriptions?: string[],
   waitForUserEnabled = true,
   wakeSchedulerEnabled = true,
+  routingEntries?: readonly RoutingEntry[],
+  runtimeNameByCanonicalId?: Readonly<Record<string, string>>,
+  marketplaceEnabled = true,
 ): AgentDefinition {
   const basePrompt = buildOrchestratorPrompt(
     disabledAgents,
     excludeDescriptions,
     waitForUserEnabled,
     wakeSchedulerEnabled,
+    routingEntries,
+    runtimeNameByCanonicalId,
+    marketplaceEnabled,
   );
   const prompt = resolvePrompt(
     'orchestrator',

+ 382 - 0
src/agents/registry.test.ts

@@ -0,0 +1,382 @@
+import { describe, expect, test } from 'bun:test';
+import type { PluginConfig } from '../config';
+import { CouncilConfigSchema } from '../config/council-schema';
+import { RuntimeConfig } from '../config/runtime';
+import { COUNCIL_SYNTHESIS_REINFORCEMENT } from './council';
+import {
+  buildResolvedAgentRegistry,
+  projectAgentPermission,
+  type ResolvedAgentRegistry,
+} from './index';
+import {
+  ROLE_DEFINITIONS,
+  SUPPORTED_SPECIALIST_ROLES,
+} from './role-definitions';
+import { TASK_REJECTION_INSTRUCTION } from './task-rejection';
+
+const DIRECTORY = 'resolved-agent-registry-test';
+
+function registryFor(config: PluginConfig = {}): ResolvedAgentRegistry {
+  RuntimeConfig.reset(DIRECTORY);
+  const runtime = RuntimeConfig.init(DIRECTORY, config);
+  return buildResolvedAgentRegistry(runtime);
+}
+
+describe('ResolvedAgentRegistry', () => {
+  test('keeps deny-all authoritative for a role-derived agent under v2 matching', () => {
+    const registry = registryFor({
+      agents: {
+        audit: {
+          baseRole: 'fixer',
+          model: 'provider/audit',
+          permission: 'deny',
+        },
+      },
+    });
+    const permission = registry.sdkConfigs.audit?.permission as Record<
+      string,
+      unknown
+    >;
+
+    // v2 resolves an explicit tool rule after the wildcard, so any generated
+    // allow would reopen that tool. Only immutable deny gates may be added.
+    const v2EffectivePermission = (tool: string): unknown =>
+      permission[tool] ?? permission['*'];
+    expect(v2EffectivePermission('read')).toBe('deny');
+    expect(v2EffectivePermission('edit')).toBe('deny');
+    expect(v2EffectivePermission('question')).toBe('deny');
+    expect(v2EffectivePermission('task_cancel')).toBe('deny');
+    expect(permission.wait_for_user).toBe('deny');
+    expect(permission.marketplace).toBe('deny');
+  });
+
+  test('keeps SDK, model, skill, MCP, and routing surfaces consistent', () => {
+    const registry = registryFor({
+      agents: {
+        explorer: {
+          model: ['provider/primary', 'provider/fallback'],
+          skills: ['simplify'],
+          mcps: ['context7'],
+        },
+      },
+    });
+
+    expect(registry.agents.map((agent) => agent.name)).toContain('explorer');
+    expect(registry.sdkConfigs.explorer.model).toBe('provider/primary');
+    expect(registry.modelArrays.explorer).toEqual([
+      { id: 'provider/primary' },
+      { id: 'provider/fallback' },
+    ]);
+    expect(registry.modelChains.explorer).toEqual([
+      'provider/primary',
+      'provider/fallback',
+    ]);
+    expect(registry.mcpLists.explorer).toEqual(['context7']);
+    expect(registry.skillPermissions.explorer.simplify).toBe('allow');
+    expect(registry.runtimeNameByCanonicalId.explorer).toBe('explorer');
+    expect(registry.canonicalIdByRuntimeName.explorer).toBe('explorer');
+    expect(registry.routing.map((entry) => entry.agentName)).toEqual(
+      [...registry.routing].map((entry) => entry.agentName).sort(),
+    );
+    expect(Object.isFrozen(registry)).toBe(true);
+    expect(Object.isFrozen(registry.modelArrays.explorer)).toBe(true);
+    expect(Object.isFrozen(registry.sdkConfigs.explorer)).toBe(true);
+    expect(Object.isFrozen(registry.sdkConfigs.explorer.permission)).toBe(true);
+  });
+
+  test('does not expose special agents as supported role definitions', async () => {
+    const { SUPPORTED_SPECIALIST_ROLES } = await import('./role-definitions');
+    expect(SUPPORTED_SPECIALIST_ROLES).not.toContain('orchestrator');
+    expect(SUPPORTED_SPECIALIST_ROLES).not.toContain('councillor');
+  });
+
+  test('preserves complete built-in role parity', () => {
+    const registry = registryFor({ disabled_agents: [] });
+    for (const roleName of SUPPORTED_SPECIALIST_ROLES) {
+      const role = ROLE_DEFINITIONS[roleName];
+      const agent = registry.agents.find((entry) => entry.name === roleName);
+      expect(agent?.baseRole).toBe(roleName);
+      expect(agent?.config.prompt).toContain(role.basePrompt);
+      expect(agent?.description).toBe(role.description);
+      expect(registry.mcpLists[roleName]).toEqual([...role.defaultMcps]);
+      expect(registry.routing).toContainEqual(
+        expect.objectContaining({ agentName: roleName }),
+      );
+      const permission = registry.sdkConfigs[roleName]?.permission as Record<
+        string,
+        unknown
+      >;
+      expect(permission.edit).toBe(
+        role.permissionPolicy === 'read-write' ? 'allow' : 'deny',
+      );
+      for (const skill of role.defaultSkills) {
+        expect(
+          (registry.skillPermissions[roleName] as Record<string, unknown>)[
+            skill
+          ],
+        ).toBe('allow');
+      }
+    }
+  });
+
+  test('discovers preset-only custom agents and preserves their model chains', () => {
+    const registry = registryFor({
+      preset: 'custom',
+      presets: {
+        custom: {
+          agents: {
+            auditOracle: {
+              baseRole: 'oracle',
+              model: ['provider/one', 'provider/two'],
+            },
+          },
+        },
+      },
+    });
+
+    expect(registry.agents.map((agent) => agent.name)).toContain('auditOracle');
+    expect(registry.modelArrays.auditOracle).toEqual([
+      { id: 'provider/one' },
+      { id: 'provider/two' },
+    ]);
+    expect(registry.modelChains.auditOracle).toEqual([
+      'provider/one',
+      'provider/two',
+    ]);
+  });
+
+  test('renders derived role routing against the runtime agent name', () => {
+    const registry = registryFor({
+      agents: {
+        auditOracle: { baseRole: 'oracle', model: 'provider/oracle' },
+      },
+    });
+    const route = registry.routing.find(
+      (entry) => entry.agentName === 'auditOracle',
+    );
+
+    expect(route?.routingBlock).toContain('@auditOracle');
+    expect(route?.routingBlock).not.toContain('@oracle');
+    expect(
+      registry.agents.find((agent) => agent.name === 'orchestrator')?.config
+        .prompt,
+    ).toContain('@auditOracle');
+  });
+
+  test('does not route an unconfigured or disabled council', () => {
+    expect(registryFor().routing.map((entry) => entry.agentName)).not.toContain(
+      'council',
+    );
+    expect(
+      registryFor({ disabled_agents: ['council'] }).routing.map(
+        (entry) => entry.agentName,
+      ),
+    ).not.toContain('council');
+  });
+
+  test('routes an enabled council exactly when it is registered', () => {
+    const registry = registryFor({
+      disabled_agents: [],
+      council: CouncilConfigSchema.parse({
+        presets: { default: { alpha: { model: 'provider/councillor' } } },
+      }),
+    });
+
+    expect(registry.sdkConfigs.council).toBeDefined();
+    expect(
+      registry.routing.filter((entry) => entry.agentName === 'council'),
+    ).toHaveLength(1);
+  });
+
+  test('renders council routing with its resolved display name', () => {
+    const registry = registryFor({
+      agents: { council: { displayName: 'consensus' } },
+      council: CouncilConfigSchema.parse({
+        presets: { default: { alpha: { model: 'provider/councillor' } } },
+      }),
+    });
+    const route = registry.routing.find(
+      (entry) => entry.agentName === 'consensus',
+    );
+
+    expect(route?.routingBlock).toContain('@consensus');
+    expect(route?.routingBlock).not.toContain('@council');
+  });
+
+  test('folds custom and ACP routing guidance into one deterministic registry', () => {
+    const rawCustomGuidance =
+      'Use @janitor after @explorer completes the initial scan.';
+    const customGuidance =
+      'Use @janitor after @scout completes the initial scan.';
+    const acpGuidance = 'Delegate provider research to @claude-research.';
+    const registry = registryFor({
+      agents: {
+        explorer: { model: 'provider/explorer', displayName: 'scout' },
+        janitor: {
+          model: 'provider/janitor',
+          orchestratorPrompt: rawCustomGuidance,
+        },
+      },
+      acpAgents: {
+        'claude-research': {
+          command: 'claude-code-acp',
+          args: [],
+          env: {},
+          timeoutMs: 0,
+          permissionMode: 'ask',
+          orchestratorPrompt: acpGuidance,
+        },
+      },
+    });
+    const orchestrator = registry.agents.find(
+      (agent) => agent.name === 'orchestrator',
+    );
+    const prompt = orchestrator?.config.prompt ?? '';
+
+    expect(
+      registry.routing.filter((entry) =>
+        entry.routingBlock.includes(customGuidance),
+      ),
+    ).toHaveLength(1);
+    expect(
+      registry.routing.filter((entry) =>
+        entry.routingBlock.includes(acpGuidance),
+      ),
+    ).toHaveLength(1);
+    expect(prompt.match(new RegExp(customGuidance, 'g'))).toHaveLength(1);
+    expect(prompt.match(new RegExp(acpGuidance, 'g'))).toHaveLength(1);
+    expect(registry.routing.map((entry) => entry.agentName)).toEqual(
+      [...registry.routing].map((entry) => entry.agentName).sort(),
+    );
+    expect(registry.routing.map((entry) => entry.agentName)).toContain('scout');
+    expect(registry.routing.map((entry) => entry.agentName)).not.toContain(
+      'explorer',
+    );
+  });
+
+  test('finalizes host fields and MCP permissions in owned registry projections', () => {
+    RuntimeConfig.reset(DIRECTORY);
+    const runtime = RuntimeConfig.init(DIRECTORY, {
+      agents: { explorer: { mcps: ['context7'] } },
+    });
+    runtime.captureHostConfig({
+      agent: {
+        explorer: {
+          model: 'host/explorer',
+          options: { nested: { value: 1 } },
+          permission: {
+            bash: { 'git status': 'allow' },
+          },
+        },
+      },
+      mcp: { context7: { type: 'remote' } },
+    });
+    const registry = buildResolvedAgentRegistry(runtime, {
+      availableMcpNames: ['context7'],
+    });
+    const explorer = registry.sdkConfigs.explorer as Record<string, unknown>;
+    const permission = explorer.permission as Record<string, unknown>;
+
+    expect(explorer.model).toBe('host/explorer');
+    expect(explorer.options).toEqual({ nested: { value: 1 } });
+    expect(permission['context7_*']).toBe('allow');
+    expect((permission.bash as Record<string, unknown>)['git status']).toBe(
+      'allow',
+    );
+    expect(Object.isFrozen(explorer.options)).toBe(true);
+  });
+
+  test('uses RoleDefinition.defaultModel for every built-in specialist', () => {
+    const registry = registryFor({ disabled_agents: [] });
+    for (const roleName of SUPPORTED_SPECIALIST_ROLES) {
+      expect(registry.sdkConfigs[roleName]?.model).toBe(
+        ROLE_DEFINITIONS[roleName].defaultModel,
+      );
+    }
+  });
+
+  test('normalizes host shorthand and reapplies only the immutable gate', () => {
+    const registry = registryFor({ disabled_agents: [] });
+    const hostEntry: Record<string, unknown> = { permission: 'ask' };
+
+    projectAgentPermission('explorer', hostEntry, registry);
+
+    expect(hostEntry.permission).toMatchObject({
+      '*': 'ask',
+      task_cancel: 'deny',
+      wait_for_user: 'deny',
+      marketplace: 'deny',
+    });
+    expect((hostEntry.permission as Record<string, unknown>).edit).toBe('deny');
+  });
+
+  test('mirrors display aliases across model, skill, and MCP consumers', () => {
+    const registry = registryFor({
+      disabled_agents: [],
+      agents: {
+        explorer: {
+          displayName: 'Scout',
+          model: ['provider/one', 'provider/two'],
+          skills: ['simplify'],
+          mcps: ['context7'],
+        },
+      },
+    });
+
+    expect(registry.canonicalIdByRuntimeName.Scout).toBe('explorer');
+    expect(registry.modelArrays.Scout).toEqual(registry.modelArrays.explorer);
+    expect(registry.mcpLists.Scout).toEqual(['context7']);
+    expect(registry.skillPermissions.Scout).toEqual(
+      registry.skillPermissions.explorer,
+    );
+    expect(registry.routing).toContainEqual(
+      expect.objectContaining({ agentName: 'Scout' }),
+    );
+    expect(registry.sdkConfigs.Scout).toMatchObject({
+      displayName: 'Scout',
+      model: 'provider/one',
+      mcps: ['context7'],
+    });
+    expect(registry.sdkConfigs.Scout.hidden).toBeUndefined();
+    expect(registry.sdkConfigs.explorer.hidden).toBe(true);
+    expect(
+      (registry.sdkConfigs.Scout.permission as Record<string, unknown>)
+        .marketplace,
+    ).toBe('deny');
+    expect(
+      (registry.sdkConfigs.explorer.permission as Record<string, unknown>)
+        .marketplace,
+    ).toBe('deny');
+  });
+
+  test('does not let host prompts replace mandatory agent suffixes', () => {
+    RuntimeConfig.reset(DIRECTORY);
+    const runtime = RuntimeConfig.init(DIRECTORY, {
+      disabled_agents: [],
+      council: CouncilConfigSchema.parse({
+        default_preset: 'default',
+        presets: {
+          default: {
+            alpha: { model: 'provider/councillor' },
+          },
+        },
+      }),
+    });
+    runtime.captureHostConfig({
+      agent: {
+        explorer: { prompt: 'host explorer prompt' },
+        council: { prompt: 'host council prompt' },
+      },
+    });
+
+    const registry = buildResolvedAgentRegistry(runtime);
+    const explorerPrompt = String(registry.sdkConfigs.explorer.prompt);
+    const councilPrompt = String(registry.sdkConfigs.council.prompt);
+
+    expect(explorerPrompt).not.toContain('host explorer prompt');
+    expect(explorerPrompt).toContain(TASK_REJECTION_INSTRUCTION);
+    expect(councilPrompt).not.toContain('host council prompt');
+    expect(councilPrompt).toContain(TASK_REJECTION_INSTRUCTION);
+    expect(councilPrompt).toContain(COUNCIL_SYNTHESIS_REINFORCEMENT);
+  });
+});

+ 10 - 0
src/agents/resolve-prompt-warn.test.ts

@@ -34,6 +34,16 @@ describe('resolvePrompt precedence', () => {
       `${FALLBACK}\n\n${APPEND}`,
     );
   });
+
+  test('required suffixes are always last and deterministic', () => {
+    const result = resolvePrompt('a', INLINE, FILE, FALLBACK, APPEND, [
+      'mandatory policy',
+      'final gate',
+    ]);
+    expect(result).toBe(
+      `${INLINE}\n\n${APPEND}\n\nmandatory policy\n\nfinal gate`,
+    );
+  });
 });
 
 describe('resolvePrompt conflict warning', () => {

+ 424 - 0
src/agents/role-definitions.ts

@@ -0,0 +1,424 @@
+import {
+  READONLY_FILE_OPERATIONS_RULES,
+  WRITABLE_FILE_OPERATIONS_RULES,
+} from '../config';
+import { DEFAULT_AGENT_MCPS } from '../config/agent-mcps';
+import type { SpecialistRole } from '../config/agent-roles';
+import { DEFAULT_MODELS } from '../config/constants';
+import type { AgentDefinition } from './orchestrator';
+
+export {
+  type SpecialistRole,
+  SUPPORTED_SPECIALIST_ROLES,
+} from '../config/agent-roles';
+
+export interface RoleDefinition {
+  readonly id: SpecialistRole;
+  readonly basePrompt: string;
+  readonly description: string;
+  readonly derivable: true;
+  readonly profileTargetable: true;
+  readonly defaultModel: string | undefined;
+  readonly defaultSkills: readonly string[];
+  readonly defaultMcps: readonly string[];
+  readonly permissionPolicy: 'read-only' | 'read-write';
+  readonly routingBlock: string;
+  readonly createBaseline: (model: string) => AgentDefinition;
+}
+
+function createRoleBaseline(
+  role: RoleDefinition,
+  model: string,
+): AgentDefinition {
+  return {
+    name: role.id,
+    baseRole: role.id,
+    description: role.description,
+    config: {
+      model,
+      prompt: role.basePrompt,
+    },
+  };
+}
+
+function createBaselineFactory(role: RoleDefinition) {
+  return (model: string): AgentDefinition => createRoleBaseline(role, model);
+}
+
+export function createRoleAgent(
+  role: RoleDefinition,
+  model: string,
+  customPrompt?: string,
+  customAppendPrompt?: string,
+): AgentDefinition {
+  const baseline = role.createBaseline(model);
+  if (customPrompt) {
+    baseline.config.prompt = customPrompt;
+  } else if (customAppendPrompt) {
+    baseline.config.prompt = `${baseline.config.prompt}\n\n${customAppendPrompt}`;
+  }
+  return baseline;
+}
+
+export function renderRoleRoutingBlock(
+  role: RoleDefinition,
+  runtimeName: string,
+): string {
+  return role.routingBlock.replace(
+    new RegExp(`@${role.id}\\b`, 'g'),
+    `@${runtimeName}`,
+  );
+}
+
+const EXPLORER_PROMPT = `You are Explorer - a fast codebase navigation specialist.
+
+**Role**: Quick contextual grep for codebases. Answer "Where is X?", "Find Y", "Which file has Z".
+
+**When to use which tools**:
+- **Text/regex patterns** (strings, comments, variable names): grep
+- **Structural patterns** (function shapes, class structures): ast_grep_search
+- **File discovery** (find by name/extension): glob
+
+${READONLY_FILE_OPERATIONS_RULES}
+
+**Behavior**:
+- Be fast and thorough
+- Fire multiple searches in parallel if needed
+- Return file paths with relevant snippets
+
+**Output Format**:
+<results>
+<files>
+- /path/to/file.ts:42 - Brief description of what's there
+</files>
+<answer>
+Concise answer to the question
+</answer>
+</results>
+
+**Constraints**:
+- READ-ONLY: Search and report, don't modify
+- Be exhaustive but concise
+- Include line numbers when relevant
+`;
+
+const LIBRARIAN_PROMPT = `You are Librarian - a research specialist for codebases and documentation.
+
+**Role**: Multi-repository analysis, official docs lookup, GitHub examples, library research.
+
+**Capabilities**:
+- Search and analyze external repositories
+- Find official documentation for libraries
+- Locate implementation examples in open source
+- Understand library internals and best practices
+
+**Tools to Use**:
+- context7: Official documentation lookup
+- gh_grep: Search GitHub repositories
+
+${READONLY_FILE_OPERATIONS_RULES}
+
+**Behavior**:
+- Provide evidence-based answers with sources
+- Quote relevant code snippets
+- Link to official docs when available
+- Distinguish between official and community patterns
+`;
+
+const ORACLE_PROMPT = `You are Oracle - a strategic technical advisor and code reviewer.
+
+**Role**: High-IQ debugging, architecture decisions, code review, simplification, and engineering guidance.
+
+**Capabilities**:
+- Analyze complex codebases and identify root causes
+- Propose architectural solutions with tradeoffs
+- Review code for correctness, performance, maintainability, and unnecessary complexity
+- Enforce YAGNI and suggest simpler designs when abstractions are not pulling their weight
+- Guide debugging when standard approaches fail
+
+**Behavior**:
+- Be direct and concise
+- Provide actionable recommendations
+- Explain reasoning briefly
+- Acknowledge uncertainty when present
+- Prefer simpler designs unless complexity clearly earns its keep
+
+**Constraints**:
+- READ-ONLY: You advise, you don't implement
+- Focus on strategy, not execution
+- Point to specific files/lines when relevant
+
+${READONLY_FILE_OPERATIONS_RULES}
+`;
+
+const DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates and reviews intentional, polished experiences.
+
+**Role**: Craft and review cohesive UI/UX that balances visual impact with usability.
+
+## Design Principles
+
+**Typography**
+- Choose distinctive, characterful fonts that elevate aesthetics
+- Avoid generic defaults (Arial, Inter)-opt for unexpected, beautiful choices
+- Pair display fonts with refined body fonts for hierarchy
+
+**Color & Theme**
+- Commit to a cohesive aesthetic with clear color variables
+- Dominant colors with sharp accents > timid, evenly-distributed palettes
+- Create atmosphere through intentional color relationships
+
+**Motion & Interaction**
+- Leverage framework animation utilities when available (Tailwind's transition/animation classes)
+- Focus on high-impact moments: orchestrated page loads with staggered reveals
+- Use scroll-triggers and hover states that surprise and delight
+- One well-timed animation > scattered micro-interactions
+- Drop to custom CSS/JS only when utilities can't achieve the vision
+
+**Spatial Composition**
+- Break conventions: asymmetry, overlap, diagonal flow, grid-breaking
+- Generous negative space OR controlled density-commit to the choice
+- Unexpected layouts that guide the eye
+
+**Visual Depth**
+- Create atmosphere beyond solid colors: gradient meshes, noise textures, geometric patterns
+- Layer transparencies, dramatic shadows, decorative borders
+- Contextual effects that match the aesthetic (grain overlays, custom cursors)
+
+**Styling Approach**
+- Default to Tailwind CSS utility classes when available-fast, maintainable, consistent
+- Use custom CSS when the vision requires it: complex animations, unique effects, advanced compositions
+- Balance utility-first speed with creative freedom where it matters
+
+**Match Vision to Execution**
+- Maximalist designs → elaborate implementation, extensive animations, rich effects
+- Minimalist designs → restraint, precision, careful spacing and typography
+- Elegance comes from executing the chosen vision fully, not halfway
+
+## Constraints
+- Respect existing design systems when present
+- Leverage component libraries where available
+- Prioritize visual excellence-code perfection comes second
+- Use grounded, normal, regular english - don't use jargon or overly technical language
+
+${WRITABLE_FILE_OPERATIONS_RULES}
+
+## Review Responsibilities
+- Review existing UI for usability, responsiveness, visual consistency, and polish when asked
+- Call out concrete UX issues and improvements, not just abstract design advice
+
+## Verification
+- Run only validation assigned by the Orchestrator; do not broaden it
+  automatically.
+- Report validation results and skips accurately.
+- Assigned validation should be user-visible.
+
+## Output Quality
+You're capable of extraordinary creative work. Commit fully to distinctive visions and show what's possible when breaking conventions thoughtfully.`;
+
+const FIXER_PROMPT = `You are Fixer - a fast, focused implementation specialist.
+
+**Role**: Execute code changes efficiently. You receive complete context from research agents and clear task specifications from the Orchestrator. Your job is to implement, not plan or research.
+
+**Behavior**:
+- Execute the task specification provided by the Orchestrator
+- Report completion with summary of changes
+
+${WRITABLE_FILE_OPERATIONS_RULES}
+
+**Constraints**:
+- NO external research (no context7, gh_grep)
+- NO spawning subagents; telling the caller which specialist to use is fine
+- No multi-step research/planning; minimal execution sequence ok
+- If context is insufficient: use grep/glob/read directly - do not delegate
+- Only ask for missing inputs you truly cannot retrieve yourself
+- Do not act as the primary reviewer; implement requested changes and surface obvious issues briefly
+- No design work — layout, styling, visual hierarchy, responsive behavior, animation, component feel. Refuse and tell the caller to use @designer.
+
+**Verification**:
+- Run only validation assigned by the Orchestrator; do not broaden it
+  automatically.
+- Report validation results and skips accurately.
+
+**Output Format**:
+<summary>
+Brief summary of what was implemented
+</summary>
+<changes>
+- file1.ts: Changed X to Y
+- file2.ts: Added Z function
+</changes>
+<verification>
+- Performed: [command/check, or skipped with reason]
+- Result: [passed/failed/unknown]
+</verification>
+
+`;
+
+const OBSERVER_PROMPT = `You are Observer - a visual analysis specialist.
+
+**Role**: Interpret images, screenshots, PDFs, and diagrams. Extract structured observations for the Orchestrator to act on.
+
+**Behavior**:
+- Read the file(s) specified in the prompt
+- Analyze visual content - layouts, UI elements, text, relationships, flows
+- For screenshots with text/code/errors: extract the **exact text** via OCR - never paraphrase error messages or code
+- For multiple files: analyze each, then compare or relate as requested
+- Return ONLY the extracted information relevant to the goal
+- If the image is unclear, blurry, or partially visible: state what you CAN see and explicitly note what is uncertain - never guess or fabricate details
+
+**Constraints**:
+- READ-ONLY: Analyze and report, don't modify files
+- Save context tokens - the Orchestrator never processes the raw file
+- Match the language of the request
+- If info not found, state clearly what's missing
+
+${READONLY_FILE_OPERATIONS_RULES}
+`;
+
+const ROLE_ROUTING_BLOCKS: Record<SpecialistRole, string> = {
+  explorer: `@explorer
+- Lane: Fast codebase recon that returns compressed context
+- Permissions: read_files
+- Stats: 2x faster codebase search than orchestrator, 1/2 cost of orchestrator
+- Capabilities: Glob, grep, AST queries to locate files, symbols, patterns
+- **Delegate when:** Need to discover what exists before planning • Parallel searches speed discovery • Need summarized map vs full contents • Broad/uncertain scope
+- **Don't delegate when:** Know the path and need actual content • Need full file anyway • Single specific lookup • About to edit the file`,
+  librarian: `@librarian
+- Lane: External knowledge and library research, fast web research
+- Role: Authoritative source for current library docs, API references, examples, bug investigations, and web retrieval
+- Stats: 2x faster web research than orchestrator, 1/2 cost of orchestrator
+- **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) • Complex APIs needing official examples (ORMs, auth) • Version-specific behavior matters • Unfamiliar library • Edge cases or advanced features • Nuanced best practices • Working on fixing tricky bug or problem and need latest web research information
+- **Don't delegate when:** Standard usage you're confident • Simple stable APIs • General programming knowledge • Info already in conversation • Built-in language features
+- **Rule of thumb:** "How does this library work?" → @librarian. "How does programming work?" → answer directly. "How do others solve or workaround this tricky issue?" → @librarian.`,
+  oracle: `@oracle
+- Lane: Architecture, risk, debugging strategy, and review
+- Role: Strategic advisor for high-stakes decisions and persistent problems, code reviewer
+- Permissions: read_files
+- Stats: 5x better decision maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
+- Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review, simplification, maintainability review
+- **Delegate when:** Major architectural decisions with long-term impact • Problems persisting after 2+ fix attempts • High-risk multi-system refactors • Costly trade-offs (performance vs maintainability) • Complex debugging with unclear root cause • Security/scalability/data integrity decisions • Genuinely uncertain and cost of wrong choice is high • Code needs simplification or YAGNI scrutiny
+- **Review use:** @oracle is an escalation, not a default verification step. Request independent @oracle review only when its analysis is expected to materially reduce risk or uncertainty.
+- **Don't delegate when:** Routine decisions you're confident about • First bug fix attempt • Straightforward trade-offs • Tactical "how" vs strategic "should" • Time-sensitive good-enough decisions • Quick research/testing can answer
+- **Rule of thumb:** Need senior architect review? → @oracle. Need code review or simplification? → @oracle. Routine coordination or final synthesis? → handle directly.`,
+  designer: `@designer
+- Lane: UI/UX design, related edits, design polish and review
+- Permissions: read_files, write_files
+- Stats: 10x better UI/UX than orchestrator
+- Capabilities: Good design taste, visual relevant edits, interactions, responsive layouts, design systems with aesthetic intent, deep UI/UX knowledge.
+- Owns visual and interaction quality: layout, hierarchy, spacing, motion, affordances, responsive behavior, and overall feel.
+- Weakness: copywriting. Ask @designer to use grounded, normal wording, then have orchestrator review/fix copy after design work without changing visual or interaction intent.
+- Avoid: "Let me ask @designer how it should look and implement yourself" → instead: "Let me ask @designer to design and implement the UI/UX changes for me"
+- **Delegate when:** User-facing interfaces needing polish • Responsive layouts • UX-critical components (forms, nav, dashboards) • Visual consistency systems • Animations/micro-interactions • Landing/marketing pages • Refining functional→delightful • Reviewing existing UI/UX quality
+- **Don't delegate when:** Backend/logic with no visual • Quick prototypes where design doesn't matter yet.`,
+  fixer: `@fixer
+- Lane: Bounded implementation and executioner
+- Role: Fast execution specialist for well-defined tasks
+- Permissions: read_files, write_files
+- Stats: 2x faster code edits, 1/2 cost of orchestrator
+- Weakness: design, taste
+- Tools/Constraints: Execution-focused-no research, no architectural decisions
+- **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer • Parallelization benefits: Task involves multiple folders and multiple files modification, scoping work per folder and spawning parallel @fixer instances for each folder.
+- **Don't delegate when:** Needs discovery/research/decisions • Single small change (<20 lines, one file) • Unclear requirements needing iteration • Explaining to @fixer > doing • Tight integration with your current work • Requires design taste, visual hierarchy, interaction polish, responsive layout decisions, animation/motion, component feel, or UI copy/design trade-offs
+- **Rule of thumb:** Headless/mechanical implementation → @fixer. User-visible design or polish → @designer. If @designer already set direction, @fixer may only do bounded mechanical follow-up that preserves that design exactly.`,
+  observer: `@observer
+- Lane: Visual/media analysis isolated from orchestrator context
+- Role: Visual analysis specialist for images, PDFs, and diagrams
+- Permissions: Read files
+- Stats: Saves main context tokens - @observer processes raw files, returns structured observations
+- Capabilities: Interprets images, screenshots, PDFs, and diagrams via native read tool; extracts UI elements, layouts, text, relationships
+- **Delegate when:** Need to analyze a multimedia file• Extract information
+- **Don't delegate when:** Plain text files that Read can handle directly • Files that need editing afterward (need literal content from Read)
+- **Rule of thumb:** Even if your model supports vision, delegate visual analysis to @observer - it isolates large image/PDF bytes from your context window, returning only concise structured text. Need exact file contents for routing? → Read only the minimal context yourself.
+- **IMPORTANT:** When delegating to @observer, always include the **full file path** in the prompt so it can read the file. Example: "Analyze the screenshot at /path/to/file.png - describe the UI elements and error messages."`,
+};
+
+function defineRole(
+  id: SpecialistRole,
+  basePrompt: string,
+  description: string,
+  defaultModel: string | undefined,
+  defaultSkills: readonly string[],
+  defaultMcps: readonly string[],
+  permissionPolicy: RoleDefinition['permissionPolicy'],
+): RoleDefinition {
+  const role = {
+    id,
+    basePrompt,
+    description,
+    derivable: true,
+    profileTargetable: true,
+    defaultModel,
+    defaultSkills: Object.freeze([...defaultSkills]),
+    defaultMcps: Object.freeze([...defaultMcps]),
+    permissionPolicy,
+    routingBlock: ROLE_ROUTING_BLOCKS[id],
+    createBaseline: undefined as unknown as RoleDefinition['createBaseline'],
+  } satisfies Omit<RoleDefinition, 'createBaseline'> & {
+    createBaseline: RoleDefinition['createBaseline'];
+  };
+  role.createBaseline = createBaselineFactory(role);
+  return Object.freeze(role);
+}
+
+export const ROLE_DEFINITIONS: Readonly<
+  Record<SpecialistRole, RoleDefinition>
+> = Object.freeze({
+  explorer: defineRole(
+    'explorer',
+    EXPLORER_PROMPT,
+    "Fast codebase search and pattern matching. Use for finding files, locating code patterns, and answering 'where is X?' questions.",
+    DEFAULT_MODELS.explorer,
+    [],
+    DEFAULT_AGENT_MCPS.explorer,
+    'read-only',
+  ),
+  librarian: defineRole(
+    'librarian',
+    LIBRARIAN_PROMPT,
+    'External documentation and library research. Use for official docs lookup, GitHub examples, and understanding library internals.',
+    DEFAULT_MODELS.librarian,
+    [],
+    DEFAULT_AGENT_MCPS.librarian,
+    'read-only',
+  ),
+  oracle: defineRole(
+    'oracle',
+    ORACLE_PROMPT,
+    'Strategic technical advisor. Use for architecture decisions, complex debugging, code review, simplification, and engineering guidance.',
+    DEFAULT_MODELS.oracle,
+    ['simplify', 'requesting-code-review'],
+    DEFAULT_AGENT_MCPS.oracle,
+    'read-only',
+  ),
+  designer: defineRole(
+    'designer',
+    DESIGNER_PROMPT,
+    'UI/UX design, review, and implementation. Use for styling, responsive design, component architecture and visual polish.',
+    DEFAULT_MODELS.designer,
+    [],
+    DEFAULT_AGENT_MCPS.designer,
+    'read-write',
+  ),
+  fixer: defineRole(
+    'fixer',
+    FIXER_PROMPT,
+    'Fast implementation specialist. Receives complete context and task spec, executes code changes efficiently.',
+    DEFAULT_MODELS.fixer,
+    [],
+    DEFAULT_AGENT_MCPS.fixer,
+    'read-write',
+  ),
+  observer: defineRole(
+    'observer',
+    OBSERVER_PROMPT,
+    'Visual analysis. Use for interpreting images, screenshots, PDFs, and diagrams - extracts structured observations without loading raw files into main context. Requires a vision-capable model.',
+    DEFAULT_MODELS.observer,
+    [],
+    DEFAULT_AGENT_MCPS.observer,
+    'read-only',
+  ),
+});
+
+export function getRoleDefinition(role: string): RoleDefinition | undefined {
+  return ROLE_DEFINITIONS[role as SpecialistRole];
+}

+ 4 - 0
src/cache-safety-tripwire.test.ts

@@ -74,6 +74,10 @@ const ALLOWLIST = new Map<string, string>([
     'hooks/image-hook.ts',
     'Date.now() throttles temp-image cleanup; extracted image paths are deterministic per part id.',
   ],
+  [
+    'hooks/orchestrator-wake/index.ts',
+    'Date.now() timestamps event-tracked child/status bookkeeping for wake decisions (staleness bound, busy-set); the wake prompt text is a static constant and never derives from them.',
+  ],
   [
     'hooks/auto-update-checker/cache.ts',
     'Date.now() and process.pid name an on-disk quarantine directory during the atomic publish transaction; the path is filesystem bookkeeping, never serialized into prompt content.',

+ 61 - 13
src/cli/config-io.test.ts

@@ -17,6 +17,7 @@ import {
   detectCurrentConfig,
   disableDefaultAgents,
   enableLspByDefault,
+  mutateJsonFile,
   parseConfig,
   parseConfigFile,
   stripJsonComments,
@@ -116,6 +117,45 @@ describe('config-io', () => {
     expect(result.config).toEqual({ a: 1 } as any);
   });
 
+  test('mutateJsonFile read-modify-writes under a unique temp', () => {
+    const path = join(tmpDir, 'mutate.json');
+    writeFileSync(path, JSON.stringify({ keep: true, count: 1 }));
+    mutateJsonFile(path, (current) => {
+      const value = current as { keep: boolean; count: number };
+      return { ...value, count: value.count + 1 };
+    });
+    expect(JSON.parse(readFileSync(path, 'utf-8'))).toEqual({
+      keep: true,
+      count: 2,
+    });
+    expect(JSON.parse(readFileSync(`${path}.bak`, 'utf-8'))).toEqual({
+      keep: true,
+      count: 1,
+    });
+    expect(existsSync(`${path}.lock`)).toBe(false);
+  });
+
+  test('mutateJsonFile warns before replacing JSONC comments', () => {
+    const path = join(tmpDir, 'mutate.jsonc');
+    writeFileSync(path, '{\n  // retain this manually\n  "count": 1\n}\n');
+    const warn = mock(() => {});
+    const originalWarn = console.warn;
+    console.warn = warn;
+
+    try {
+      mutateJsonFile(path, (current) => ({
+        ...(current as Record<string, unknown>),
+        count: 2,
+      }));
+    } finally {
+      console.warn = originalWarn;
+    }
+
+    expect(warn).toHaveBeenCalledWith(
+      '[config-manager] Writing to .jsonc file - comments will not be preserved',
+    );
+  });
+
   test('writeConfig writes JSON and creates backup', () => {
     const path = join(tmpDir, 'test.json');
     writeFileSync(path, '{"old": true}');
@@ -509,14 +549,18 @@ describe('config-io', () => {
     expect(saved.preset).toBe('opencode-go');
     expect(saved.disabled_agents).toEqual([]);
     expect(saved.presets.openai).toBeDefined();
-    expect(saved.presets['opencode-go'].orchestrator.model).toBe(
+    expect(saved.presets['opencode-go'].agents.orchestrator.model).toBe(
       'opencode-go/minimax-m3',
     );
-    expect(saved.presets['opencode-go'].orchestrator.variant).toBe('thinking');
-    expect(saved.presets['opencode-go'].observer.model).toBe(
+    expect(saved.presets['opencode-go'].agents.orchestrator.variant).toBe(
+      'thinking',
+    );
+    expect(saved.presets['opencode-go'].agents.observer.model).toBe(
       'opencode-go/mimo-v2.5',
     );
-    expect(saved.presets['opencode-go'].observer.variant).toBeUndefined();
+    expect(
+      saved.presets['opencode-go'].agents.observer.variant,
+    ).toBeUndefined();
   });
 
   test('disableDefaultAgents disables conflicting OpenCode built-in agents', () => {
@@ -615,10 +659,12 @@ describe('config-io', () => {
         preset: 'openai',
         presets: {
           openai: {
-            orchestrator: { model: 'openai/gpt-4' },
-            oracle: { model: 'anthropic/claude-opus-4-6' },
-            explorer: { model: 'github-copilot/grok-code-fast-1' },
-            librarian: { model: 'zai-coding-plan/glm-4.7' },
+            agents: {
+              orchestrator: { model: 'openai/gpt-4' },
+              oracle: { model: 'anthropic/claude-opus-4-6' },
+              explorer: { model: 'github-copilot/grok-code-fast-1' },
+              librarian: { model: 'zai-coding-plan/glm-4.7' },
+            },
           },
         },
       }),
@@ -668,11 +714,13 @@ describe('config-io', () => {
         preset: 'dev',
         presets: {
           dev: {
-            orchestrator: {
-              model: [
-                'openai/gpt-5.6-luna',
-                { id: 'anthropic/claude-opus-4-6' },
-              ],
+            agents: {
+              orchestrator: {
+                model: [
+                  'openai/gpt-5.6-luna',
+                  { id: 'anthropic/claude-opus-4-6' },
+                ],
+              },
             },
           },
         },

+ 134 - 60
src/cli/config-io.ts

@@ -3,13 +3,19 @@ import {
   existsSync,
   mkdirSync,
   readFileSync,
-  renameSync,
   rmSync,
   statSync,
   writeFileSync,
 } from 'node:fs';
 import { homedir } from 'node:os';
-import { dirname, join } from 'node:path';
+import { basename, dirname, join } from 'node:path';
+import {
+  parseJsonConfig,
+  parseJsonConfigFile,
+  stripJsonComments,
+} from '../config/jsonc';
+import { withMarketplaceLease, writeAtomic } from '../marketplace/lease';
+import type { MarketplacePaths } from '../marketplace/paths';
 import {
   INSTALLER_MANAGED_PLUGIN_OPTION,
   type PluginEntry,
@@ -31,6 +37,8 @@ import type {
   OpenCodeConfig,
 } from './types';
 
+export { stripJsonComments } from '../config/jsonc';
+
 const PACKAGE_NAME = 'oh-my-opencode-slim';
 const DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ['explore', 'general'] as const;
 
@@ -380,53 +388,142 @@ export async function warmOpenCodePluginCache(): Promise<ConfigMergeResult | nul
   }
 }
 
-/**
- * Strip JSON comments (single-line // and multi-line) and trailing commas for JSONC support.
- */
-export function stripJsonComments(json: string): string {
-  const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
-  const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
-
-  return json
-    .replace(commentPattern, (match, commentGroup) =>
-      commentGroup ? '' : match,
-    )
-    .replace(trailingCommaPattern, (match, comma, closing) =>
-      comma ? closing : match,
-    );
-}
-
 export function parseConfigFile(path: string): {
   config: OpenCodeConfig | null;
   error?: string;
 } {
-  try {
-    if (!existsSync(path)) return { config: null };
-    const stat = statSync(path);
-    if (stat.size === 0) return { config: null };
-    // Strip a UTF-8 BOM (RFC 8259 permits one) so JSON.parse does not choke.
-    const content = readFileSync(path, 'utf-8').replace(/^\uFEFF/, '');
-    if (content.trim().length === 0) return { config: null };
-    return { config: JSON.parse(stripJsonComments(content)) as OpenCodeConfig };
-  } catch (err) {
-    return { config: null, error: String(err) };
-  }
+  const result = parseJsonConfigFile(path);
+  return {
+    config: result.config as OpenCodeConfig | null,
+    error: result.error,
+  };
 }
 
 export function parseConfig(path: string): {
   config: OpenCodeConfig | null;
   error?: string;
 } {
-  const result = parseConfigFile(path);
-  if (result.config || result.error) return result;
+  const result = parseJsonConfig(path);
+  if (result.config || result.error) {
+    return {
+      config: result.config as OpenCodeConfig | null,
+      error: result.error,
+    };
+  }
 
   if (path.endsWith('.json')) {
     const jsoncPath = path.replace(/\.json$/, '.jsonc');
-    return parseConfigFile(jsoncPath);
+    return parseJsonConfigFile(jsoncPath) as {
+      config: OpenCodeConfig | null;
+      error?: string;
+    };
   }
   return { config: null };
 }
 
+function errnoCode(error: unknown): string | undefined {
+  if (!error || typeof error !== 'object') return undefined;
+  const code = (error as { code?: unknown }).code;
+  return typeof code === 'string' ? code : undefined;
+}
+
+function sleepSync(milliseconds: number): void {
+  const sleeper = new Int32Array(new SharedArrayBuffer(4));
+  Atomics.wait(sleeper, 0, 0, milliseconds);
+}
+
+function readFileBytes(filePath: string): string | null {
+  try {
+    return readFileSync(filePath, 'utf8');
+  } catch (error) {
+    if (errnoCode(error) === 'ENOENT') return null;
+    throw error;
+  }
+}
+
+function configMutationPaths(filePath: string): MarketplacePaths {
+  const root = join(dirname(filePath), `.${basename(filePath)}.write-lock`);
+  return {
+    rootDir: root,
+    packagesDir: join(root, 'packages'),
+    lockfilePath: join(root, 'lock.json'),
+    lockDir: join(root, 'lock'),
+    breakerDir: join(root, 'lock.breaker'),
+    stagingDir: join(root, '.staging'),
+  };
+}
+
+function testMutationBarrier(point: string): void {
+  const dir = process.env.CONFIG_MUTATION_BARRIER_DIR;
+  if (!dir) return;
+  const waitPath = join(dir, `${point}.wait`);
+  try {
+    statSync(waitPath);
+  } catch {
+    return;
+  }
+  writeAtomic(join(dir, `${point}.reached`), `${process.pid}\n`);
+  const goPath = join(dir, `${point}.go`);
+  while (true) {
+    try {
+      statSync(goPath);
+      return;
+    } catch {
+      sleepSync(10);
+    }
+  }
+}
+
+function publishJsonFile(filePath: string, content: string): void {
+  mkdirSync(dirname(filePath), { recursive: true });
+  if (existsSync(filePath)) {
+    copyFileSync(filePath, `${filePath}.bak`);
+  }
+  writeAtomic(filePath, content);
+}
+
+function withSerializedConfigWrite<T>(filePath: string, operation: () => T): T {
+  return withMarketplaceLease(
+    configMutationPaths(filePath),
+    (lease) => lease.commit(operation),
+    {},
+  );
+}
+
+/**
+ * Whole-file read-modify-write serialized by the generation-safe lease.
+ */
+export function mutateJsonFile(
+  filePath: string,
+  mutate: (current: unknown) => unknown,
+): void {
+  withSerializedConfigWrite(filePath, () => {
+    if (filePath.endsWith('.jsonc')) {
+      console.warn(
+        '[config-manager] Writing to .jsonc file - comments will not be preserved',
+      );
+    }
+    const original = readFileBytes(filePath);
+    const parsed =
+      original === null
+        ? null
+        : (JSON.parse(
+            stripJsonComments(original.replace(/^\uFEFF/, '')),
+          ) as unknown);
+    testMutationBarrier('config-rmw-read');
+    publishJsonFile(filePath, `${JSON.stringify(mutate(parsed), null, 2)}\n`);
+  });
+}
+
+/**
+ * Write JSON to disk atomically under the generation-safe lease.
+ */
+export function writeJsonAtomic(filePath: string, value: unknown): void {
+  withSerializedConfigWrite(filePath, () => {
+    publishJsonFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
+  });
+}
+
 /**
  * Write config to file atomically.
  */
@@ -436,19 +533,7 @@ export function writeConfig(configPath: string, config: OpenCodeConfig): void {
       '[config-manager] Writing to .jsonc file - comments will not be preserved',
     );
   }
-
-  const tmpPath = `${configPath}.tmp`;
-  const bakPath = `${configPath}.bak`;
-  const content = `${JSON.stringify(config, null, 2)}\n`;
-
-  // Backup existing config if it exists
-  if (existsSync(configPath)) {
-    copyFileSync(configPath, bakPath);
-  }
-
-  // Atomic write pattern: write to tmp, then rename
-  writeFileSync(tmpPath, content);
-  renameSync(tmpPath, configPath);
+  writeJsonAtomic(configPath, config);
 }
 
 export async function addPluginToOpenCodeConfig(): Promise<ConfigMergeResult> {
@@ -553,19 +638,7 @@ export function writeLiteConfig(
   try {
     ensureConfigDir();
     const config = generateLiteConfig(installConfig);
-
-    // Atomic write for lite config too
-    const tmpPath = `${configPath}.tmp`;
-    const bakPath = `${configPath}.bak`;
-    const content = `${JSON.stringify(config, null, 2)}\n`;
-
-    // Backup existing config if it exists
-    if (existsSync(configPath)) {
-      copyFileSync(configPath, bakPath);
-    }
-
-    writeFileSync(tmpPath, content);
-    renameSync(tmpPath, configPath);
+    writeJsonAtomic(configPath, config);
 
     return { success: true, configPath };
   } catch (err) {
@@ -685,9 +758,10 @@ export function detectCurrentConfig(): DetectedConfig {
     const configObj = liteConfig as Record<string, unknown>;
     const presetName = configObj.preset as string;
     const presets = configObj.presets as Record<string, unknown>;
-    const agents = presets?.[presetName] as
-      | Record<string, { model?: unknown }>
+    const preset = presets?.[presetName] as
+      | { agents?: Record<string, { model?: unknown }> }
       | undefined;
+    const agents = preset?.agents;
 
     if (agents && typeof agents === 'object') {
       const models = Object.values(agents)

+ 11 - 9
src/cli/doctor.test.ts

@@ -297,7 +297,7 @@ describe('runDoctorCheck', () => {
       path.join(configDir, 'oh-my-opencode-slim.json'),
       JSON.stringify({
         preset: 'mypreset',
-        presets: { mypreset: { oracle: { model: 'test/model' } } },
+        presets: { mypreset: { agents: { oracle: { model: 'test/model' } } } },
       }),
     );
 
@@ -316,7 +316,7 @@ describe('runDoctorCheck', () => {
       path.join(configDir, 'oh-my-opencode-slim.json'),
       JSON.stringify({
         preset: 'mypreset',
-        presets: { mypreset: { oracle: { model: 'test/model' } } },
+        presets: { mypreset: { agents: { oracle: { model: 'test/model' } } } },
       }),
     );
 
@@ -334,7 +334,7 @@ describe('runDoctorCheck', () => {
       path.join(configDir, 'oh-my-opencode-slim.json'),
       JSON.stringify({
         preset: 'nonexistent',
-        presets: { other: {} },
+        presets: { other: { agents: {} } },
       }),
     );
 
@@ -354,8 +354,8 @@ describe('runDoctorCheck', () => {
       JSON.stringify({
         preset: 'config-preset',
         presets: {
-          'config-preset': { oracle: { model: 'config/model' } },
-          'env-preset': { oracle: { model: 'env/model' } },
+          'config-preset': { agents: { oracle: { model: 'config/model' } } },
+          'env-preset': { agents: { oracle: { model: 'env/model' } } },
         },
       }),
     );
@@ -378,8 +378,10 @@ describe('runDoctorCheck', () => {
         agents: { oracle: { temperature: 0.5 } },
         presets: {
           'test-preset': {
-            oracle: { model: 'user/model' },
-            explorer: { model: 'user/explorer' },
+            agents: {
+              oracle: { model: 'user/model' },
+              explorer: { model: 'user/explorer' },
+            },
           },
         },
       }),
@@ -433,8 +435,8 @@ describe('runDoctorCheck', () => {
       JSON.stringify({
         preset: 'user-preset',
         presets: {
-          'user-preset': { oracle: { model: 'user/model' } },
-          'project-preset': { oracle: { model: 'project/model' } },
+          'user-preset': { agents: { oracle: { model: 'user/model' } } },
+          'project-preset': { agents: { oracle: { model: 'project/model' } } },
         },
       }),
     );

+ 16 - 0
src/cli/index.ts

@@ -1,6 +1,7 @@
 #!/usr/bin/env node
 import { doctor, parseDoctorArgs } from './doctor';
 import { install } from './install';
+import { marketplaceCommand } from './marketplace';
 import { getGeneratedPresetNames, isGeneratedPresetName } from './providers';
 import type {
   BackgroundSubagentsArg,
@@ -75,6 +76,18 @@ oh-my-opencode-slim installer
 Usage:
   bunx oh-my-opencode-slim install [OPTIONS]
   bunx oh-my-opencode-slim doctor [OPTIONS]
+  bunx oh-my-opencode-slim marketplace install <package.json>
+  bunx oh-my-opencode-slim marketplace import <package.json> (alias for install)
+  bunx oh-my-opencode-slim marketplace update <package.json>
+  bunx oh-my-opencode-slim marketplace list
+  bunx oh-my-opencode-slim marketplace show <package-id> [--json]
+  bunx oh-my-opencode-slim marketplace verify [package-id] [--json]
+  bunx oh-my-opencode-slim marketplace remove <package-id> [--force]
+  bunx oh-my-opencode-slim marketplace enable <package-id>
+  bunx oh-my-opencode-slim marketplace disable <package-id>
+  bunx oh-my-opencode-slim marketplace profile <role> <package-id>
+  bunx oh-my-opencode-slim marketplace profile <role> --clear
+  bunx oh-my-opencode-slim marketplace status [--json]
 
 Options:
   --skills=yes|no|force  Install bundled skills; force replaces existing skill
@@ -123,6 +136,9 @@ async function main(): Promise<void> {
     const doctorArgs = parseDoctorArgs(args.slice(1));
     const exitCode = await doctor(doctorArgs);
     process.exit(exitCode);
+  } else if (args[0] === 'marketplace') {
+    const exitCode = await marketplaceCommand(args.slice(1));
+    process.exit(exitCode);
   } else if (args[0] === '-h' || args[0] === '--help') {
     printHelp();
     process.exit(0);

+ 63 - 0
src/cli/marketplace.test.ts

@@ -0,0 +1,63 @@
+import { describe, expect, test } from 'bun:test';
+import { parseMarketplaceArgs } from './marketplace';
+
+describe('marketplace CLI parsing', () => {
+  test('parses lifecycle commands and the import alias', () => {
+    expect(parseMarketplaceArgs(['import', './package.json'])).toEqual({
+      command: 'install',
+      value: './package.json',
+      force: false,
+      json: false,
+      clear: false,
+    });
+  });
+
+  test('requires package input for install and update', () => {
+    expect(() => parseMarketplaceArgs(['install'])).toThrow();
+    expect(() => parseMarketplaceArgs(['update'])).toThrow();
+    expect(parseMarketplaceArgs(['list'])).toEqual({
+      command: 'list',
+      value: undefined,
+      force: false,
+      json: false,
+      clear: false,
+    });
+    expect(() =>
+      parseMarketplaceArgs(['remove', 'community/example', '--json']),
+    ).toThrow();
+  });
+
+  test('parses enable, disable, and profile commands', () => {
+    expect(parseMarketplaceArgs(['enable', 'community/example'])).toEqual({
+      command: 'enable',
+      value: 'community/example',
+      force: false,
+      json: false,
+      clear: false,
+    });
+    expect(
+      parseMarketplaceArgs(['profile', 'librarian', 'community/deep']),
+    ).toEqual({
+      command: 'profile',
+      role: 'librarian',
+      value: 'community/deep',
+      force: false,
+      json: false,
+      clear: false,
+    });
+    expect(parseMarketplaceArgs(['profile', 'oracle', '--clear'])).toEqual({
+      command: 'profile',
+      role: 'oracle',
+      force: false,
+      json: false,
+      clear: true,
+    });
+    expect(parseMarketplaceArgs(['status', '--json'])).toEqual({
+      command: 'status',
+      value: undefined,
+      force: false,
+      json: true,
+      clear: false,
+    });
+  });
+});

+ 247 - 0
src/cli/marketplace.ts

@@ -0,0 +1,247 @@
+import {
+  MarketplaceService,
+  type MarketplaceServiceOptions,
+} from '../marketplace';
+import {
+  disableMarketplacePackage,
+  enableMarketplaceAgent,
+  setMarketplaceProfile,
+} from '../marketplace/activation-config';
+import {
+  collectMarketplaceStatus,
+  formatMarketplaceStatus,
+  mutationReloadNotice,
+} from '../marketplace/status';
+
+export type MarketplaceCommandName =
+  | 'install'
+  | 'list'
+  | 'show'
+  | 'verify'
+  | 'update'
+  | 'remove'
+  | 'enable'
+  | 'disable'
+  | 'profile'
+  | 'status';
+
+export interface MarketplaceArgs {
+  command: MarketplaceCommandName;
+  value?: string;
+  role?: string;
+  force: boolean;
+  json: boolean;
+  clear: boolean;
+}
+
+function commandSet(): readonly string[] {
+  return [
+    'install',
+    'import',
+    'list',
+    'show',
+    'verify',
+    'update',
+    'remove',
+    'enable',
+    'disable',
+    'profile',
+    'status',
+  ];
+}
+
+export function parseMarketplaceArgs(args: string[]): MarketplaceArgs {
+  const rawCommand = args[0];
+  if (!rawCommand || !commandSet().includes(rawCommand)) {
+    throw new Error(
+      'Usage: marketplace install|import|list|show|verify|update|remove|enable|disable|profile|status [value] [options]',
+    );
+  }
+  const command: MarketplaceCommandName =
+    rawCommand === 'import'
+      ? 'install'
+      : (rawCommand as MarketplaceCommandName);
+  const force = args.includes('--force');
+  const json = args.includes('--json');
+  const clear = args.includes('--clear');
+  const options = args.filter((arg) => arg.startsWith('--'));
+  const allowedOptions = new Set(
+    command === 'remove'
+      ? ['--force']
+      : command === 'verify' || command === 'show' || command === 'status'
+        ? ['--json']
+        : command === 'profile'
+          ? ['--clear']
+          : [],
+  );
+  for (const option of options) {
+    if (!allowedOptions.has(option)) {
+      throw new Error(
+        `Option ${option} is not valid for marketplace ${rawCommand}`,
+      );
+    }
+  }
+
+  const positional = args.slice(1).filter((arg) => !arg.startsWith('--'));
+  if (command === 'profile') {
+    if (!positional[0]) {
+      throw new Error('marketplace profile requires a specialist role');
+    }
+    if (clear) {
+      if (positional.length > 1) {
+        throw new Error('marketplace profile --clear accepts only a role');
+      }
+      return { command, role: positional[0], force, json, clear };
+    }
+    if (!positional[1] || positional.length > 2) {
+      throw new Error('marketplace profile requires a role and package ID');
+    }
+    return {
+      command,
+      role: positional[0],
+      value: positional[1],
+      force,
+      json,
+      clear,
+    };
+  }
+  const needsValue = [
+    'install',
+    'show',
+    'update',
+    'remove',
+    'enable',
+    'disable',
+  ].includes(command);
+  const allowsValue = needsValue || command === 'verify';
+  if (positional.length > (allowsValue ? 1 : 0)) {
+    throw new Error(
+      `marketplace ${rawCommand} accepts ${needsValue ? 'one' : 'no more than one'} value`,
+    );
+  }
+  if (needsValue && !positional[0]) {
+    throw new Error(`marketplace ${rawCommand} requires a value`);
+  }
+  if ((command === 'install' || command === 'update') && force) {
+    throw new Error(
+      `Option --force is not valid for marketplace ${rawCommand}`,
+    );
+  }
+  return { command, value: positional[0], force, json, clear };
+}
+
+export async function marketplaceCommand(
+  args: string[],
+  options: MarketplaceServiceOptions = {},
+): Promise<number> {
+  try {
+    const parsed = parseMarketplaceArgs(args);
+    const service = new MarketplaceService(options);
+    const projectDir = options.projectDir ?? process.cwd();
+    switch (parsed.command) {
+      case 'install': {
+        const pkg = service.installFile(parsed.value as string);
+        console.log(
+          mutationReloadNotice(
+            `Installed ${pkg.manifest.id}@${pkg.manifest.version}`,
+            'unknown',
+          ),
+        );
+        return 0;
+      }
+      case 'update': {
+        const pkg = service.updateFile(parsed.value as string);
+        console.log(
+          mutationReloadNotice(
+            `Updated ${pkg.manifest.id}@${pkg.manifest.version}`,
+            'unknown',
+          ),
+        );
+        return 0;
+      }
+      case 'list':
+        for (const pkg of service.list()) {
+          console.log(
+            `${pkg.manifest.id}@${pkg.manifest.version}\t${pkg.manifest.kind}\t${pkg.manifest.displayName}`,
+          );
+        }
+        return 0;
+      case 'show': {
+        const pkg = service.show(parsed.value as string);
+        console.log(
+          parsed.json
+            ? JSON.stringify(pkg, null, 2)
+            : `${pkg.manifest.id}@${pkg.manifest.version}\n${pkg.manifest.description}\nsource: ${JSON.stringify(pkg.source)}`,
+        );
+        return 0;
+      }
+      case 'verify': {
+        const results = service.verify(parsed.value);
+        if (parsed.json) console.log(JSON.stringify(results, null, 2));
+        else {
+          for (const result of results) {
+            console.log(`${result.valid ? 'OK' : 'FAIL'} ${result.message}`);
+          }
+        }
+        return results.every((result) => result.valid) ? 0 : 1;
+      }
+      case 'remove':
+        service.remove(parsed.value as string, { force: parsed.force });
+        console.log(mutationReloadNotice(`Removed ${parsed.value}`, 'unknown'));
+        return 0;
+      case 'enable':
+        enableMarketplaceAgent(
+          projectDir,
+          parsed.value as string,
+          service.store,
+        );
+        console.log(
+          mutationReloadNotice(
+            `Enabled ${parsed.value} in the active preset`,
+            'unknown',
+          ),
+        );
+        return 0;
+      case 'disable':
+        disableMarketplacePackage(projectDir, parsed.value as string);
+        console.log(
+          mutationReloadNotice(
+            `Disabled ${parsed.value} in the active preset`,
+            'unknown',
+          ),
+        );
+        return 0;
+      case 'profile':
+        setMarketplaceProfile(
+          projectDir,
+          parsed.role as string,
+          parsed.clear ? null : (parsed.value as string),
+          service.store,
+        );
+        console.log(
+          mutationReloadNotice(
+            parsed.clear
+              ? `Cleared the ${parsed.role} profile`
+              : `Selected ${parsed.value} for ${parsed.role}`,
+            'unknown',
+          ),
+        );
+        return 0;
+      case 'status': {
+        const report = collectMarketplaceStatus({
+          service,
+          projectDir,
+        });
+        console.log(
+          parsed.json
+            ? JSON.stringify(report, null, 2)
+            : formatMarketplaceStatus(report),
+        );
+        return 0;
+      }
+    }
+  } catch (error) {
+    console.error(error instanceof Error ? error.message : String(error));
+    return 1;
+  }
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff