ソースを参照

Updated OpenCode to v1.0.133

paviko 2 ヶ月 前
コミット
7e15b4b86b
100 ファイル変更3615 行追加1327 行削除
  1. 0 58
      .github/guidelines-check.yml
  2. 1 1
      .github/workflows/auto-label-tui.yml
  3. 1 1
      .github/workflows/deploy.yml
  4. 4 1
      .github/workflows/duplicate-issues.yml
  5. 1 1
      .github/workflows/format.yml
  6. 1 1
      .github/workflows/notify-discord.yml
  7. 1 1
      .github/workflows/opencode.yml
  8. 1 1
      .github/workflows/publish-github-action.yml
  9. 1 1
      .github/workflows/publish-vscode.yml
  10. 1 1
      .github/workflows/publish.yml
  11. 78 0
      .github/workflows/review.yml
  12. 1 1
      .github/workflows/snapshot.yml
  13. 1 1
      .github/workflows/stats.yml
  14. 1 1
      .github/workflows/sync-zed-extension.yml
  15. 1 1
      .github/workflows/test.yml
  16. 1 1
      .github/workflows/typecheck.yml
  17. 1 1
      .github/workflows/update-nix-hashes.yml
  18. 4 1
      .opencode/command/commit.md
  19. 0 8
      .opencode/command/hello.md
  20. 1 1
      .opencode/command/issues.md
  21. 15 0
      .opencode/command/rmslop.md
  22. 1 1
      .opencode/command/spellcheck.md
  23. 1 0
      .opencode/opencode.jsonc
  24. 1 0
      .prettierignore
  25. 0 13
      AGENTS.md
  26. 2 0
      CONTRIBUTING.md
  27. 6 0
      STATS.md
  28. 12 0
      STYLE_GUIDE.md
  29. 203 115
      bun.lock
  30. 3 3
      flake.lock
  31. 5 0
      github/action.yml
  32. 1 0
      hosts/jetbrains-plugin/changelog.html
  33. 1 0
      hosts/vscode-plugin/CHANGELOG.md
  34. 1 1
      infra/console.ts
  35. 4 5
      install
  36. 1 1
      nix/hashes.json
  37. 2 2
      package.json
  38. 1 1
      packages/console/app/package.json
  39. 5 5
      packages/console/app/src/config.ts
  40. 32 3
      packages/console/app/src/routes/index.css
  41. 21 15
      packages/console/app/src/routes/index.tsx
  42. 1 1
      packages/console/app/src/routes/workspace-picker.tsx
  43. 87 70
      packages/console/app/src/routes/workspace/[id]/graph-section.tsx
  44. 21 9
      packages/console/app/src/routes/zen/util/dataDumper.ts
  45. 38 14
      packages/console/app/src/routes/zen/util/handler.ts
  46. 16 0
      packages/console/app/src/routes/zen/util/stickyProviderTracker.ts
  47. 1 1
      packages/console/core/package.json
  48. 5 6
      packages/console/core/src/account.ts
  49. 2 0
      packages/console/core/src/model.ts
  50. 1 1
      packages/console/core/src/provider.ts
  51. 117 113
      packages/console/core/sst-env.d.ts
  52. 1 1
      packages/console/function/package.json
  53. 1 1
      packages/console/function/src/auth.ts
  54. 117 113
      packages/console/function/sst-env.d.ts
  55. 1 1
      packages/console/mail/package.json
  56. 2 2
      packages/console/resource/resource.node.ts
  57. 117 113
      packages/console/resource/sst-env.d.ts
  58. 3 1
      packages/desktop/package.json
  59. 649 0
      packages/desktop/src/addons/serialize.ts
  60. 2 2
      packages/desktop/src/components/prompt-input.tsx
  61. 151 0
      packages/desktop/src/components/terminal.tsx
  62. 21 1
      packages/desktop/src/context/layout.tsx
  63. 1 1
      packages/desktop/src/context/sdk.tsx
  64. 88 12
      packages/desktop/src/context/session.tsx
  65. 109 5
      packages/desktop/src/pages/layout.tsx
  66. 376 268
      packages/desktop/src/pages/session.tsx
  67. 3 1
      packages/desktop/src/sst-env.d.ts
  68. 3 0
      packages/desktop/vite.config.ts
  69. 1 1
      packages/enterprise/package.json
  70. 75 25
      packages/enterprise/src/core/share.ts
  71. 20 5
      packages/enterprise/src/core/storage.ts
  72. 62 13
      packages/enterprise/src/routes/share/[shareID].tsx
  73. 117 113
      packages/enterprise/sst-env.d.ts
  74. 40 0
      packages/enterprise/test-debug.ts
  75. 262 0
      packages/enterprise/test/core/share.test.ts
  76. 64 0
      packages/enterprise/test/core/storage.test.ts
  77. 3 0
      packages/enterprise/vite.config.ts
  78. 6 6
      packages/extensions/zed/extension.toml
  79. 1 1
      packages/function/package.json
  80. 117 113
      packages/function/sst-env.d.ts
  81. 6 3
      packages/opencode/package.json
  82. 10 3
      packages/opencode/script/build.ts
  83. 2 1
      packages/opencode/src/agent/agent.ts
  84. 31 8
      packages/opencode/src/bus/index.ts
  85. 17 1
      packages/opencode/src/cli/cmd/auth.ts
  86. 3 1
      packages/opencode/src/cli/cmd/cmd.ts
  87. 5 0
      packages/opencode/src/cli/cmd/github.ts
  88. 1 1
      packages/opencode/src/cli/cmd/models.ts
  89. 1 1
      packages/opencode/src/cli/cmd/run.ts
  90. 106 0
      packages/opencode/src/cli/cmd/session.ts
  91. 42 6
      packages/opencode/src/cli/cmd/tui/app.tsx
  92. 5 1
      packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
  93. 41 11
      packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
  94. 27 11
      packages/opencode/src/cli/cmd/tui/context/local.tsx
  95. 18 0
      packages/opencode/src/cli/cmd/tui/context/prompt.tsx
  96. 2 0
      packages/opencode/src/cli/cmd/tui/context/route.tsx
  97. 49 1
      packages/opencode/src/cli/cmd/tui/context/theme.tsx
  98. 16 6
      packages/opencode/src/cli/cmd/tui/routes/home.tsx
  99. 108 8
      packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
  100. 4 2
      packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx

+ 0 - 58
.github/guidelines-check.yml

@@ -1,58 +0,0 @@
-#
-# This file is intentionally in the wrong dir, will move and add later....
-#
-
-name: Guidelines Check
-
-on:
-  # Disabled - uncomment to re-enable
-  # pull_request_target:
-  #   types: [opened, synchronize]
-  workflow_dispatch:
-
-jobs:
-  check-guidelines:
-    runs-on: ubuntu-latest
-    permissions:
-      contents: read
-      pull-requests: write
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 1
-
-      - name: Install opencode
-        run: curl -fsSL https://opencode.ai/install | bash
-
-      - name: Check PR guidelines compliance
-        env:
-          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }'
-        run: |
-          opencode run -m anthropic/claude-sonnet-4-20250514 "A new pull request has been created: '${{ github.event.pull_request.title }}'
-
-          <pr-number>
-          ${{ github.event.pull_request.number }}
-          </pr-number>
-
-          <pr-description>
-          ${{ github.event.pull_request.body }}
-          </pr-description>
-
-          Please check all the code changes in this pull request against the guidelines in AGENTS.md file in this repository. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
-
-          Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
-
-          Command MUST be like this.
-          ```
-          gh api \
-            --method POST \
-            -H "Accept: application/vnd.github+json" \
-            -H "X-GitHub-Api-Version: 2022-11-28" \
-            /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments \
-            -f 'body=[summary of issue]' -f 'commit_id=${{ github.event.pull_request.head.sha }}' -f 'path=[path-to-file]' -F "line=[line]" -f 'side=RIGHT'
-          ```
-
-          Only create comments for actual violations. If the code follows all guidelines, don't run any gh commands."

+ 1 - 1
.github/workflows/auto-label-tui.yml

@@ -6,7 +6,7 @@ on:
 
 jobs:
   auto-label:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       contents: read
       issues: write

+ 1 - 1
.github/workflows/deploy.yml

@@ -11,7 +11,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
 
 jobs:
   deploy:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
 

+ 4 - 1
.github/workflows/duplicate-issues.yml

@@ -6,7 +6,7 @@ on:
 
 jobs:
   check-duplicates:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       contents: read
       issues: write
@@ -55,4 +55,7 @@ jobs:
 
           Feel free to ignore if none of these address your specific case.'
 
+          Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, please add a comment mentioning the pinned keybinds issue #4997:
+          'For keybind-related issues, please also check our pinned keybinds documentation: #4997'
+
           If no clear duplicates are found, do not comment."

+ 1 - 1
.github/workflows/format.yml

@@ -10,7 +10,7 @@ on:
   workflow_dispatch:
 jobs:
   format:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       contents: write
     steps:

+ 1 - 1
.github/workflows/notify-discord.yml

@@ -6,7 +6,7 @@ on:
 
 jobs:
   notify:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - name: Send nicely-formatted embed to Discord
         uses: SethCohen/github-releases-to-discord@v1

+ 1 - 1
.github/workflows/opencode.yml

@@ -13,7 +13,7 @@ jobs:
       startsWith(github.event.comment.body, '/oc') ||
       contains(github.event.comment.body, ' /opencode') ||
       startsWith(github.event.comment.body, '/opencode')
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       id-token: write
       contents: read

+ 1 - 1
.github/workflows/publish-github-action.yml

@@ -14,7 +14,7 @@ permissions:
 
 jobs:
   publish:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
         with:

+ 1 - 1
.github/workflows/publish-vscode.yml

@@ -13,7 +13,7 @@ permissions:
 
 jobs:
   publish:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
         with:

+ 1 - 1
.github/workflows/publish.yml

@@ -25,7 +25,7 @@ permissions:
 
 jobs:
   publish:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
         with:

+ 78 - 0
.github/workflows/review.yml

@@ -0,0 +1,78 @@
+name: Guidelines Check
+
+on:
+  issue_comment:
+    types: [created]
+
+jobs:
+  check-guidelines:
+    if: |
+      github.event.issue.pull_request &&
+      startsWith(github.event.comment.body, '/review') &&
+      contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
+    runs-on: blacksmith-4vcpu-ubuntu-2404
+    permissions:
+      contents: read
+      pull-requests: write
+    steps:
+      - name: Get PR number
+        id: pr-number
+        run: |
+          if [ "${{ github.event_name }}" = "pull_request_target" ]; then
+            echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
+          else
+            echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
+          fi
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+
+      - name: Install opencode
+        run: curl -fsSL https://opencode.ai/install | bash
+
+      - name: Get PR details
+        id: pr-details
+        run: |
+          gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json
+          echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT
+          echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Check PR guidelines compliance
+        env:
+          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }'
+        run: |
+          PR_BODY=$(jq -r .body pr_data.json)
+          opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}'
+
+          <pr-number>
+          ${{ steps.pr-number.outputs.number }}
+          </pr-number>
+
+          <pr-description>
+          $PR_BODY
+          </pr-description>
+
+          Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
+
+          When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage.
+          When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simpliest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)
+
+          Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
+
+          Command MUST be like this.
+          \`\`\`
+          gh api \
+            --method POST \
+            -H \"Accept: application/vnd.github+json\" \
+            -H \"X-GitHub-Api-Version: 2022-11-28\" \
+            /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \
+            -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT'
+          \`\`\`
+
+          Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!."

+ 1 - 1
.github/workflows/snapshot.yml

@@ -14,7 +14,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
 
 jobs:
   publish:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
         with:

+ 1 - 1
.github/workflows/stats.yml

@@ -7,7 +7,7 @@ on:
 
 jobs:
   stats:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       contents: write
 

+ 1 - 1
.github/workflows/sync-zed-extension.yml

@@ -8,7 +8,7 @@ on:
 jobs:
   zed:
     name: Release Zed Extension
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v4
         with:

+ 1 - 1
.github/workflows/test.yml

@@ -10,7 +10,7 @@ on:
   workflow_dispatch:
 jobs:
   test:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4

+ 1 - 1
.github/workflows/typecheck.yml

@@ -7,7 +7,7 @@ on:
 
 jobs:
   typecheck:
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4

+ 1 - 1
.github/workflows/update-nix-hashes.yml

@@ -19,7 +19,7 @@ on:
 jobs:
   update:
     if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
-    runs-on: ubuntu-latest
+    runs-on: blacksmith-4vcpu-ubuntu-2404
     env:
       SYSTEM: x86_64-linux
 

+ 4 - 1
.opencode/command/commit.md

@@ -1,5 +1,5 @@
 ---
-description: Git commit and push
+description: git commit and push
 ---
 
 commit and push
@@ -21,3 +21,6 @@ WHAT was done.
 
 do not do generic messages like "improved agent experience" be very specific
 about what user facing changes were made
+
+if there are changes do a git pull --rebase
+if there are conflicts DO NOT FIX THEM. notify me and I will fix them

+ 0 - 8
.opencode/command/hello.md

@@ -1,8 +0,0 @@
----
-description: hello world iaosd ioasjdoiasjd oisadjoisajd osiajd oisaj dosaij dsoajsajdaijdoisa jdoias jdoias jdoia jois jo jdois jdoias jdoias j djoasdj
----
-
-hey there $ARGUMENTS
-
-!`ls`
-check out @README.md

+ 1 - 1
.opencode/command/issues.md

@@ -1,5 +1,5 @@
 ---
-description: "Find issue(s) on github"
+description: "find issue(s) on github"
 model: opencode/claude-haiku-4-5
 ---
 

+ 15 - 0
.opencode/command/rmslop.md

@@ -0,0 +1,15 @@
+---
+description: Remove AI code slop
+---
+
+Check the diff against dev, and remove all AI generated slop introduced in this branch.
+
+This includes:
+
+- Extra comments that a human wouldn't add or is inconsistent with the rest of the file
+- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths)
+- Casts to any to get around type issues
+- Any other style that is inconsistent with the file
+- Unnecessary emoji usage
+
+Report at the end with only a 1-3 sentence summary of what you changed

+ 1 - 1
.opencode/command/spellcheck.md

@@ -1,5 +1,5 @@
 ---
-description: Spellcheck all markdown file changes
+description: spellcheck all markdown file changes
 ---
 
 Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.

+ 1 - 0
.opencode/opencode.jsonc

@@ -4,6 +4,7 @@
   // "enterprise": {
   //   "url": "https://enterprise.dev.opencode.ai",
   // },
+  "instructions": ["STYLE_GUIDE.md"],
   "provider": {
     "opencode": {
       "options": {

+ 1 - 0
.prettierignore

@@ -0,0 +1 @@
+sst-env.d.ts

+ 0 - 13
AGENTS.md

@@ -1,16 +1,3 @@
-## IMPORTANT
-
-- Try to keep things in one function unless composable or reusable
-- DO NOT do unnecessary destructuring of variables
-- DO NOT use `else` statements unless necessary
-- DO NOT use `try`/`catch` if it can be avoided
-- AVOID `try`/`catch` where possible
-- AVOID `else` statements
-- AVOID using `any` type
-- AVOID `let` statements
-- PREFER single word variable names where possible
-- Use as many bun apis as possible like Bun.file()
-
 ## Debugging
 
 - To test opencode in the `packages/opencode` directory you can run `bun dev`

+ 2 - 0
CONTRIBUTING.md

@@ -42,6 +42,8 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
 > [!NOTE]
 > After touching `packages/opencode/src/server/server.ts`, run "./packages/sdk/js/script/build.ts" to regenerate the JS sdk.
 
+Please try to follow the [style guide](./STYLE_GUIDE.md)
+
 ### Setting up a Debugger
 
 Bun debugging is currently rough around the edges. We hope this guide helps you get set up and avoid some pain points.

+ 6 - 0
STATS.md

@@ -155,3 +155,9 @@
 | 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
 | 2025-11-28 | 901,741 (+7,781)  | 856,482 (+10,302) | 1,758,223 (+18,083) |
 | 2025-11-29 | 908,689 (+6,948)  | 863,361 (+6,879)  | 1,772,050 (+13,827) |
+| 2025-11-30 | 916,116 (+7,427)  | 870,194 (+6,833)  | 1,786,310 (+14,260) |
+| 2025-12-01 | 925,898 (+9,782)  | 876,500 (+6,306)  | 1,802,398 (+16,088) |
+| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
+| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
+| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
+| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |

+ 12 - 0
STYLE_GUIDE.md

@@ -0,0 +1,12 @@
+## Style Guide
+
+- Try to keep things in one function unless composable or reusable
+- DO NOT do unnecessary destructuring of variables
+- DO NOT use `else` statements unless necessary
+- DO NOT use `try`/`catch` if it can be avoided
+- AVOID `try`/`catch` where possible
+- AVOID `else` statements
+- AVOID using `any` type
+- AVOID `let` statements
+- PREFER single word variable names where possible
+- Use as many bun apis as possible like Bun.file()

+ 203 - 115
bun.lock

@@ -23,7 +23,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -51,7 +51,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -78,7 +78,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
@@ -102,7 +102,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -126,7 +126,7 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -138,11 +138,13 @@
         "@solid-primitives/resize-observer": "2.1.3",
         "@solid-primitives/scroll": "2.1.3",
         "@solid-primitives/storage": "4.3.3",
+        "@solid-primitives/websocket": "1.3.1",
         "@solidjs/meta": "catalog:",
         "@solidjs/router": "catalog:",
         "@thisbeyond/solid-dnd": "0.7.5",
         "diff": "catalog:",
         "fuzzysort": "catalog:",
+        "ghostty-web": "0.3.0",
         "luxon": "catalog:",
         "marked": "16.2.0",
         "marked-shiki": "1.2.1",
@@ -167,7 +169,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/util": "workspace:*",
@@ -195,7 +197,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "22.0.0",
@@ -211,7 +213,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -227,6 +229,8 @@
         "@ai-sdk/mcp": "0.0.8",
         "@ai-sdk/openai": "2.0.71",
         "@ai-sdk/openai-compatible": "1.0.27",
+        "@ai-sdk/provider": "2.0.0",
+        "@ai-sdk/provider-utils": "3.0.18",
         "@clack/prompts": "1.0.0-alpha.1",
         "@hono/standard-validator": "0.1.5",
         "@hono/zod-validator": "catalog:",
@@ -240,14 +244,15 @@
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/util": "workspace:*",
         "@openrouter/ai-sdk-provider": "1.2.8",
-        "@opentui/core": "0.1.52",
-        "@opentui/solid": "0.1.52",
+        "@opentui/core": "0.1.56",
+        "@opentui/solid": "0.1.56",
         "@parcel/watcher": "2.5.1",
         "@pierre/precision-diffs": "catalog:",
         "@solid-primitives/event-bus": "1.1.2",
         "@standard-schema/spec": "1.0.0",
         "@zip.js/zip.js": "2.7.62",
         "ai": "catalog:",
+        "bun-pty": "0.4.2",
         "chokidar": "4.0.3",
         "clipboardy": "4.0.0",
         "decimal.js": "10.5.0",
@@ -342,7 +347,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
@@ -362,7 +367,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "devDependencies": {
         "@hey-api/openapi-ts": "0.81.0",
         "@tsconfig/node22": "catalog:",
@@ -373,7 +378,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -386,7 +391,7 @@
     },
     "packages/tauri": {
       "name": "@opencode-ai/tauri",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@tauri-apps/api": "^2",
         "@tauri-apps/plugin-opener": "^2",
@@ -399,7 +404,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -431,17 +436,18 @@
     },
     "packages/util": {
       "name": "@opencode-ai/util",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "zod": "catalog:",
       },
       "devDependencies": {
+        "@types/bun": "catalog:",
         "typescript": "catalog:",
       },
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.0.121",
+      "version": "1.0.133",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
@@ -487,7 +493,7 @@
     "@hono/zod-validator": "0.4.2",
     "@kobalte/core": "0.13.11",
     "@openauthjs/openauth": "0.0.0-20250322224806",
-    "@pierre/precision-diffs": "0.5.7",
+    "@pierre/precision-diffs": "0.6.0-beta.3",
     "@solidjs/meta": "0.29.4",
     "@solidjs/router": "0.15.4",
     "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
@@ -501,7 +507,7 @@
     "ai": "5.0.97",
     "diff": "8.0.2",
     "fuzzysort": "3.1.0",
-    "hono": "4.7.10",
+    "hono": "4.10.7",
     "hono-openapi": "1.1.1",
     "luxon": "3.6.1",
     "remeda": "2.26.0",
@@ -516,7 +522,7 @@
     "zod": "4.1.8",
   },
   "packages": {
-    "@acemir/cssom": ["@acemir/[email protected]4", "", {}, "sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg=="],
+    "@acemir/cssom": ["@acemir/[email protected]6", "", {}, "sha512-UMFbL3EnWH/eTvl21dz9s7Td4wYDMtxz/56zD8sL9IZGYyi48RxmdgPMiyT7R6Vn3rjMTwYZ42bqKa7ex74GEQ=="],
 
     "@actions/core": ["@actions/[email protected]", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
 
@@ -552,7 +558,7 @@
 
     "@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
 
-    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected].0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
+    "@ai-sdk/provider-utils": ["@ai-sdk/[email protected].18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
 
     "@alloc/quick-lru": ["@alloc/[email protected]", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
 
@@ -832,6 +838,8 @@
 
     "@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
 
+    "@esbuild/openharmony-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
     "@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
 
     "@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
@@ -946,6 +954,8 @@
 
     "@internationalized/number": ["@internationalized/[email protected]", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
 
+    "@ioredis/commands": ["@ioredis/[email protected]", "", {}, "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ=="],
+
     "@isaacs/balanced-match": ["@isaacs/[email protected]", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
 
     "@isaacs/brace-expansion": ["@isaacs/[email protected]", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
@@ -1222,21 +1232,21 @@
 
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
 
-    "@opentui/core": ["@opentui/[email protected]2", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.52", "@opentui/core-darwin-x64": "0.1.52", "@opentui/core-linux-arm64": "0.1.52", "@opentui/core-linux-x64": "0.1.52", "@opentui/core-win32-arm64": "0.1.52", "@opentui/core-win32-x64": "0.1.52", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-om1s6Y1CHoILjW77G1QyakizMKgfjsBAJT61surxIHehaDiF3sSNf+Vkc2d7TEskgFvaJI5CWbTIXiUFsIi9cA=="],
+    "@opentui/core": ["@opentui/[email protected]6", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.56", "@opentui/core-darwin-x64": "0.1.56", "@opentui/core-linux-arm64": "0.1.56", "@opentui/core-linux-x64": "0.1.56", "@opentui/core-win32-arm64": "0.1.56", "@opentui/core-win32-x64": "0.1.56", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-TI5cSCPYythHIQYpAEdXyZhewGACn2TfnfC1qZmrSyEq33zFo4W7zpQ4EZNpy9xZJFCI+elAUVJFARwhudp9EQ=="],
 
-    "@opentui/core-darwin-arm64": ["@opentui/[email protected]2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vsJV9pRlpxMsUx90qxvS/uzzFtlsb9wzwPfdHViNtmyy8rgUHUCmrcFlZY+m4GUOf6YIn1ZLn+F56SQg4BjcWw=="],
+    "@opentui/core-darwin-arm64": ["@opentui/[email protected]6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-x5U9J2k1Fmbb9Mdh1nOd/yZVpg4ARCrV5pFngpaeKrIWDhs8RLpQW3ap+r7uyFLGFkSn4h5wBR0jj6Dg+Tyw+A=="],
 
-    "@opentui/core-darwin-x64": ["@opentui/[email protected]2", "", { "os": "darwin", "cpu": "x64" }, "sha512-h0R1phpNLmBe34caT602jGHIlJPqHkfBznrUfyMytP9vQ1FQlTBTFpBXtJQ+Eh3tVWP937TB2uBuZJbWmaRVhw=="],
+    "@opentui/core-darwin-x64": ["@opentui/[email protected]6", "", { "os": "darwin", "cpu": "x64" }, "sha512-7swq9rV/SaNVBWoUbC7mlP1VNyKBl7SSwmyVMkcaBP71lkm95zWuh4pgGj82fLgZ9gITRBD95TJVDmTovOyW0A=="],
 
-    "@opentui/core-linux-arm64": ["@opentui/[email protected]2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Lb2sMEEO5a+ZLhaTiZNCidjsxiQ98wXym0C54T6N9uBIWJfYZfxLSrt3LH1GtIwzgryvHBkfRLgKK6XPgdusUQ=="],
+    "@opentui/core-linux-arm64": ["@opentui/[email protected]6", "", { "os": "linux", "cpu": "arm64" }, "sha512-v8b+kiTlynAJzR0hFeVpGFzVi5PGqXAe3Zql9iTiQqTExkm/sR34sfC/P6rBOUhuAnos8ovPDKWtDb6eCTSm9g=="],
 
-    "@opentui/core-linux-x64": ["@opentui/[email protected]2", "", { "os": "linux", "cpu": "x64" }, "sha512-+XPWjqO5ceHqcR35HO78CPvigkKeSxtX4FW2CRSYKJZhCSJR3CrPuXuia2lb4lKmmfOg2eZUuLTfeop3u0GApw=="],
+    "@opentui/core-linux-x64": ["@opentui/[email protected]6", "", { "os": "linux", "cpu": "x64" }, "sha512-lbxgvAi5SBswK/2hoMPtLhPvJxASgquPUwvGTRHqzDkCvrOChP/loTjBQpL09/nAFc3jbM3SAbZtnEgA2SGYVw=="],
 
-    "@opentui/core-win32-arm64": ["@opentui/[email protected]2", "", { "os": "win32", "cpu": "arm64" }, "sha512-ifsMIwrd81veX9Nsuq5CV6vxKg9x0G/xnLPZWHG1ikq4UGgBEHPhG3L4v90Ntm2I53AYaozErbjh+lMIsTOsKw=="],
+    "@opentui/core-win32-arm64": ["@opentui/[email protected]6", "", { "os": "win32", "cpu": "arm64" }, "sha512-RoCAbvDo+59OevX+6GrEGbaueERiBVnTaWJkrS41hRAD2fFS3CZpW7UuS5jIg7zn5clHmOGyfvCiBkTRXmgkhw=="],
 
-    "@opentui/core-win32-x64": ["@opentui/[email protected]2", "", { "os": "win32", "cpu": "x64" }, "sha512-NGFnuwTTPUTgs4N4fqRGJI9LLRs/HDLHSfwGvcxKL5i3thENboT5AeYd/aOWeFPbtMy4/flc6Y/CY+yVtxiCag=="],
+    "@opentui/core-win32-x64": ["@opentui/[email protected]6", "", { "os": "win32", "cpu": "x64" }, "sha512-i6N5TjZU5gRkJsKmH8e/qY9vwSk0rh6A5t37mHDGlzN4E5yO/MbBrYH4ppLp5stps9Zfi1Re51ofJX1s2hZY/Q=="],
 
-    "@opentui/solid": ["@opentui/[email protected]2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.52", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-1EgGko0bUb0NnRx6SkewLhqWWOADX6t07yrh6eZIhDu8pNhtUUj27dxlolsGmzsBzylhZ9uj1k9k44/+QNXW1Q=="],
+    "@opentui/solid": ["@opentui/[email protected]6", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.56", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-3R7AfxsYHUyehwJK98rt5dI9u2WCT/uH/CYvddZIgXPHyfFm1SHJekMdy3DUoiQTCUllt68eFGKMv9zRi6Laww=="],
 
     "@oslojs/asn1": ["@oslojs/[email protected]", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
 
@@ -1352,7 +1362,7 @@
 
     "@petamoriken/float16": ["@petamoriken/[email protected]", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
 
-    "@pierre/precision-diffs": ["@pierre/precision-diffs@0.5.7", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-Y+e4kJ9pT2I4NS5fE39KdoiXtwMkVPRvrwLM6O2IqO7PDCRWLBS7CYxcSgSyngEndccUll2krx66I2QnfO0Ovg=="],
+    "@pierre/precision-diffs": ["@pierre/precision-diffs@0.6.0-beta.3", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/transformers": "3.15.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.15.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1FBm9jhLWZvs7BqN3yG2Wh9SpGuO1us2QsKZlQqSwyCctMr9DRGzYQJ9lF6yR03LHzXs3fuIzO++d9sCObYzrQ=="],
 
     "@pkgjs/parseargs": ["@pkgjs/[email protected]", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
 
@@ -1644,6 +1654,8 @@
 
     "@solid-primitives/utils": ["@solid-primitives/[email protected]", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
 
+    "@solid-primitives/websocket": ["@solid-primitives/[email protected]", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-F06tA2FKa5VsnS4E4WEc3jHpsJfXRlMTGOtolugTzCqV3JmJTyvk9UVg1oz6PgGHKGi1CQ91OP8iW34myyJgaQ=="],
+
     "@solidjs/meta": ["@solidjs/[email protected]", "", { "peerDependencies": { "solid-js": ">=1.8.4" } }, "sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g=="],
 
     "@solidjs/router": ["@solidjs/[email protected]", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="],
@@ -1848,25 +1860,25 @@
 
     "@types/yargs-parser": ["@types/[email protected]", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
 
-    "@typescript-eslint/eslint-plugin": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/type-utils": "8.48.0", "@typescript-eslint/utils": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ=="],
+    "@typescript-eslint/eslint-plugin": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/type-utils": "8.48.1", "@typescript-eslint/utils": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA=="],
 
-    "@typescript-eslint/parser": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ=="],
+    "@typescript-eslint/parser": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA=="],
 
-    "@typescript-eslint/project-service": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.0", "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw=="],
+    "@typescript-eslint/project-service": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.1", "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w=="],
 
-    "@typescript-eslint/scope-manager": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0" } }, "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ=="],
+    "@typescript-eslint/scope-manager": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1" } }, "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w=="],
 
-    "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/[email protected].0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w=="],
+    "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/[email protected].1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw=="],
 
-    "@typescript-eslint/type-utils": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw=="],
+    "@typescript-eslint/type-utils": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg=="],
 
-    "@typescript-eslint/types": ["@typescript-eslint/[email protected].0", "", {}, "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA=="],
+    "@typescript-eslint/types": ["@typescript-eslint/[email protected].1", "", {}, "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q=="],
 
-    "@typescript-eslint/typescript-estree": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.0", "@typescript-eslint/tsconfig-utils": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ=="],
+    "@typescript-eslint/typescript-estree": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.1", "@typescript-eslint/tsconfig-utils": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg=="],
 
-    "@typescript-eslint/utils": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ=="],
+    "@typescript-eslint/utils": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA=="],
 
-    "@typescript-eslint/visitor-keys": ["@typescript-eslint/[email protected].0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg=="],
+    "@typescript-eslint/visitor-keys": ["@typescript-eslint/[email protected].1", "", { "dependencies": { "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q=="],
 
     "@typescript/native-preview": ["@typescript/[email protected]", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251014.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251014.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-IqmX5CYCBqXbfL+HKlcQAMaDlfJ0Z8OhUxvADFV2TENnzSYI4CuhvKxwOB2wFSLXufVsgtAlf3Fjwn24KmMyPQ=="],
 
@@ -2012,7 +2024,7 @@
 
     "bare-events": ["[email protected]", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
 
-    "bare-fs": ["[email protected].2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw=="],
+    "bare-fs": ["[email protected].1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg=="],
 
     "bare-os": ["[email protected]", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
 
@@ -2026,7 +2038,7 @@
 
     "base64-js": ["[email protected]", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
 
-    "baseline-browser-mapping": ["[email protected]2", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="],
+    "baseline-browser-mapping": ["[email protected]0", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA=="],
 
     "bcp-47": ["[email protected]", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="],
 
@@ -2052,7 +2064,7 @@
 
     "boolbase": ["[email protected]", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
 
-    "bowser": ["[email protected]3.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
+    "bowser": ["[email protected]2.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="],
 
     "boxen": ["[email protected]", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
 
@@ -2072,6 +2084,8 @@
 
     "bun-ffi-structs": ["[email protected]", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
 
+    "bun-pty": ["[email protected]", "", {}, "sha512-sHImDz6pJDsHAroYpC9ouKVgOyqZ7FP3N+stX5IdMddHve3rf9LIZBDomQcXrACQ7sQDNuwZQHG8BKR7w8krkQ=="],
+
     "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
 
     "bun-webgpu": ["[email protected]", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
@@ -2104,7 +2118,7 @@
 
     "camelcase-css": ["[email protected]", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
 
-    "caniuse-lite": ["[email protected]7", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="],
+    "caniuse-lite": ["[email protected]6", "", {}, "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A=="],
 
     "ccount": ["[email protected]", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
 
@@ -2152,6 +2166,8 @@
 
     "clsx": ["[email protected]", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
 
+    "cluster-key-slot": ["[email protected]", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
+
     "collapse-white-space": ["[email protected]", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
 
     "color": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
@@ -2188,7 +2204,7 @@
 
     "convert-source-map": ["[email protected]", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
 
-    "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+    "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
 
     "cookie-es": ["[email protected]", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
 
@@ -2322,7 +2338,7 @@
 
     "ee-first": ["[email protected]", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
 
-    "electron-to-chromium": ["[email protected]62", "", {}, "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ=="],
+    "electron-to-chromium": ["[email protected]59", "", {}, "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ=="],
 
     "emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
 
@@ -2562,6 +2578,8 @@
 
     "get-tsconfig": ["[email protected]", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
 
+    "ghostty-web": ["[email protected]", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="],
+
     "gifwrap": ["[email protected]", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
 
     "giget": ["[email protected]", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="],
@@ -2666,7 +2684,7 @@
 
     "highlightjs-vue": ["[email protected]", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="],
 
-    "hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
+    "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
 
     "hono-openapi": ["[email protected]", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="],
 
@@ -2732,6 +2750,8 @@
 
     "internal-slot": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
 
+    "ioredis": ["[email protected]", "", { "dependencies": { "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q=="],
+
     "ipaddr.js": ["[email protected]", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
 
     "iron-webcrypto": ["[email protected]", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
@@ -2940,8 +2960,12 @@
 
     "lodash": ["[email protected]", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
 
+    "lodash.defaults": ["[email protected]", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
+
     "lodash.includes": ["[email protected]", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
 
+    "lodash.isarguments": ["[email protected]", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
+
     "lodash.isboolean": ["[email protected]", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
 
     "lodash.isinteger": ["[email protected]", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
@@ -3022,7 +3046,7 @@
 
     "mdast-util-phrasing": ["[email protected]", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
 
-    "mdast-util-to-hast": ["[email protected].1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
+    "mdast-util-to-hast": ["[email protected].0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
 
     "mdast-util-to-markdown": ["[email protected]", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
 
@@ -3216,8 +3240,6 @@
 
     "object.assign": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
 
-    "obug": ["[email protected]", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
-
     "ofetch": ["[email protected]", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="],
 
     "ohash": ["[email protected]", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
@@ -3276,7 +3298,7 @@
 
     "package-json-from-dist": ["[email protected]", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
 
-    "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
+    "package-manager-detector": ["package-manager-detector@1.5.0", "", {}, "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw=="],
 
     "pagefind": ["[email protected]", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="],
 
@@ -3414,7 +3436,7 @@
 
     "range-parser": ["[email protected]", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
 
-    "raw-body": ["[email protected].3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
+    "raw-body": ["[email protected].2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
 
     "rc": ["[email protected]", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
 
@@ -3422,7 +3444,7 @@
 
     "react": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
 
-    "react-dom": ["[email protected].0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
+    "react-dom": ["[email protected].1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="],
 
     "react-error-boundary": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "react": ">=16.13.1" } }, "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA=="],
 
@@ -3462,6 +3484,10 @@
 
     "redent": ["[email protected]", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
 
+    "redis-errors": ["[email protected]", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
+
+    "redis-parser": ["[email protected]", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
+
     "reflect.getprototypeof": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
 
     "refractor": ["[email protected]", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="],
@@ -3652,7 +3678,7 @@
 
     "sqlstring": ["[email protected]", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
 
-    "srvx": ["[email protected].7", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-N2a2nx8YTq13+A8qucg4lHZREfWOVnlMHAvrA9C2jbY9/QnVEAPzjdmpFHrY6/9BxSwIbvywCj7zahuGrVzCiQ=="],
+    "srvx": ["[email protected].6", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-5L4rT6qQqqb+xcoDoklUgCNdmzqJ6vbcDRwPVGRXewF55IJH0pqh0lQlrJ266ZWTKJ4mfeioqHQJeAYesS+RrQ=="],
 
     "sst": ["[email protected]", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.23", "sst-darwin-x64": "3.17.23", "sst-linux-arm64": "3.17.23", "sst-linux-x64": "3.17.23", "sst-linux-x86": "3.17.23", "sst-win32-arm64": "3.17.23", "sst-win32-x64": "3.17.23", "sst-win32-x86": "3.17.23" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-TwKgUgDnZdc1Swe+bvCNeyO4dQnYz5cTodMpYj3jlXZdK9/KNz0PVxT1f0u5E76i1pmilXrUBL/f7iiMPw4RDg=="],
 
@@ -3678,6 +3704,8 @@
 
     "stage-js": ["[email protected]", "", {}, "sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw=="],
 
+    "standard-as-callback": ["[email protected]", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
+
     "statuses": ["[email protected]", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
 
     "std-env": ["[email protected]", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
@@ -3852,7 +3880,7 @@
 
     "typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
 
-    "typescript-eslint": ["[email protected].0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.0", "@typescript-eslint/parser": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw=="],
+    "typescript-eslint": ["[email protected].1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.1", "@typescript-eslint/parser": "8.48.1", "@typescript-eslint/typescript-estree": "8.48.1", "@typescript-eslint/utils": "8.48.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A=="],
 
     "ufo": ["[email protected]", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
 
@@ -4074,20 +4102,22 @@
 
     "@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
 
+    "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
+
     "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
 
     "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
 
     "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
 
-    "@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
-
     "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
 
-    "@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
-
     "@ai-sdk/mcp/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
 
+    "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
+
+    "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
+
     "@asamuzakjp/css-color/lru-cache": ["[email protected]", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
 
     "@asamuzakjp/dom-selector/lru-cache": ["[email protected]", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
@@ -4110,6 +4140,8 @@
 
     "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
 
+    "@aws-crypto/sha256-js/@aws-sdk/types": ["@aws-sdk/[email protected]", "", { "dependencies": { "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA=="],
+
     "@aws-crypto/util/@smithy/util-utf8": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
 
     "@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-sdk/types": "3.775.0", "@smithy/core": "^3.2.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA=="],
@@ -4276,8 +4308,6 @@
 
     "@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/types": "3.15.0" } }, "sha512-Hmwip5ovvSkg+Kc41JTvSHHVfCYF+C8Cp1omb5AJj4Xvd+y9IXz2rKJwmFRGsuN0vpHxywcXJ1+Y4B9S7EG1/A=="],
 
-    "@pierre/precision-diffs/react": ["[email protected]", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
-
     "@pierre/precision-diffs/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
 
     "@poppinss/dumper/supports-color": ["[email protected]", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
@@ -4330,7 +4360,7 @@
 
     "@slack/web-api/p-queue": ["[email protected]", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
 
-    "@solidjs/start/@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
+    "@solidjs/start/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
 
     "@solidjs/start/path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
 
@@ -4338,8 +4368,6 @@
 
     "@solidjs/start/vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
 
-    "@solidjs/start/vitest": ["[email protected]", "", { "dependencies": { "@vitest/expect": "4.0.14", "@vitest/mocker": "4.0.14", "@vitest/pretty-format": "4.0.14", "@vitest/runner": "4.0.14", "@vitest/snapshot": "4.0.14", "@vitest/spy": "4.0.14", "@vitest/utils": "4.0.14", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.14", "@vitest/browser-preview": "4.0.14", "@vitest/browser-webdriverio": "4.0.14", "@vitest/ui": "4.0.14", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw=="],
-
     "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/[email protected]", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
 
     "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
@@ -4358,14 +4386,8 @@
 
     "@tailwindcss/postcss/tailwindcss": ["[email protected]", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
 
-    "@tanstack/directive-functions-plugin/@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
-
-    "@tanstack/router-utils/@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
-
     "@tanstack/router-utils/@babel/preset-typescript": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
 
-    "@tanstack/server-functions-plugin/@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
-
     "@testing-library/dom/aria-query": ["[email protected]", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
 
     "@testing-library/dom/dom-accessibility-api": ["[email protected]", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
@@ -4400,8 +4422,6 @@
 
     "astro/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
-    "babel-dead-code-elimination/@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
-
     "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
 
     "babel-plugin-jsx-dom-expressions/parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
@@ -4416,8 +4436,6 @@
 
     "body-parser/qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
 
-    "body-parser/raw-body": ["[email protected]", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
-
     "boxen/chalk": ["[email protected]", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
 
     "c12/ohash": ["[email protected]", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="],
@@ -4574,13 +4592,11 @@
 
     "prompts/kleur": ["[email protected]", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
 
-    "raw-body/http-errors": ["[email protected]", "", { "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=="],
-
     "raw-body/iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
 
     "rc/strip-json-comments": ["[email protected]", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
 
-    "react-dom/react": ["[email protected].0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
+    "react-dom/react": ["[email protected].1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
 
     "readable-web-to-node-stream/readable-stream": ["[email protected]", "", { "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=="],
 
@@ -4648,7 +4664,7 @@
 
     "webgui/diff": ["[email protected]", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="],
 
-    "webgui/react": ["[email protected].0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
+    "webgui/react": ["[email protected].1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
 
     "webgui/tailwindcss": ["[email protected]", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
 
@@ -4664,6 +4680,8 @@
 
     "wrap-ansi-cjs/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
 
+    "xml2js/sax": ["[email protected]", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
+
     "yargs/yargs-parser": ["[email protected]", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
 
     "zod-to-json-schema/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -4708,8 +4726,6 @@
 
     "@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/[email protected]", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
-
     "@astrojs/mdx/@astrojs/markdown-remark/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/engine-javascript": "3.15.0", "@shikijs/engine-oniguruma": "3.15.0", "@shikijs/langs": "3.15.0", "@shikijs/themes": "3.15.0", "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw=="],
 
     "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -4794,8 +4810,6 @@
 
     "@isaacs/cliui/wrap-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
 
-    "@jsx-email/cli/@vitejs/plugin-react/@babel/core": ["@babel/[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
-
     "@jsx-email/cli/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/[email protected]", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
 
     "@jsx-email/cli/@vitejs/plugin-react/react-refresh": ["[email protected]", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
@@ -4862,7 +4876,7 @@
 
     "@modelcontextprotocol/sdk/express/accepts": ["[email protected]", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
 
-    "@modelcontextprotocol/sdk/express/body-parser": ["[email protected].1", "", { "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.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
+    "@modelcontextprotocol/sdk/express/body-parser": ["[email protected].0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
 
     "@modelcontextprotocol/sdk/express/content-disposition": ["[email protected]", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
 
@@ -4978,35 +4992,67 @@
 
     "@slack/web-api/p-queue/p-timeout": ["[email protected]", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
 
-    "@solidjs/start/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+    "@solidjs/start/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
 
-    "@solidjs/start/shiki/@shikijs/core": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
+    "@solidjs/start/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
 
-    "@solidjs/start/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="],
+    "@solidjs/start/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
 
-    "@solidjs/start/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="],
+    "@solidjs/start/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
 
-    "@solidjs/start/shiki/@shikijs/langs": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ=="],
+    "@solidjs/start/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
 
-    "@solidjs/start/shiki/@shikijs/themes": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="],
+    "@solidjs/start/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
 
-    "@solidjs/start/shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
+    "@solidjs/start/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+    "@solidjs/start/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+    "@solidjs/start/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+    "@solidjs/start/esbuild/@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
 
-    "@solidjs/start/vitest/@vitest/expect": ["@vitest/[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.14", "@vitest/utils": "4.0.14", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw=="],
+    "@solidjs/start/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
 
-    "@solidjs/start/vitest/@vitest/mocker": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/spy": "4.0.14", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg=="],
+    "@solidjs/start/esbuild/@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
 
-    "@solidjs/start/vitest/@vitest/pretty-format": ["@vitest/[email protected]", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ=="],
+    "@solidjs/start/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
 
-    "@solidjs/start/vitest/@vitest/runner": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/utils": "4.0.14", "pathe": "^2.0.3" } }, "sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw=="],
+    "@solidjs/start/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
 
-    "@solidjs/start/vitest/@vitest/snapshot": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.14", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag=="],
+    "@solidjs/start/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
 
-    "@solidjs/start/vitest/@vitest/spy": ["@vitest/[email protected]", "", {}, "sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg=="],
+    "@solidjs/start/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
 
-    "@solidjs/start/vitest/@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.0.14", "tinyrainbow": "^3.0.3" } }, "sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw=="],
+    "@solidjs/start/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
 
-    "@solidjs/start/vitest/why-is-node-running": ["[email protected]", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
+    "@solidjs/start/shiki/@shikijs/core": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
+
+    "@solidjs/start/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="],
+
+    "@solidjs/start/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="],
+
+    "@solidjs/start/shiki/@shikijs/langs": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ=="],
+
+    "@solidjs/start/shiki/@shikijs/themes": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="],
+
+    "@solidjs/start/shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
 
     "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
 
@@ -5036,12 +5082,6 @@
 
     "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
 
-    "@tanstack/directive-functions-plugin/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
-    "@tanstack/router-utils/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
-    "@tanstack/server-functions-plugin/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
     "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
 
     "@vitejs/plugin-react/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -5070,8 +5110,6 @@
 
     "astro/unstorage/ofetch": ["[email protected]", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
 
-    "babel-dead-code-elimination/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
     "babel-plugin-jsx-dom-expressions/parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
 
     "babel-plugin-module-resolver/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="],
@@ -5080,8 +5118,6 @@
 
     "babel-plugin-module-resolver/glob/path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
 
-    "bl/buffer/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
-
     "body-parser/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
 
     "cheerio/parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
@@ -5172,8 +5208,6 @@
 
     "node-fetch/whatwg-url/webidl-conversions": ["[email protected]", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
 
-    "opencode/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
-
     "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
 
     "opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
@@ -5190,6 +5224,8 @@
 
     "p-locate/p-limit/yocto-queue": ["[email protected]", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
 
+    "parse-bmfont-xml/xml2js/sax": ["[email protected]", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
+
     "parse5-htmlparser2-tree-adapter/parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
 
     "pkg-dir/find-up/locate-path": ["[email protected]", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
@@ -5200,8 +5236,6 @@
 
     "prebuild-install/tar-fs/tar-stream": ["[email protected]", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
 
-    "raw-body/http-errors/statuses": ["[email protected]", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
     "readable-web-to-node-stream/readable-stream/buffer": ["[email protected]", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
 
     "readable-web-to-node-stream/readable-stream/events": ["[email protected]", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
@@ -5240,6 +5274,8 @@
 
     "type-is/mime-types/mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
 
+    "vitest/vite/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
     "wrap-ansi-cjs/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
 
     "wrap-ansi-cjs/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -5280,8 +5316,6 @@
 
     "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
 
-    "@jsx-email/cli/@vitejs/plugin-react/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
     "@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 
     "@jsx-email/cli/tailwindcss/chokidar/readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
@@ -5332,6 +5366,8 @@
 
     "@modelcontextprotocol/sdk/express/accepts/negotiator": ["[email protected]", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
 
+    "@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
     "@modelcontextprotocol/sdk/express/type-is/media-typer": ["[email protected]", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
 
     "@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["[email protected]", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
@@ -5394,7 +5430,7 @@
 
     "opencontrol/@modelcontextprotocol/sdk/express/accepts": ["[email protected]", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
 
-    "opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["[email protected].1", "", { "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.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
+    "opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["[email protected].0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
 
     "opencontrol/@modelcontextprotocol/sdk/express/content-disposition": ["[email protected]", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
 
@@ -5436,6 +5472,56 @@
 
     "tw-to-css/tailwindcss/chokidar/readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
 
+    "vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+    "vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+    "vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+    "vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+    "vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+    "vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+    "vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+    "vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+    "vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+    "vitest/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+    "vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+    "vitest/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+    "vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+    "vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+    "vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+    "vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+    "vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
     "@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/[email protected]", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
 
     "@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -5446,6 +5532,8 @@
 
     "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["[email protected]", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
 
+    "opencontrol/@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
     "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["[email protected]", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
 
     "opencontrol/@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["[email protected]", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],

+ 3 - 3
flake.lock

@@ -2,11 +2,11 @@
   "nodes": {
     "nixpkgs": {
       "locked": {
-        "lastModified": 1764474957,
-        "narHash": "sha256-RCNYRb7zHt+qycQwfTD/Zxnbd4Sxi2fgvkeAljtLEOs=",
+        "lastModified": 1764856222,
+        "narHash": "sha256-yEJmtoFu4cJre1NuU4fb8q57Oux+NTbocnALtJ64aEI=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "890f57fde071de281cd0e950cd80ea3e1ab55e75",
+        "rev": "ece6e266caf1effab32eceef0403b797b4330373",
         "type": "github"
       },
       "original": {

+ 5 - 0
github/action.yml

@@ -13,6 +13,10 @@ inputs:
     description: "Share the opencode session (defaults to true for public repos)"
     required: false
 
+  prompt:
+    description: "Custom prompt to override the default prompt"
+    required: false
+
 runs:
   using: "composite"
   steps:
@@ -27,3 +31,4 @@ runs:
       env:
         MODEL: ${{ inputs.model }}
         SHARE: ${{ inputs.share }}
+        PROMPT: ${{ inputs.prompt }}

+ 1 - 0
hosts/jetbrains-plugin/changelog.html

@@ -6,6 +6,7 @@
   <li>New panel with all modified files in session</li>
   <li>New panel with all TODOs in session</li>
   <li>Fixed placement of Model/Agent selector</li>
+  <li>Updated OpenCode to v1.0.133</li>
 </ul>
 
 <h3>2025.11.30</h3>

+ 1 - 0
hosts/vscode-plugin/CHANGELOG.md

@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - New panel with all modified files in session
 - New panel with all TODOs in session
 - Fixed placement of Model/Agent selector
+- Updated OpenCode to v1.0.133
 
 ## [25.11.30] - 2025-11-30
 

+ 1 - 1
infra/console.ts

@@ -116,7 +116,7 @@ const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
 // CONSOLE
 ////////////////
 
-const bucket = new sst.cloudflare.Bucket("ConsoleData")
+const bucket = new sst.cloudflare.Bucket("ZenData")
 
 const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
 const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")

+ 4 - 5
install

@@ -4,7 +4,7 @@ APP=opencode
 
 MUTED='\033[0;2m'
 RED='\033[0;31m'
-ORANGE='\033[38;2;255;140;0m'
+ORANGE='\033[38;5;214m'
 NC='\033[0m' # No Color
 
 requested_version=${VERSION:-}
@@ -353,10 +353,9 @@ echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░
 echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀  ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
 echo -e ""
 echo -e ""
-echo -e "${MUTED}To get started, navigate to a project and run:${NC}"
-echo -e "opencode                   ${MUTED}Use free models${NC}"
-echo -e "opencode auth login        ${MUTED}Add paid provider API keys${NC}"
-echo -e "opencode help              ${MUTED}List commands and options${NC}"
+echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
+echo -e "cd <project>  ${MUTED}# Open directory${NC}"
+echo -e "opencode      ${MUTED}# Run command${NC}"
 echo -e ""
 echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
 echo -e ""

+ 1 - 1
nix/hashes.json

@@ -1,3 +1,3 @@
 {
-  "nodeModules": "sha256-7ayinTE359aOG+CAeatKAU7j/MD+e9ybD2jYZLYT2H8="
+  "nodeModules": "sha256-Wrfwnmo0lpck2rbt6ttkAuDGvBvqqWJfNA8QDQxoZ6I="
 }

+ 2 - 2
package.json

@@ -31,11 +31,11 @@
       "@tsconfig/bun": "1.0.9",
       "@cloudflare/workers-types": "4.20251008.0",
       "@openauthjs/openauth": "0.0.0-20250322224806",
-      "@pierre/precision-diffs": "0.5.7",
+      "@pierre/precision-diffs": "0.6.0-beta.3",
       "@tailwindcss/vite": "4.1.11",
       "diff": "8.0.2",
       "ai": "5.0.97",
-      "hono": "4.7.10",
+      "hono": "4.10.7",
       "hono-openapi": "1.1.1",
       "fuzzysort": "3.1.0",
       "luxon": "3.6.1",

+ 1 - 1
packages/console/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "type": "module",
   "scripts": {
     "typecheck": "tsgo --noEmit",

+ 5 - 5
packages/console/app/src/config.ts

@@ -9,8 +9,8 @@ export const config = {
   github: {
     repoUrl: "https://github.com/sst/opencode",
     starsFormatted: {
-      compact: "30K",
-      full: "30,000",
+      compact: "35K",
+      full: "35,000",
     },
   },
 
@@ -22,8 +22,8 @@ export const config = {
 
   // Static stats (used on landing page)
   stats: {
-    contributors: "300",
-    commits: "4,000",
-    monthlyUsers: "300,000",
+    contributors: "350",
+    commits: "5,000",
+    monthlyUsers: "400,000",
   },
 } as const

+ 32 - 3
packages/console/app/src/routes/index.css

@@ -338,6 +338,11 @@ body {
     }
   }
 
+  [data-slot="installation-instructions"] {
+    color: var(--color-text-strong);
+    margin-bottom: 8px;
+  }
+
   [data-slot="installation"] {
     width: 100%;
     max-width: 100%;
@@ -348,6 +353,11 @@ body {
     }
   }
 
+  [data-slot="installation-options"] {
+    font-size: 13px;
+    margin-top: 12px;
+  }
+
   [data-component="tabs"] {
     [data-slot="tablist"] {
       display: flex;
@@ -480,10 +490,10 @@ body {
     }
 
     h1 {
-      font-size: 28px;
+      font-size: 38px;
       color: var(--color-text-strong);
       font-weight: 500;
-      margin-bottom: 16px;
+      margin-bottom: 8px;
 
       @media (max-width: 60rem) {
         font-size: 22px;
@@ -492,7 +502,7 @@ body {
 
     p {
       color: var(--color-text);
-      margin-bottom: 24px;
+      margin-bottom: 40px;
       max-width: 82%;
 
       @media (max-width: 50rem) {
@@ -607,6 +617,25 @@ body {
     padding: var(--vertical-padding) var(--padding);
     color: var(--color-text);
 
+    a {
+      background: var(--color-background-strong);
+      padding: 8px 12px 8px 20px;
+      color: var(--color-text-inverted);
+      border: none;
+      border-radius: 4px;
+      font-weight: 500;
+      cursor: pointer;
+      margin-top: 40px;
+      display: flex;
+      width: fit-content;
+      gap: 12px;
+      text-decoration: none;
+    }
+
+    a:hover {
+      background: var(--color-background-strong-hover);
+    }
+
     ul {
       padding: 0;
       li {

+ 21 - 15
packages/console/app/src/routes/index.tsx

@@ -43,7 +43,7 @@ export default function Home() {
   return (
     <main data-page="opencode">
       {/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
-      <Title>OpenCode | The AI coding agent built for the terminal</Title>
+      <Title>OpenCode | The open source AI coding agent</Title>
       <Link rel="canonical" href={config.baseUrl} />
       <Meta property="og:image" content="/social-share.png" />
       <Meta name="twitter:image" content="/social-share.png" />
@@ -56,23 +56,13 @@ export default function Home() {
               <a data-slot="releases" href={release()?.url ?? `${config.github.repoUrl}/releases`} target="_blank">
                 What’s new in {release()?.name ?? "the latest release"}
               </a>
-              <h1>The AI coding agent built for the terminal</h1>
+              <h1>The open source coding agent</h1>
               <p>
-                OpenCode is fully open source, giving you control and freedom to use any provider, any model, and any
-                editor.
+                OpenCode includes free models or connect from any provider to <br />
+                use other models, including Claude, GPT, Gemini and more.
               </p>
-              <a href="/docs">
-                <span>Read docs </span>
-                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-                  <path
-                    d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
-                    stroke="currentColor"
-                    stroke-width="1.5"
-                    stroke-linecap="square"
-                  />
-                </svg>
-              </a>
             </div>
+            <p data-slot="installation-instructions">Install and use. No account, no email, and no credit card.</p>
             <div data-slot="installation">
               <Tabs
                 as="section"
@@ -151,6 +141,11 @@ export default function Home() {
                 </div>
               </Tabs>
             </div>
+            <p data-slot="installation-options">
+              Available in terminal, web, and desktop (coming soon).
+              <br />
+              Extensions for VS Code, Cursor, Windsurf, and more.
+            </p>
           </section>
 
           <section data-component="video">
@@ -208,6 +203,17 @@ export default function Home() {
                 </div>
               </li>
             </ul>
+            <a href="/docs">
+              <span>Read docs </span>
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <path
+                  d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
+                  stroke="currentColor"
+                  stroke-width="1.5"
+                  stroke-linecap="square"
+                />
+              </svg>
+            </a>
           </section>
 
           <section data-component="growth">

+ 1 - 1
packages/console/app/src/routes/workspace-picker.tsx

@@ -14,7 +14,7 @@ import "./workspace-picker.css"
 const getWorkspaces = query(async () => {
   "use server"
   return withActor(async () => {
-    return Database.transaction((tx) =>
+    return Database.use((tx) =>
       tx
         .select({
           id: WorkspaceTable.id,

+ 87 - 70
packages/console/app/src/routes/workspace/[id]/graph-section.tsx

@@ -3,7 +3,7 @@ import { UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
 import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
 import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
 import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
-import { createAsync, query, useParams } from "@solidjs/router"
+import { useParams } from "@solidjs/router"
 import { createEffect, createMemo, onCleanup, Show, For } from "solid-js"
 import { createStore } from "solid-js/store"
 import { withActor } from "~/context/auth.withActor"
@@ -94,8 +94,6 @@ async function getCosts(workspaceID: string, year: number, month: number) {
   }, workspaceID)
 }
 
-const queryCosts = query(getCosts, "costs.get")
-
 const MODEL_COLORS: Record<string, string> = {
   "claude-sonnet-4-5": "#D4745C",
   "claude-sonnet-4": "#E8B4A4",
@@ -158,32 +156,27 @@ export function GraphSection() {
     model: null as string | null,
     modelDropdownOpen: false,
     keyDropdownOpen: false,
+    colorScheme: "light" as "light" | "dark",
   })
-  const initialData = createAsync(() => queryCosts(params.id!, store.year, store.month))
-
   const onPreviousMonth = async () => {
     const month = store.month === 0 ? 11 : store.month - 1
     const year = store.month === 0 ? store.year - 1 : store.year
-    const data = await getCosts(params.id!, year, month)
-    setStore({ month, year, data })
+    setStore({ month, year })
   }
 
   const onNextMonth = async () => {
     const month = store.month === 11 ? 0 : store.month + 1
     const year = store.month === 11 ? store.year + 1 : store.year
-    setStore({ month, year, data: await getCosts(params.id!, year, month) })
+    setStore({ month, year })
   }
 
   const onSelectModel = (model: string | null) => setStore({ model, modelDropdownOpen: false })
 
   const onSelectKey = (keyID: string | null) => setStore({ key: keyID, keyDropdownOpen: false })
 
-  const getData = createMemo(() => store.data ?? initialData())
-
   const getModels = createMemo(() => {
-    const data = getData()
-    if (!data?.usage) return []
-    return Array.from(new Set(data.usage.map((row) => row.model))).sort()
+    if (!store.data?.usage) return []
+    return Array.from(new Set(store.data.usage.map((row) => row.model))).sort()
   })
 
   const getDates = createMemo(() => {
@@ -206,10 +199,19 @@ export function GraphSection() {
   const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
 
   const chartConfig = createMemo((): ChartConfiguration | null => {
-    const data = getData()
+    const data = store.data
     const dates = getDates()
     if (!data?.usage?.length) return null
 
+    store.colorScheme
+    const styles = getComputedStyle(document.documentElement)
+    const colorTextMuted = styles.getPropertyValue("--color-text-muted").trim()
+    const colorBorderMuted = styles.getPropertyValue("--color-border-muted").trim()
+    const colorBgElevated = styles.getPropertyValue("--color-bg-elevated").trim()
+    const colorText = styles.getPropertyValue("--color-text").trim()
+    const colorTextSecondary = styles.getPropertyValue("--color-text-secondary").trim()
+    const colorBorder = styles.getPropertyValue("--color-border").trim()
+
     const dailyData = new Map<string, Map<string, number>>()
     for (const dateKey of dates) dailyData.set(dateKey, new Map())
 
@@ -252,7 +254,7 @@ export function GraphSection() {
             ticks: {
               maxRotation: 0,
               autoSkipPadding: 20,
-              color: "rgba(255, 255, 255, 0.5)",
+              color: colorTextMuted,
               font: {
                 family: "monospace",
                 size: 11,
@@ -263,10 +265,10 @@ export function GraphSection() {
             stacked: true,
             beginAtZero: true,
             grid: {
-              color: "rgba(255, 255, 255, 0.1)",
+              color: colorBorderMuted,
             },
             ticks: {
-              color: "rgba(255, 255, 255, 0.5)",
+              color: colorTextMuted,
               font: {
                 family: "monospace",
                 size: 11,
@@ -282,10 +284,10 @@ export function GraphSection() {
           tooltip: {
             mode: "index",
             intersect: false,
-            backgroundColor: "rgba(0, 0, 0, 0.9)",
-            titleColor: "rgba(255, 255, 255, 0.9)",
-            bodyColor: "rgba(255, 255, 255, 0.8)",
-            borderColor: "rgba(255, 255, 255, 0.1)",
+            backgroundColor: colorBgElevated,
+            titleColor: colorText,
+            bodyColor: colorTextSecondary,
+            borderColor: colorBorder,
             borderWidth: 1,
             padding: 12,
             displayColors: true,
@@ -301,7 +303,7 @@ export function GraphSection() {
             display: true,
             position: "bottom",
             labels: {
-              color: "rgba(255, 255, 255, 0.7)",
+              color: colorTextSecondary,
               font: {
                 size: 12,
               },
@@ -339,15 +341,32 @@ export function GraphSection() {
     }
   })
 
+  createEffect(async () => {
+    const data = await getCosts(params.id!, store.year, store.month)
+    setStore({ data })
+  })
+
   createEffect(() => {
     const config = chartConfig()
     if (!config || !canvasRef) return
 
     if (chartInstance) chartInstance.destroy()
     chartInstance = new Chart(canvasRef, config)
+
+    onCleanup(() => chartInstance?.destroy())
   })
 
-  onCleanup(() => chartInstance?.destroy())
+  createEffect(() => {
+    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
+    setStore({ colorScheme: mediaQuery.matches ? "dark" : "light" })
+
+    const handleColorSchemeChange = (e: MediaQueryListEvent) => {
+      setStore({ colorScheme: e.matches ? "dark" : "light" })
+    }
+
+    mediaQuery.addEventListener("change", handleColorSchemeChange)
+    onCleanup(() => mediaQuery.removeEventListener("change", handleColorSchemeChange))
+  })
 
   return (
     <section class={styles.root}>
@@ -356,55 +375,53 @@ export function GraphSection() {
         <p>Usage costs broken down by model.</p>
       </div>
 
-      <Show when={getData()}>
-        <div data-slot="filter-container">
-          <div data-slot="month-picker">
-            <button data-slot="month-button" onClick={onPreviousMonth}>
-              <IconChevronLeft />
+      <div data-slot="filter-container">
+        <div data-slot="month-picker">
+          <button data-slot="month-button" onClick={onPreviousMonth}>
+            <IconChevronLeft />
+          </button>
+          <span data-slot="month-label">{formatMonthYear()}</span>
+          <button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
+            <IconChevronRight />
+          </button>
+        </div>
+        <Dropdown
+          trigger={store.model === null ? "All Models" : store.model}
+          open={store.modelDropdownOpen}
+          onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
+        >
+          <>
+            <button data-slot="model-item" onClick={() => onSelectModel(null)}>
+              <span>All Models</span>
             </button>
-            <span data-slot="month-label">{formatMonthYear()}</span>
-            <button data-slot="month-button" onClick={onNextMonth} disabled={isCurrentMonth()}>
-              <IconChevronRight />
+            <For each={getModels()}>
+              {(model) => (
+                <button data-slot="model-item" onClick={() => onSelectModel(model)}>
+                  <span>{model}</span>
+                </button>
+              )}
+            </For>
+          </>
+        </Dropdown>
+        <Dropdown
+          trigger={getKeyName(store.key)}
+          open={store.keyDropdownOpen}
+          onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
+        >
+          <>
+            <button data-slot="model-item" onClick={() => onSelectKey(null)}>
+              <span>All Keys</span>
             </button>
-          </div>
-          <Dropdown
-            trigger={store.model === null ? "All Models" : store.model}
-            open={store.modelDropdownOpen}
-            onOpenChange={(open) => setStore({ modelDropdownOpen: open })}
-          >
-            <>
-              <button data-slot="model-item" onClick={() => onSelectModel(null)}>
-                <span>All Models</span>
-              </button>
-              <For each={getModels()}>
-                {(model) => (
-                  <button data-slot="model-item" onClick={() => onSelectModel(model)}>
-                    <span>{model}</span>
-                  </button>
-                )}
-              </For>
-            </>
-          </Dropdown>
-          <Dropdown
-            trigger={getKeyName(store.key)}
-            open={store.keyDropdownOpen}
-            onOpenChange={(open) => setStore({ keyDropdownOpen: open })}
-          >
-            <>
-              <button data-slot="model-item" onClick={() => onSelectKey(null)}>
-                <span>All Keys</span>
-              </button>
-              <For each={getData()?.keys || []}>
-                {(key) => (
-                  <button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
-                    <span>{key.displayName}</span>
-                  </button>
-                )}
-              </For>
-            </>
-          </Dropdown>
-        </div>
-      </Show>
+            <For each={store.data?.keys || []}>
+              {(key) => (
+                <button data-slot="model-item" onClick={() => onSelectKey(key.id)}>
+                  <span>{key.displayName}</span>
+                </button>
+              )}
+            </For>
+          </>
+        </Dropdown>
+      </div>
 
       <Show
         when={chartConfig()}

+ 21 - 9
packages/console/app/src/routes/zen/util/dataDumper.ts

@@ -1,25 +1,37 @@
 import { Resource, waitUntil } from "@opencode-ai/console-resource"
 
-export function createDataDumper(sessionId: string, requestId: string) {
+export function createDataDumper(sessionId: string, requestId: string, projectId: string) {
   if (Resource.App.stage !== "production") return
+  if (sessionId === "") return
 
-  let data: Record<string, any> = {}
-  let modelName: string | undefined
+  let data: Record<string, any> = { sessionId, requestId, projectId }
+  let metadata: Record<string, any> = { sessionId, requestId, projectId }
 
   return {
-    provideModel: (model?: string) => (modelName = model),
+    provideModel: (model?: string) => {
+      data.modelName = model
+      metadata.modelName = model
+    },
     provideRequest: (request: string) => (data.request = request),
     provideResponse: (response: string) => (data.response = response),
     provideStream: (chunk: string) => (data.response = (data.response ?? "") + chunk),
     flush: () => {
-      if (!modelName) return
+      if (!data.modelName) return
+
+      const timestamp = new Date().toISOString().replace(/[^0-9]/g, "")
 
-      const str = new Date().toISOString().replace(/[^0-9]/g, "")
-      const yyyymmdd = str.substring(0, 8)
-      const hh = str.substring(8, 10)
+      waitUntil(
+        Resource.ZenData.put(
+          `data/${data.modelName}/${sessionId}/${requestId}.json`,
+          JSON.stringify({ timestamp, ...data }),
+        ),
+      )
 
       waitUntil(
-        Resource.ConsoleData.put(`${yyyymmdd}/${hh}/${modelName}/${sessionId}/${requestId}.json`, JSON.stringify(data)),
+        Resource.ZenData.put(
+          `meta/${data.modelName}/${timestamp}/${requestId}.json`,
+          JSON.stringify({ timestamp, ...metadata }),
+        ),
       )
     },
   }

+ 38 - 14
packages/console/app/src/routes/zen/util/handler.ts

@@ -13,13 +13,7 @@ import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
 import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
 import { logger } from "./logger"
 import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
-import {
-  createBodyConverter,
-  createStreamPartConverter,
-  createResponseConverter,
-  ProviderHelper,
-  UsageInfo,
-} from "./provider/provider"
+import { createBodyConverter, createStreamPartConverter, createResponseConverter, UsageInfo } from "./provider/provider"
 import { anthropicHelper } from "./provider/anthropic"
 import { googleHelper } from "./provider/google"
 import { openaiHelper } from "./provider/openai"
@@ -27,6 +21,7 @@ import { oaCompatHelper } from "./provider/openai-compatible"
 import { createRateLimiter } from "./rateLimiter"
 import { createDataDumper } from "./dataDumper"
 import { createTrialLimiter } from "./trialLimiter"
+import { createStickyTracker } from "./stickyProviderTracker"
 
 type ZenData = Awaited<ReturnType<typeof ZenData.list>>
 type RetryOptions = {
@@ -61,6 +56,7 @@ export async function handler(
     const ip = input.request.headers.get("x-real-ip") ?? ""
     const sessionId = input.request.headers.get("x-opencode-session") ?? ""
     const requestId = input.request.headers.get("x-opencode-request") ?? ""
+    const projectId = input.request.headers.get("x-opencode-project") ?? ""
     logger.metric({
       is_tream: isStream,
       session: sessionId,
@@ -68,15 +64,25 @@ export async function handler(
     })
     const zenData = ZenData.list()
     const modelInfo = validateModel(zenData, model)
-    const dataDumper = createDataDumper(sessionId, requestId)
+    const dataDumper = createDataDumper(sessionId, requestId, projectId)
     const trialLimiter = createTrialLimiter(modelInfo.trial?.limit, ip)
     const isTrial = await trialLimiter?.isTrial()
     const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
     await rateLimiter?.check()
+    const stickyTracker = createStickyTracker(modelInfo.stickyProvider ?? false, sessionId)
+    const stickyProvider = await stickyTracker?.get()
 
     const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
-      const providerInfo = selectProvider(zenData, modelInfo, sessionId, isTrial ?? false, retry)
-      const authInfo = await authenticate(modelInfo, providerInfo)
+      const authInfo = await authenticate(modelInfo)
+      const providerInfo = selectProvider(
+        zenData,
+        authInfo,
+        modelInfo,
+        sessionId,
+        isTrial ?? false,
+        retry,
+        stickyProvider,
+      )
       validateBilling(authInfo, modelInfo)
       validateModelSettings(authInfo)
       updateProviderKey(authInfo, providerInfo)
@@ -126,6 +132,9 @@ export async function handler(
     dataDumper?.provideModel(providerInfo.storeModel)
     dataDumper?.provideRequest(reqBody)
 
+    // Store sticky provider
+    await stickyTracker?.set(providerInfo.id)
+
     // Scrub response headers
     const resHeaders = new Headers()
     const keepHeaders = ["content-type", "cache-control"]
@@ -290,16 +299,27 @@ export async function handler(
 
   function selectProvider(
     zenData: ZenData,
+    authInfo: AuthInfo,
     modelInfo: ModelInfo,
     sessionId: string,
     isTrial: boolean,
     retry: RetryOptions,
+    stickyProvider: string | undefined,
   ) {
     const provider = (() => {
+      if (authInfo?.provider?.credentials) {
+        return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
+      }
+
       if (isTrial) {
         return modelInfo.providers.find((provider) => provider.id === modelInfo.trial!.provider)
       }
 
+      if (stickyProvider) {
+        const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider)
+        if (provider) return provider
+      }
+
       if (retry.retryCount === MAX_RETRIES) {
         return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
       }
@@ -335,7 +355,7 @@ export async function handler(
     }
   }
 
-  async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) {
+  async function authenticate(modelInfo: ModelInfo) {
     const apiKey = opts.parseApiKey(input.request.headers)
     if (!apiKey || apiKey === "public") {
       if (modelInfo.allowAnonymous) return
@@ -373,7 +393,12 @@ export async function handler(
         .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id)))
         .leftJoin(
           ProviderTable,
-          and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)),
+          modelInfo.byokProvider
+            ? and(
+                eq(ProviderTable.workspaceID, KeyTable.workspaceID),
+                eq(ProviderTable.provider, modelInfo.byokProvider),
+              )
+            : sql`false`,
         )
         .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
         .then((rows) => rows[0]),
@@ -450,8 +475,7 @@ export async function handler(
   }
 
   function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {
-    if (!authInfo) return
-    if (!authInfo.provider?.credentials) return
+    if (!authInfo?.provider?.credentials) return
     providerInfo.apiKey = authInfo.provider.credentials
   }
 

+ 16 - 0
packages/console/app/src/routes/zen/util/stickyProviderTracker.ts

@@ -0,0 +1,16 @@
+import { Resource } from "@opencode-ai/console-resource"
+
+export function createStickyTracker(stickyProvider: boolean, session: string) {
+  if (!stickyProvider) return
+  if (!session) return
+  const key = `sticky:${session}`
+
+  return {
+    get: async () => {
+      return await Resource.GatewayKv.get(key)
+    },
+    set: async (providerId: string) => {
+      await Resource.GatewayKv.put(key, providerId, { expirationTtl: 86400 })
+    },
+  }
+}

+ 1 - 1
packages/console/core/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "private": true,
   "type": "module",
   "dependencies": {

+ 5 - 6
packages/console/core/src/account.ts

@@ -11,7 +11,7 @@ export namespace Account {
       id: z.string().optional(),
     }),
     async (input) =>
-      Database.transaction(async (tx) => {
+      Database.use(async (tx) => {
         const id = input.id ?? Identifier.create("account")
         await tx.insert(AccountTable).values({
           id,
@@ -21,13 +21,12 @@ export namespace Account {
   )
 
   export const fromID = fn(z.string(), async (id) =>
-    Database.transaction(async (tx) => {
-      return tx
+    Database.use((tx) =>
+      tx
         .select()
         .from(AccountTable)
         .where(eq(AccountTable.id, id))
-        .execute()
-        .then((rows) => rows[0])
-    }),
+        .then((rows) => rows[0]),
+    ),
   )
 }

+ 2 - 0
packages/console/core/src/model.ts

@@ -24,6 +24,8 @@ export namespace ZenData {
     cost: ModelCostSchema,
     cost200K: ModelCostSchema.optional(),
     allowAnonymous: z.boolean().optional(),
+    byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
+    stickyProvider: z.boolean().optional(),
     trial: z
       .object({
         limit: z.number(),

+ 1 - 1
packages/console/core/src/provider.ts

@@ -47,7 +47,7 @@ export namespace Provider {
     }),
     async ({ provider }) => {
       Actor.assertAdmin()
-      return Database.transaction((tx) =>
+      return Database.use((tx) =>
         tx
           .delete(ProviderTable)
           .where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),

+ 117 - 113
packages/console/core/sst-env.d.ts

@@ -6,124 +6,128 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    ADMIN_SECRET: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    AWS_SES_ACCESS_KEY_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AWS_SES_SECRET_ACCESS_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_API_TOKEN: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
-    }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
-    }
-    Desktop: {
-      type: "sst.cloudflare.StaticSite"
-      url: string
-    }
-    EMAILOCTOPUS_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2AccessKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2SecretKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
-    }
-    ZEN_MODELS1: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS2: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS3: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS4: {
-      type: "sst.sst.Secret"
-      value: string
+    "ADMIN_SECRET": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "AWS_SES_ACCESS_KEY_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AWS_SES_SECRET_ACCESS_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_API_TOKEN": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
+    }
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
+    }
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Enterprise": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2AccessKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2SecretKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS1": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS2": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS3": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS4": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    ConsoleData: cloudflare.R2Bucket
-    EnterpriseStorage: cloudflare.R2Bucket
-    GatewayKv: cloudflare.KVNamespace
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "EnterpriseStorage": cloudflare.R2Bucket
+    "GatewayKv": cloudflare.KVNamespace
+    "LogProcessor": cloudflare.Service
+    "ZenData": cloudflare.R2Bucket
   }
 }
 

+ 1 - 1
packages/console/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 1 - 1
packages/console/function/src/auth.ts

@@ -194,7 +194,7 @@ export default {
         // Get workspace
         await Actor.provide("account", { accountID, email }, async () => {
           await User.joinInvitedWorkspaces()
-          const workspaces = await Database.transaction(async (tx) =>
+          const workspaces = await Database.use((tx) =>
             tx
               .select({ id: WorkspaceTable.id })
               .from(WorkspaceTable)

+ 117 - 113
packages/console/function/sst-env.d.ts

@@ -6,124 +6,128 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    ADMIN_SECRET: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    AWS_SES_ACCESS_KEY_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AWS_SES_SECRET_ACCESS_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_API_TOKEN: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
-    }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
-    }
-    Desktop: {
-      type: "sst.cloudflare.StaticSite"
-      url: string
-    }
-    EMAILOCTOPUS_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2AccessKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2SecretKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
-    }
-    ZEN_MODELS1: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS2: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS3: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS4: {
-      type: "sst.sst.Secret"
-      value: string
+    "ADMIN_SECRET": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "AWS_SES_ACCESS_KEY_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AWS_SES_SECRET_ACCESS_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_API_TOKEN": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
+    }
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
+    }
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Enterprise": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2AccessKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2SecretKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS1": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS2": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS3": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS4": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    ConsoleData: cloudflare.R2Bucket
-    EnterpriseStorage: cloudflare.R2Bucket
-    GatewayKv: cloudflare.KVNamespace
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "EnterpriseStorage": cloudflare.R2Bucket
+    "GatewayKv": cloudflare.KVNamespace
+    "LogProcessor": cloudflare.Service
+    "ZenData": cloudflare.R2Bucket
   }
 }
 

+ 1 - 1
packages/console/mail/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",

+ 2 - 2
packages/console/resource/resource.node.ts

@@ -2,8 +2,8 @@ import type { KVNamespaceListOptions, KVNamespaceListResult, KVNamespacePutOptio
 import { Resource as ResourceBase } from "sst"
 import Cloudflare from "cloudflare"
 
-export const waitUntil = async (fn: () => Promise<void>) => {
-  await fn()
+export const waitUntil = async (promise: Promise<any>) => {
+  await promise
 }
 
 export const Resource = new Proxy(

+ 117 - 113
packages/console/resource/sst-env.d.ts

@@ -6,124 +6,128 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    ADMIN_SECRET: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    AWS_SES_ACCESS_KEY_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AWS_SES_SECRET_ACCESS_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_API_TOKEN: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
-    }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
-    }
-    Desktop: {
-      type: "sst.cloudflare.StaticSite"
-      url: string
-    }
-    EMAILOCTOPUS_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2AccessKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2SecretKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
-    }
-    ZEN_MODELS1: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS2: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS3: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS4: {
-      type: "sst.sst.Secret"
-      value: string
+    "ADMIN_SECRET": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "AWS_SES_ACCESS_KEY_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AWS_SES_SECRET_ACCESS_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_API_TOKEN": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
+    }
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
+    }
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Enterprise": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2AccessKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2SecretKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS1": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS2": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS3": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS4": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    ConsoleData: cloudflare.R2Bucket
-    EnterpriseStorage: cloudflare.R2Bucket
-    GatewayKv: cloudflare.KVNamespace
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "EnterpriseStorage": cloudflare.R2Bucket
+    "GatewayKv": cloudflare.KVNamespace
+    "LogProcessor": cloudflare.Service
+    "ZenData": cloudflare.R2Bucket
   }
 }
 

+ 3 - 1
packages/desktop/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/desktop",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "description": "",
   "type": "module",
   "scripts": {
@@ -33,11 +33,13 @@
     "@solid-primitives/resize-observer": "2.1.3",
     "@solid-primitives/scroll": "2.1.3",
     "@solid-primitives/storage": "4.3.3",
+    "@solid-primitives/websocket": "1.3.1",
     "@solidjs/meta": "catalog:",
     "@solidjs/router": "catalog:",
     "@thisbeyond/solid-dnd": "0.7.5",
     "diff": "catalog:",
     "fuzzysort": "catalog:",
+    "ghostty-web": "0.3.0",
     "luxon": "catalog:",
     "marked": "16.2.0",
     "marked-shiki": "1.2.1",

+ 649 - 0
packages/desktop/src/addons/serialize.ts

@@ -0,0 +1,649 @@
+/**
+ * SerializeAddon - Serialize terminal buffer contents
+ *
+ * Port of xterm.js addon-serialize for ghostty-web.
+ * Enables serialization of terminal contents to a string that can
+ * be written back to restore terminal state.
+ *
+ * Usage:
+ * ```typescript
+ * const serializeAddon = new SerializeAddon();
+ * term.loadAddon(serializeAddon);
+ * const content = serializeAddon.serialize();
+ * ```
+ */
+
+import type { ITerminalAddon, ITerminalCore, IBufferRange } from "ghostty-web"
+
+// ============================================================================
+// Buffer Types (matching ghostty-web internal interfaces)
+// ============================================================================
+
+interface IBuffer {
+  readonly type: "normal" | "alternate"
+  readonly cursorX: number
+  readonly cursorY: number
+  readonly viewportY: number
+  readonly baseY: number
+  readonly length: number
+  getLine(y: number): IBufferLine | undefined
+  getNullCell(): IBufferCell
+}
+
+interface IBufferLine {
+  readonly length: number
+  readonly isWrapped: boolean
+  getCell(x: number): IBufferCell | undefined
+  translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string
+}
+
+interface IBufferCell {
+  getChars(): string
+  getCode(): number
+  getWidth(): number
+  getFgColorMode(): number
+  getBgColorMode(): number
+  getFgColor(): number
+  getBgColor(): number
+  isBold(): number
+  isItalic(): number
+  isUnderline(): number
+  isStrikethrough(): number
+  isBlink(): number
+  isInverse(): number
+  isInvisible(): number
+  isFaint(): number
+  isDim(): boolean
+}
+
+// ============================================================================
+// Types
+// ============================================================================
+
+export interface ISerializeOptions {
+  /**
+   * The row range to serialize. When an explicit range is specified, the cursor
+   * will get its final repositioning.
+   */
+  range?: ISerializeRange
+  /**
+   * The number of rows in the scrollback buffer to serialize, starting from
+   * the bottom of the scrollback buffer. When not specified, all available
+   * rows in the scrollback buffer will be serialized.
+   */
+  scrollback?: number
+  /**
+   * Whether to exclude the terminal modes from the serialization.
+   * Default: false
+   */
+  excludeModes?: boolean
+  /**
+   * Whether to exclude the alt buffer from the serialization.
+   * Default: false
+   */
+  excludeAltBuffer?: boolean
+}
+
+export interface ISerializeRange {
+  /**
+   * The line to start serializing (inclusive).
+   */
+  start: number
+  /**
+   * The line to end serializing (inclusive).
+   */
+  end: number
+}
+
+export interface IHTMLSerializeOptions {
+  /**
+   * The number of rows in the scrollback buffer to serialize, starting from
+   * the bottom of the scrollback buffer.
+   */
+  scrollback?: number
+  /**
+   * Whether to only serialize the selection.
+   * Default: false
+   */
+  onlySelection?: boolean
+  /**
+   * Whether to include the global background of the terminal.
+   * Default: false
+   */
+  includeGlobalBackground?: boolean
+  /**
+   * The range to serialize. This is prioritized over onlySelection.
+   */
+  range?: {
+    startLine: number
+    endLine: number
+    startCol: number
+  }
+}
+
+// ============================================================================
+// Helper Functions
+// ============================================================================
+
+function constrain(value: number, low: number, high: number): number {
+  return Math.max(low, Math.min(value, high))
+}
+
+function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
+  return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor()
+}
+
+function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
+  return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor()
+}
+
+function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
+  return (
+    !!cell1.isInverse() === !!cell2.isInverse() &&
+    !!cell1.isBold() === !!cell2.isBold() &&
+    !!cell1.isUnderline() === !!cell2.isUnderline() &&
+    !!cell1.isBlink() === !!cell2.isBlink() &&
+    !!cell1.isInvisible() === !!cell2.isInvisible() &&
+    !!cell1.isItalic() === !!cell2.isItalic() &&
+    !!cell1.isDim() === !!cell2.isDim() &&
+    !!cell1.isStrikethrough() === !!cell2.isStrikethrough()
+  )
+}
+
+// ============================================================================
+// Base Serialize Handler
+// ============================================================================
+
+abstract class BaseSerializeHandler {
+  constructor(protected readonly _buffer: IBuffer) {}
+
+  public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
+    let oldCell = this._buffer.getNullCell()
+
+    const startRow = range.start.y
+    const endRow = range.end.y
+    const startColumn = range.start.x
+    const endColumn = range.end.x
+
+    this._beforeSerialize(endRow - startRow, startRow, endRow)
+
+    for (let row = startRow; row <= endRow; row++) {
+      const line = this._buffer.getLine(row)
+      if (line) {
+        const startLineColumn = row === range.start.y ? startColumn : 0
+        const endLineColumn = row === range.end.y ? endColumn : line.length
+        for (let col = startLineColumn; col < endLineColumn; col++) {
+          const c = line.getCell(col)
+          if (!c) {
+            continue
+          }
+          this._nextCell(c, oldCell, row, col)
+          oldCell = c
+        }
+      }
+      this._rowEnd(row, row === endRow)
+    }
+
+    this._afterSerialize()
+
+    return this._serializeString(excludeFinalCursorPosition)
+  }
+
+  protected _nextCell(_cell: IBufferCell, _oldCell: IBufferCell, _row: number, _col: number): void {}
+  protected _rowEnd(_row: number, _isLastRow: boolean): void {}
+  protected _beforeSerialize(_rows: number, _startRow: number, _endRow: number): void {}
+  protected _afterSerialize(): void {}
+  protected _serializeString(_excludeFinalCursorPosition?: boolean): string {
+    return ""
+  }
+}
+
+// ============================================================================
+// String Serialize Handler
+// ============================================================================
+
+class StringSerializeHandler extends BaseSerializeHandler {
+  private _rowIndex: number = 0
+  private _allRows: string[] = []
+  private _allRowSeparators: string[] = []
+  private _currentRow: string = ""
+  private _nullCellCount: number = 0
+  private _cursorStyle: IBufferCell
+  private _cursorStyleRow: number = 0
+  private _cursorStyleCol: number = 0
+  private _backgroundCell: IBufferCell
+  private _firstRow: number = 0
+  private _lastCursorRow: number = 0
+  private _lastCursorCol: number = 0
+  private _lastContentCursorRow: number = 0
+  private _lastContentCursorCol: number = 0
+  private _thisRowLastChar: IBufferCell
+  private _thisRowLastSecondChar: IBufferCell
+  private _nextRowFirstChar: IBufferCell
+
+  constructor(
+    buffer: IBuffer,
+    private readonly _terminal: ITerminalCore,
+  ) {
+    super(buffer)
+    this._cursorStyle = this._buffer.getNullCell()
+    this._backgroundCell = this._buffer.getNullCell()
+    this._thisRowLastChar = this._buffer.getNullCell()
+    this._thisRowLastSecondChar = this._buffer.getNullCell()
+    this._nextRowFirstChar = this._buffer.getNullCell()
+  }
+
+  protected _beforeSerialize(rows: number, start: number, _end: number): void {
+    this._allRows = new Array<string>(rows)
+    this._lastContentCursorRow = start
+    this._lastCursorRow = start
+    this._firstRow = start
+  }
+
+  protected _rowEnd(row: number, isLastRow: boolean): void {
+    // if there is colorful empty cell at line end, we must pad it back
+    if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
+      this._currentRow += `\u001b[${this._nullCellCount}X`
+    }
+
+    let rowSeparator = ""
+
+    if (!isLastRow) {
+      // Enable BCE
+      if (row - this._firstRow >= this._terminal.rows) {
+        const line = this._buffer.getLine(this._cursorStyleRow)
+        const cell = line?.getCell(this._cursorStyleCol)
+        if (cell) {
+          this._backgroundCell = cell
+        }
+      }
+
+      const currentLine = this._buffer.getLine(row)!
+      const nextLine = this._buffer.getLine(row + 1)!
+
+      if (!nextLine.isWrapped) {
+        rowSeparator = "\r\n"
+        this._lastCursorRow = row + 1
+        this._lastCursorCol = 0
+      } else {
+        rowSeparator = ""
+        const thisRowLastChar = currentLine.getCell(currentLine.length - 1)
+        const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2)
+        const nextRowFirstChar = nextLine.getCell(0)
+
+        if (thisRowLastChar) this._thisRowLastChar = thisRowLastChar
+        if (thisRowLastSecondChar) this._thisRowLastSecondChar = thisRowLastSecondChar
+        if (nextRowFirstChar) this._nextRowFirstChar = nextRowFirstChar
+
+        const isNextRowFirstCharDoubleWidth = this._nextRowFirstChar.getWidth() > 1
+
+        let isValid = false
+
+        if (
+          this._nextRowFirstChar.getChars() &&
+          (isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0)
+        ) {
+          if (
+            (this._thisRowLastChar.getChars() || this._thisRowLastChar.getWidth() === 0) &&
+            equalBg(this._thisRowLastChar, this._nextRowFirstChar)
+          ) {
+            isValid = true
+          }
+
+          if (
+            isNextRowFirstCharDoubleWidth &&
+            (this._thisRowLastSecondChar.getChars() || this._thisRowLastSecondChar.getWidth() === 0) &&
+            equalBg(this._thisRowLastChar, this._nextRowFirstChar) &&
+            equalBg(this._thisRowLastSecondChar, this._nextRowFirstChar)
+          ) {
+            isValid = true
+          }
+        }
+
+        if (!isValid) {
+          rowSeparator = "-".repeat(this._nullCellCount + 1)
+          rowSeparator += "\u001b[1D\u001b[1X"
+
+          if (this._nullCellCount > 0) {
+            rowSeparator += "\u001b[A"
+            rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`
+            rowSeparator += `\u001b[${this._nullCellCount}X`
+            rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`
+            rowSeparator += "\u001b[B"
+          }
+
+          this._lastContentCursorRow = row + 1
+          this._lastContentCursorCol = 0
+          this._lastCursorRow = row + 1
+          this._lastCursorCol = 0
+        }
+      }
+    }
+
+    this._allRows[this._rowIndex] = this._currentRow
+    this._allRowSeparators[this._rowIndex++] = rowSeparator
+    this._currentRow = ""
+    this._nullCellCount = 0
+  }
+
+  private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
+    const sgrSeq: number[] = []
+    const fgChanged = !equalFg(cell, oldCell)
+    const bgChanged = !equalBg(cell, oldCell)
+    const flagsChanged = !equalFlags(cell, oldCell)
+
+    if (fgChanged || bgChanged || flagsChanged) {
+      if (this._isAttributeDefault(cell)) {
+        if (!this._isAttributeDefault(oldCell)) {
+          sgrSeq.push(0)
+        }
+      } else {
+        if (fgChanged) {
+          const color = cell.getFgColor()
+          const mode = cell.getFgColorMode()
+          if (mode === 2) {
+            // RGB
+            sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
+          } else if (mode === 1) {
+            // Palette
+            if (color >= 16) {
+              sgrSeq.push(38, 5, color)
+            } else {
+              sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7))
+            }
+          } else {
+            sgrSeq.push(39)
+          }
+        }
+        if (bgChanged) {
+          const color = cell.getBgColor()
+          const mode = cell.getBgColorMode()
+          if (mode === 2) {
+            // RGB
+            sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
+          } else if (mode === 1) {
+            // Palette
+            if (color >= 16) {
+              sgrSeq.push(48, 5, color)
+            } else {
+              sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7))
+            }
+          } else {
+            sgrSeq.push(49)
+          }
+        }
+        if (flagsChanged) {
+          if (!!cell.isInverse() !== !!oldCell.isInverse()) {
+            sgrSeq.push(cell.isInverse() ? 7 : 27)
+          }
+          if (!!cell.isBold() !== !!oldCell.isBold()) {
+            sgrSeq.push(cell.isBold() ? 1 : 22)
+          }
+          if (!!cell.isUnderline() !== !!oldCell.isUnderline()) {
+            sgrSeq.push(cell.isUnderline() ? 4 : 24)
+          }
+          if (!!cell.isBlink() !== !!oldCell.isBlink()) {
+            sgrSeq.push(cell.isBlink() ? 5 : 25)
+          }
+          if (!!cell.isInvisible() !== !!oldCell.isInvisible()) {
+            sgrSeq.push(cell.isInvisible() ? 8 : 28)
+          }
+          if (!!cell.isItalic() !== !!oldCell.isItalic()) {
+            sgrSeq.push(cell.isItalic() ? 3 : 23)
+          }
+          if (!!cell.isDim() !== !!oldCell.isDim()) {
+            sgrSeq.push(cell.isDim() ? 2 : 22)
+          }
+          if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) {
+            sgrSeq.push(cell.isStrikethrough() ? 9 : 29)
+          }
+        }
+      }
+    }
+
+    return sgrSeq
+  }
+
+  private _isAttributeDefault(cell: IBufferCell): boolean {
+    return (
+      cell.getFgColorMode() === 0 &&
+      cell.getBgColorMode() === 0 &&
+      !cell.isBold() &&
+      !cell.isItalic() &&
+      !cell.isUnderline() &&
+      !cell.isBlink() &&
+      !cell.isInverse() &&
+      !cell.isInvisible() &&
+      !cell.isDim() &&
+      !cell.isStrikethrough()
+    )
+  }
+
+  protected _nextCell(cell: IBufferCell, _oldCell: IBufferCell, row: number, col: number): void {
+    const isPlaceHolderCell = cell.getWidth() === 0
+
+    if (isPlaceHolderCell) {
+      return
+    }
+
+    const isEmptyCell = cell.getChars() === ""
+
+    const sgrSeq = this._diffStyle(cell, this._cursorStyle)
+
+    const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0
+
+    if (styleChanged) {
+      if (this._nullCellCount > 0) {
+        if (!equalBg(this._cursorStyle, this._backgroundCell)) {
+          this._currentRow += `\u001b[${this._nullCellCount}X`
+        }
+        this._currentRow += `\u001b[${this._nullCellCount}C`
+        this._nullCellCount = 0
+      }
+
+      this._lastContentCursorRow = this._lastCursorRow = row
+      this._lastContentCursorCol = this._lastCursorCol = col
+
+      this._currentRow += `\u001b[${sgrSeq.join(";")}m`
+
+      const line = this._buffer.getLine(row)
+      const cellFromLine = line?.getCell(col)
+      if (cellFromLine) {
+        this._cursorStyle = cellFromLine
+        this._cursorStyleRow = row
+        this._cursorStyleCol = col
+      }
+    }
+
+    if (isEmptyCell) {
+      this._nullCellCount += cell.getWidth()
+    } else {
+      if (this._nullCellCount > 0) {
+        if (equalBg(this._cursorStyle, this._backgroundCell)) {
+          this._currentRow += `\u001b[${this._nullCellCount}C`
+        } else {
+          this._currentRow += `\u001b[${this._nullCellCount}X`
+          this._currentRow += `\u001b[${this._nullCellCount}C`
+        }
+        this._nullCellCount = 0
+      }
+
+      this._currentRow += cell.getChars()
+
+      this._lastContentCursorRow = this._lastCursorRow = row
+      this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth()
+    }
+  }
+
+  protected _serializeString(excludeFinalCursorPosition?: boolean): string {
+    let rowEnd = this._allRows.length
+
+    if (this._buffer.length - this._firstRow <= this._terminal.rows) {
+      rowEnd = this._lastContentCursorRow + 1 - this._firstRow
+      this._lastCursorCol = this._lastContentCursorCol
+      this._lastCursorRow = this._lastContentCursorRow
+    }
+
+    let content = ""
+
+    for (let i = 0; i < rowEnd; i++) {
+      content += this._allRows[i]
+      if (i + 1 < rowEnd) {
+        content += this._allRowSeparators[i]
+      }
+    }
+
+    if (!excludeFinalCursorPosition) {
+      // Get cursor position relative to viewport (1-indexed for ANSI)
+      // cursorY is relative to the viewport, cursorX is column position
+      const cursorRow = this._buffer.cursorY + 1 // 1-indexed
+      const cursorCol = this._buffer.cursorX + 1 // 1-indexed
+
+      // Use absolute cursor positioning (CUP - Cursor Position)
+      // This is more reliable than relative moves which depend on knowing
+      // exactly where the cursor ended up after all the content
+      content += `\u001b[${cursorRow};${cursorCol}H`
+    }
+
+    return content
+  }
+}
+
+// ============================================================================
+// SerializeAddon Class
+// ============================================================================
+
+export class SerializeAddon implements ITerminalAddon {
+  private _terminal?: ITerminalCore
+
+  /**
+   * Activate the addon (called by Terminal.loadAddon)
+   */
+  public activate(terminal: ITerminalCore): void {
+    this._terminal = terminal
+  }
+
+  /**
+   * Dispose the addon and clean up resources
+   */
+  public dispose(): void {
+    this._terminal = undefined
+  }
+
+  /**
+   * Serializes terminal rows into a string that can be written back to the
+   * terminal to restore the state. The cursor will also be positioned to the
+   * correct cell.
+   *
+   * @param options Custom options to allow control over what gets serialized.
+   */
+  public serialize(options?: ISerializeOptions): string {
+    if (!this._terminal) {
+      throw new Error("Cannot use addon until it has been loaded")
+    }
+
+    const terminal = this._terminal as any
+    const buffer = terminal.buffer
+
+    if (!buffer) {
+      return ""
+    }
+
+    const activeBuffer = buffer.active || buffer.normal
+    if (!activeBuffer) {
+      return ""
+    }
+
+    let content = options?.range
+      ? this._serializeBufferByRange(activeBuffer, options.range, true)
+      : this._serializeBufferByScrollback(activeBuffer, options?.scrollback)
+
+    // Handle alternate buffer if active and not excluded
+    if (!options?.excludeAltBuffer) {
+      const altBuffer = buffer.alternate
+      if (altBuffer && buffer.active?.type === "alternate") {
+        const alternateContent = this._serializeBufferByScrollback(altBuffer, undefined)
+        content += `\u001b[?1049h\u001b[H${alternateContent}`
+      }
+    }
+
+    return content
+  }
+
+  /**
+   * Serializes terminal content as plain text (no escape sequences)
+   * @param options Custom options to allow control over what gets serialized.
+   */
+  public serializeAsText(options?: { scrollback?: number; trimWhitespace?: boolean }): string {
+    if (!this._terminal) {
+      throw new Error("Cannot use addon until it has been loaded")
+    }
+
+    const terminal = this._terminal as any
+    const buffer = terminal.buffer
+
+    if (!buffer) {
+      return ""
+    }
+
+    const activeBuffer = buffer.active || buffer.normal
+    if (!activeBuffer) {
+      return ""
+    }
+
+    const maxRows = activeBuffer.length
+    const scrollback = options?.scrollback
+    const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows)
+
+    const startRow = maxRows - correctRows
+    const endRow = maxRows - 1
+    const lines: string[] = []
+
+    for (let row = startRow; row <= endRow; row++) {
+      const line = activeBuffer.getLine(row)
+      if (line) {
+        const text = line.translateToString(options?.trimWhitespace ?? true)
+        lines.push(text)
+      }
+    }
+
+    // Trim trailing empty lines if requested
+    if (options?.trimWhitespace) {
+      while (lines.length > 0 && lines[lines.length - 1] === "") {
+        lines.pop()
+      }
+    }
+
+    return lines.join("\n")
+  }
+
+  private _serializeBufferByScrollback(buffer: IBuffer, scrollback?: number): string {
+    const maxRows = buffer.length
+    const rows = this._terminal?.rows ?? 24
+    const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + rows, 0, maxRows)
+    return this._serializeBufferByRange(
+      buffer,
+      {
+        start: maxRows - correctRows,
+        end: maxRows - 1,
+      },
+      false,
+    )
+  }
+
+  private _serializeBufferByRange(
+    buffer: IBuffer,
+    range: ISerializeRange,
+    excludeFinalCursorPosition: boolean,
+  ): string {
+    const handler = new StringSerializeHandler(buffer, this._terminal!)
+    const cols = this._terminal?.cols ?? 80
+    return handler.serialize(
+      {
+        start: { x: 0, y: range.start },
+        end: { x: cols, y: range.end },
+      },
+      excludeFinalCursorPosition,
+    )
+  }
+}

+ 2 - 2
packages/desktop/src/components/prompt-input.tsx

@@ -456,9 +456,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     <img src={`https://models.dev/logos/${i.provider.id}.svg`} class="size-6 p-0.5 shrink-0" />
                     <div class="flex gap-x-3 items-baseline flex-[1_0_0]">
                       <span class="text-14-medium text-text-strong overflow-hidden text-ellipsis">{i.name}</span>
-                      <Show when={i.release_date}>
+                      <Show when={false}>
                         <span class="text-12-medium text-text-weak overflow-hidden text-ellipsis truncate min-w-0">
-                          {DateTime.fromFormat(i.release_date, "yyyy-MM-dd").toFormat("LLL yyyy")}
+                          {DateTime.fromFormat("unknown", "yyyy-MM-dd").toFormat("LLL yyyy")}
                         </span>
                       </Show>
                     </div>

+ 151 - 0
packages/desktop/src/components/terminal.tsx

@@ -0,0 +1,151 @@
+import { init, Terminal as Term, FitAddon } from "ghostty-web"
+import { ComponentProps, onCleanup, onMount, splitProps } from "solid-js"
+import { createReconnectingWS, ReconnectingWebSocket } from "@solid-primitives/websocket"
+import { useSDK } from "@/context/sdk"
+import { SerializeAddon } from "@/addons/serialize"
+import { LocalPTY } from "@/context/session"
+
+await init()
+
+export interface TerminalProps extends ComponentProps<"div"> {
+  pty: LocalPTY
+  onSubmit?: () => void
+  onCleanup?: (pty: LocalPTY) => void
+}
+
+export const Terminal = (props: TerminalProps) => {
+  const sdk = useSDK()
+  let container!: HTMLDivElement
+  const [local, others] = splitProps(props, ["pty", "class", "classList"])
+  let ws: ReconnectingWebSocket
+  let term: Term
+  let serializeAddon: SerializeAddon
+  let fitAddon: FitAddon
+
+  onMount(async () => {
+    ws = createReconnectingWS(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
+    term = new Term({
+      cursorBlink: true,
+      fontSize: 14,
+      fontFamily: "TX-02, monospace",
+      allowTransparency: true,
+      theme: {
+        background: "#191515",
+        foreground: "#d4d4d4",
+      },
+      scrollback: 10_000,
+    })
+    term.attachCustomKeyEventHandler((event) => {
+      // allow for ctrl-` to toggle terminal in parent
+      if (event.ctrlKey && event.key.toLowerCase() === "`") {
+        event.preventDefault()
+        return true
+      }
+      return false
+    })
+
+    fitAddon = new FitAddon()
+    serializeAddon = new SerializeAddon()
+    term.loadAddon(serializeAddon)
+    term.loadAddon(fitAddon)
+
+    term.open(container)
+
+    if (local.pty.buffer) {
+      const originalSize = { cols: term.cols, rows: term.rows }
+      let resized = false
+      if (local.pty.rows && local.pty.cols) {
+        term.resize(local.pty.cols, local.pty.rows)
+        resized = true
+      }
+      term.write(local.pty.buffer)
+      if (local.pty.scrollY) {
+        term.scrollToLine(local.pty.scrollY)
+      }
+      if (resized) {
+        term.resize(originalSize.cols, originalSize.rows)
+      }
+    }
+
+    container.focus()
+
+    fitAddon.fit()
+    fitAddon.observeResize()
+    window.addEventListener("resize", () => fitAddon.fit())
+    term.onResize(async (size) => {
+      if (ws && ws.readyState === WebSocket.OPEN) {
+        await sdk.client.pty.update({
+          path: { id: local.pty.id },
+          body: {
+            size: {
+              cols: size.cols,
+              rows: size.rows,
+            },
+          },
+        })
+      }
+    })
+    term.onData((data) => {
+      if (ws && ws.readyState === WebSocket.OPEN) {
+        ws.send(data)
+      }
+    })
+    term.onKey((key) => {
+      if (key.key == "Enter") {
+        props.onSubmit?.()
+      }
+    })
+    // term.onScroll((ydisp) => {
+    // console.log("Scroll position:", ydisp)
+    // })
+    ws.addEventListener("open", () => {
+      console.log("WebSocket connected")
+      sdk.client.pty.update({
+        path: { id: local.pty.id },
+        body: {
+          size: {
+            cols: term.cols,
+            rows: term.rows,
+          },
+        },
+      })
+    })
+    ws.addEventListener("message", (event) => {
+      term.write(event.data)
+    })
+    ws.addEventListener("error", (error) => {
+      console.error("WebSocket error:", error)
+    })
+    ws.addEventListener("close", () => {
+      console.log("WebSocket disconnected")
+    })
+  })
+
+  onCleanup(() => {
+    if (serializeAddon && props.onCleanup) {
+      const buffer = serializeAddon.serialize()
+      props.onCleanup({
+        ...local.pty,
+        buffer,
+        rows: term.rows,
+        cols: term.cols,
+        scrollY: term.getViewportY(),
+      })
+    }
+    ws?.close()
+    term?.dispose()
+  })
+
+  return (
+    <div
+      ref={container}
+      data-component="terminal"
+      classList={{
+        ...(local.classList ?? {}),
+        "size-full px-6 py-3 font-mono": true,
+        [local.class ?? ""]: !!local.class,
+      }}
+      {...others}
+    />
+  )
+}

+ 21 - 1
packages/desktop/src/context/layout.tsx

@@ -15,12 +15,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
           opened: true,
           width: 280,
         },
+        terminal: {
+          opened: false,
+          height: 280,
+        },
         review: {
           state: "pane" as "pane" | "tab",
         },
       }),
       {
-        name: "___default-layout",
+        name: "____default-layout",
       },
     )
 
@@ -61,6 +65,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
           setStore("sidebar", "width", width)
         },
       },
+      terminal: {
+        opened: createMemo(() => store.terminal.opened),
+        open() {
+          setStore("terminal", "opened", true)
+        },
+        close() {
+          setStore("terminal", "opened", false)
+        },
+        toggle() {
+          setStore("terminal", "opened", (x) => !x)
+        },
+        height: createMemo(() => store.terminal.height),
+        resize(height: number) {
+          setStore("terminal", "height", height)
+        },
+      },
       review: {
         state: createMemo(() => store.review?.state ?? "closed"),
         pane() {

+ 1 - 1
packages/desktop/src/context/sdk.tsx

@@ -27,6 +27,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
       abort.abort()
     })
 
-    return { directory: props.directory, client: sdk, event: emitter }
+    return { directory: props.directory, client: sdk, event: emitter, url: globalSDK.url }
   },
 })

+ 88 - 12
packages/desktop/src/context/session.tsx

@@ -8,14 +8,25 @@ import { pipe, sumBy } from "remeda"
 import { AssistantMessage, UserMessage } from "@opencode-ai/sdk"
 import { useParams } from "@solidjs/router"
 import { base64Encode } from "@/utils"
+import { useSDK } from "./sdk"
+
+export type LocalPTY = {
+  id: string
+  title: string
+  rows?: number
+  cols?: number
+  buffer?: string
+  scrollY?: number
+}
 
 export const { use: useSession, provider: SessionProvider } = createSimpleContext({
   name: "Session",
   init: () => {
+    const sdk = useSDK()
     const params = useParams()
     const sync = useSync()
     const name = createMemo(
-      () => `___${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`,
+      () => `______${base64Encode(sync.data.project.worktree)}/session${params.id ? "/" + params.id : ""}`,
     )
 
     const [store, setStore] = makePersisted(
@@ -23,16 +34,21 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
         messageId?: string
         tabs: {
           active?: string
-          opened: string[]
+          all: string[]
         }
         prompt: Prompt
         cursor?: number
+        terminals: {
+          active?: string
+          all: LocalPTY[]
+        }
       }>({
         tabs: {
-          opened: [],
+          all: [],
         },
         prompt: clonePrompt(DEFAULT_PROMPT),
         cursor: undefined,
+        terminals: { all: [] },
       }),
       {
         name: name(),
@@ -138,7 +154,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
           setStore("tabs", "active", tab)
         },
         setOpenedTabs(tabs: string[]) {
-          setStore("tabs", "opened", tabs)
+          setStore("tabs", "all", tabs)
         },
         async openTab(tab: string) {
           if (tab === "chat") {
@@ -146,8 +162,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
             return
           }
           if (tab !== "review") {
-            if (!store.tabs.opened.includes(tab)) {
-              setStore("tabs", "opened", [...store.tabs.opened, tab])
+            if (!store.tabs.all.includes(tab)) {
+              setStore("tabs", "all", [...store.tabs.all, tab])
             }
           }
           setStore("tabs", "active", tab)
@@ -156,28 +172,88 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
           batch(() => {
             setStore(
               "tabs",
-              "opened",
-              store.tabs.opened.filter((x) => x !== tab),
+              "all",
+              store.tabs.all.filter((x) => x !== tab),
             )
             if (store.tabs.active === tab) {
-              const index = store.tabs.opened.findIndex((f) => f === tab)
-              const previous = store.tabs.opened[Math.max(0, index - 1)]
+              const index = store.tabs.all.findIndex((f) => f === tab)
+              const previous = store.tabs.all[Math.max(0, index - 1)]
               setStore("tabs", "active", previous)
             }
           })
         },
         moveTab(tab: string, to: number) {
-          const index = store.tabs.opened.findIndex((f) => f === tab)
+          const index = store.tabs.all.findIndex((f) => f === tab)
           if (index === -1) return
           setStore(
             "tabs",
-            "opened",
+            "all",
             produce((opened) => {
               opened.splice(to, 0, opened.splice(index, 1)[0])
             }),
           )
         },
       },
+      terminal: {
+        all: createMemo(() => Object.values(store.terminals.all)),
+        active: createMemo(() => store.terminals.active),
+        new() {
+          sdk.client.pty.create({ body: { title: `Terminal ${store.terminals.all.length + 1}` } }).then((pty) => {
+            const id = pty.data?.id
+            if (!id) return
+            batch(() => {
+              setStore("terminals", "all", [
+                ...store.terminals.all,
+                {
+                  id,
+                  title: pty.data?.title ?? "Terminal",
+                  // rows: pty.data?.rows ?? 24,
+                  // cols: pty.data?.cols ?? 80,
+                  // buffer: "",
+                  // scrollY: 0,
+                },
+              ])
+              setStore("terminals", "active", id)
+            })
+          })
+        },
+        update(pty: Partial<LocalPTY> & { id: string }) {
+          setStore("terminals", "all", (x) => x.map((x) => (x.id === pty.id ? { ...x, ...pty } : x)))
+          sdk.client.pty.update({
+            path: { id: pty.id },
+            body: { title: pty.title, size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined },
+          })
+        },
+        open(id: string) {
+          setStore("terminals", "active", id)
+        },
+        async close(id: string) {
+          batch(() => {
+            setStore(
+              "terminals",
+              "all",
+              store.terminals.all.filter((x) => x.id !== id),
+            )
+            if (store.terminals.active === id) {
+              const index = store.terminals.all.findIndex((f) => f.id === id)
+              const previous = store.tabs.all[Math.max(0, index - 1)]
+              setStore("terminals", "active", previous)
+            }
+          })
+          await sdk.client.pty.remove({ path: { id } })
+        },
+        move(id: string, to: number) {
+          const index = store.terminals.all.findIndex((f) => f.id === id)
+          if (index === -1) return
+          setStore(
+            "terminals",
+            "all",
+            produce((all) => {
+              all.splice(to, 0, all.splice(index, 1)[0])
+            }),
+          )
+        },
+      },
     }
   },
 })

+ 109 - 5
packages/desktop/src/pages/layout.tsx

@@ -1,9 +1,9 @@
 import { createMemo, For, ParentProps, Show } from "solid-js"
 import { DateTime } from "luxon"
-import { A, useParams } from "@solidjs/router"
+import { A, useNavigate, useParams } from "@solidjs/router"
 import { useLayout } from "@/context/layout"
 import { useGlobalSync } from "@/context/global-sync"
-import { base64Encode } from "@/utils"
+import { base64Decode, base64Encode } from "@/utils"
 import { Mark } from "@opencode-ai/ui/logo"
 import { Button } from "@opencode-ai/ui/button"
 import { Icon } from "@opencode-ai/ui/icon"
@@ -12,11 +12,21 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
 import { Collapsible } from "@opencode-ai/ui/collapsible"
 import { DiffChanges } from "@opencode-ai/ui/diff-changes"
 import { getFilename } from "@opencode-ai/util/path"
+import { Select } from "@opencode-ai/ui/select"
+import { Session } from "@opencode-ai/sdk/client"
 
 export default function Layout(props: ParentProps) {
+  const navigate = useNavigate()
   const params = useParams()
   const globalSync = useGlobalSync()
   const layout = useLayout()
+  const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
+  const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
+  const currentSession = createMemo(() => sessions().find((s) => s.id === params.id) ?? sessions().at(0))
+
+  function navigateToSession(session: Session | undefined) {
+    navigate(`/${params.dir}/session/${session?.id}`)
+  }
 
   const handleOpenProject = async () => {
     // layout.projects.open(dir.)
@@ -24,7 +34,7 @@ export default function Layout(props: ParentProps) {
 
   return (
     <div class="relative h-screen flex flex-col">
-      <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base">
+      <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex">
         <A
           href="/"
           classList={{
@@ -36,16 +46,110 @@ export default function Layout(props: ParentProps) {
         >
           <Mark class="shrink-0" />
         </A>
+        <div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
+          <div class="flex items-center gap-3">
+            <div class="flex items-center gap-2">
+              <Select
+                options={layout.projects.list().map((project) => getFilename(project.directory))}
+                current={getFilename(currentDirectory())}
+                class="text-14-regular text-text-base"
+                variant="ghost"
+              />
+              <div class="text-text-weaker">/</div>
+              <Select
+                options={sessions()}
+                current={currentSession()}
+                label={(x) => x.title}
+                value={(x) => x.id}
+                onSelect={navigateToSession}
+                class="text-14-regular text-text-base max-w-3xs"
+                variant="ghost"
+              />
+            </div>
+            <Button as={A} href={`/${params.dir}/session`} icon="plus-small">
+              New session
+            </Button>
+          </div>
+          <div class="flex items-center gap-4">
+            <Tooltip
+              class="shrink-0"
+              value={
+                <div class="flex items-center gap-2">
+                  <span>Toggle terminal</span>
+                  <span class="text-icon-base text-12-medium">Ctrl `</span>
+                </div>
+              }
+            >
+              <Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
+                <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
+                  <Icon
+                    size="small"
+                    name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
+                    class="group-hover/terminal-toggle:hidden"
+                  />
+                  <Icon
+                    size="small"
+                    name="layout-bottom-partial"
+                    class="hidden group-hover/terminal-toggle:inline-block"
+                  />
+                  <Icon
+                    size="small"
+                    name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
+                    class="hidden group-active/terminal-toggle:inline-block"
+                  />
+                </div>
+              </Button>
+            </Tooltip>
+          </div>
+        </div>
       </header>
       <div class="h-[calc(100vh-3rem)] flex">
         <div
           classList={{
-            "@container w-12 pb-5 shrink-0 bg-background-base": true,
+            "relative @container w-12 pb-5 shrink-0 bg-background-base": true,
             "flex flex-col gap-5.5 items-start self-stretch justify-between": true,
             "border-r border-border-weak-base": true,
           }}
           style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
         >
+          <Show when={layout.sidebar.opened()}>
+            <div
+              class="absolute inset-y-0 right-0 z-10 w-2 translate-x-1/2 cursor-ew-resize"
+              onMouseDown={(e) => {
+                e.preventDefault()
+                const startX = e.clientX
+                const startWidth = layout.sidebar.width()
+                const maxWidth = window.innerWidth * 0.3
+                const minWidth = 150
+                const collapseThreshold = 80
+                let currentWidth = startWidth
+
+                document.body.style.userSelect = "none"
+                document.body.style.overflow = "hidden"
+
+                const onMouseMove = (moveEvent: MouseEvent) => {
+                  const deltaX = moveEvent.clientX - startX
+                  currentWidth = startWidth + deltaX
+                  const clampedWidth = Math.min(maxWidth, Math.max(minWidth, currentWidth))
+                  layout.sidebar.resize(clampedWidth)
+                }
+
+                const onMouseUp = () => {
+                  document.body.style.userSelect = ""
+                  document.body.style.overflow = ""
+                  document.removeEventListener("mousemove", onMouseMove)
+                  document.removeEventListener("mouseup", onMouseUp)
+
+                  if (currentWidth < collapseThreshold) {
+                    layout.sidebar.close()
+                  }
+                }
+
+                document.addEventListener("mousemove", onMouseMove)
+                document.addEventListener("mouseup", onMouseUp)
+              }}
+            />
+          </Show>
           <div class="grow flex flex-col items-start self-stretch gap-4 p-2 min-h-0">
             <Tooltip class="shrink-0" placement="right" value="Toggle sidebar" inactive={layout.sidebar.opened()}>
               <Button
@@ -197,7 +301,7 @@ export default function Layout(props: ParentProps) {
             </Tooltip>
           </div>
         </div>
-        <main class="size-full overflow-x-hidden">{props.children}</main>
+        <main class="size-full overflow-x-hidden flex flex-col items-start">{props.children}</main>
       </div>
     </div>
   )

+ 376 - 268
packages/desktop/src/pages/session.tsx

@@ -1,4 +1,4 @@
-import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo } from "solid-js"
+import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect } from "solid-js"
 import { useLocal, type LocalFile } from "@/context/local"
 import { createStore } from "solid-js/store"
 import { PromptInput } from "@/components/prompt-input"
@@ -30,6 +30,8 @@ import { useSync } from "@/context/sync"
 import { useSession } from "@/context/session"
 import { useLayout } from "@/context/layout"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { Diff } from "@opencode-ai/ui/diff"
+import { Terminal } from "@/components/terminal"
 
 export default function Page() {
   const layout = useLayout()
@@ -53,6 +55,14 @@ export default function Page() {
     document.removeEventListener("keydown", handleKeyDown)
   })
 
+  createEffect(() => {
+    if (layout.terminal.opened()) {
+      if (session.terminal.all().length === 0) {
+        session.terminal.new()
+      }
+    }
+  })
+
   const handleKeyDown = (event: KeyboardEvent) => {
     if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") {
       event.preventDefault()
@@ -72,6 +82,16 @@ export default function Page() {
       document.documentElement.setAttribute("data-theme", nextTheme)
       return
     }
+    if (event.ctrlKey && event.key.toLowerCase() === "`") {
+      event.preventDefault()
+      layout.terminal.toggle()
+      return
+    }
+
+    // @ts-expect-error
+    if (document.activeElement?.dataset?.component === "terminal") {
+      return
+    }
 
     const focused = document.activeElement === inputRef
     if (focused) {
@@ -123,7 +143,6 @@ export default function Page() {
   const handleTabClick = async (tab: string) => {
     if (store.clickTimer) {
       resetClickTimer()
-      // local.file.update(file.path, { ...file, pinned: true })
     } else {
       if (tab.startsWith("file://")) {
         local.file.open(tab.replace("file://", ""))
@@ -141,7 +160,7 @@ export default function Page() {
   const handleDragOver = (event: DragEvent) => {
     const { draggable, droppable } = event
     if (draggable && droppable) {
-      const currentTabs = session.layout.tabs.opened
+      const currentTabs = session.layout.tabs.all
       const fromIndex = currentTabs?.indexOf(draggable.id.toString())
       const toIndex = currentTabs?.indexOf(droppable.id.toString())
       if (fromIndex !== toIndex && toIndex !== undefined) {
@@ -259,308 +278,397 @@ export default function Page() {
   const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length)
 
   return (
-    <div class="relative bg-background-base size-full overflow-x-hidden">
-      <DragDropProvider
-        onDragStart={handleDragStart}
-        onDragEnd={handleDragEnd}
-        onDragOver={handleDragOver}
-        collisionDetector={closestCenter}
-      >
-        <DragDropSensors />
-        <ConstrainDragYAxis />
-        <Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
-          <div class="sticky top-0 shrink-0 flex">
-            <Tabs.List>
-              <Tabs.Trigger value="chat">
-                <div class="flex gap-x-[17px] items-center">
-                  <div>Session</div>
-                  <Tooltip
-                    value={`${new Intl.NumberFormat("en-US", {
-                      notation: "compact",
-                      compactDisplay: "short",
-                    }).format(session.usage.tokens() ?? 0)} Tokens`}
-                    class="flex items-center gap-1.5"
+    <div class="relative bg-background-base size-full overflow-x-hidden flex flex-col items-start">
+      <div class="min-h-0 grow w-full">
+        <DragDropProvider
+          onDragStart={handleDragStart}
+          onDragEnd={handleDragEnd}
+          onDragOver={handleDragOver}
+          collisionDetector={closestCenter}
+        >
+          <DragDropSensors />
+          <ConstrainDragYAxis />
+          <Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
+            <div class="sticky top-0 shrink-0 flex">
+              <Tabs.List>
+                <Tabs.Trigger value="chat">
+                  <div class="flex gap-x-[17px] items-center">
+                    <div>Session</div>
+                    <Tooltip
+                      value={`${new Intl.NumberFormat("en-US", {
+                        notation: "compact",
+                        compactDisplay: "short",
+                      }).format(session.usage.tokens() ?? 0)} Tokens`}
+                      class="flex items-center gap-1.5"
+                    >
+                      <ProgressCircle percentage={session.usage.context() ?? 0} />
+                      <div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
+                    </Tooltip>
+                  </div>
+                </Tabs.Trigger>
+                <Show when={layout.review.state() === "tab" && session.diffs().length}>
+                  <Tabs.Trigger
+                    value="review"
+                    closeButton={
+                      <IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
+                    }
                   >
-                    <ProgressCircle percentage={session.usage.context() ?? 0} />
-                    <div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
-                  </Tooltip>
-                </div>
-              </Tabs.Trigger>
-              <Show when={layout.review.state() === "tab" && session.diffs().length}>
-                <Tabs.Trigger
-                  value="review"
-                  closeButton={
-                    <IconButton icon="collapse" size="normal" variant="ghost" onClick={layout.review.pane} />
-                  }
-                >
-                  <div class="flex items-center gap-3">
-                    <Show when={session.diffs()}>
-                      <DiffChanges changes={session.diffs()} variant="bars" />
-                    </Show>
-                    <div class="flex items-center gap-1.5">
-                      <div>Review</div>
-                      <Show when={session.info()?.summary?.files}>
-                        <div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
-                          {session.info()?.summary?.files ?? 0}
-                        </div>
+                    <div class="flex items-center gap-3">
+                      <Show when={session.diffs()}>
+                        <DiffChanges changes={session.diffs()} variant="bars" />
                       </Show>
+                      <div class="flex items-center gap-1.5">
+                        <div>Review</div>
+                        <Show when={session.info()?.summary?.files}>
+                          <div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
+                            {session.info()?.summary?.files ?? 0}
+                          </div>
+                        </Show>
+                      </div>
                     </div>
-                  </div>
-                </Tabs.Trigger>
-              </Show>
-              <SortableProvider ids={session.layout.tabs.opened ?? []}>
-                <For each={session.layout.tabs.opened ?? []}>
-                  {(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={session.layout.closeTab} />}
-                </For>
-              </SortableProvider>
-              <div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
-                <Tooltip value="Open file" class="flex items-center">
-                  <IconButton
-                    icon="plus-small"
-                    variant="ghost"
-                    iconSize="large"
-                    onClick={() => setStore("fileSelectOpen", true)}
-                  />
-                </Tooltip>
-              </div>
-            </Tabs.List>
-          </div>
-          <Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
-            <div
-              classList={{
-                "w-full flex-1 min-h-0": true,
-                grid: layout.review.state() === "tab",
-                flex: layout.review.state() === "pane",
-              }}
-            >
+                  </Tabs.Trigger>
+                </Show>
+                <SortableProvider ids={session.layout.tabs.all ?? []}>
+                  <For each={session.layout.tabs.all ?? []}>
+                    {(tab) => (
+                      <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={session.layout.closeTab} />
+                    )}
+                  </For>
+                </SortableProvider>
+                <div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
+                  <Tooltip value="Open file" class="flex items-center">
+                    <IconButton
+                      icon="plus-small"
+                      variant="ghost"
+                      iconSize="large"
+                      onClick={() => setStore("fileSelectOpen", true)}
+                    />
+                  </Tooltip>
+                </div>
+              </Tabs.List>
+            </div>
+            <Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
               <div
                 classList={{
-                  "relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
-                  "max-w-146 mx-auto": !wide(),
+                  "w-full flex-1 min-h-0": true,
+                  grid: layout.review.state() === "tab",
+                  flex: layout.review.state() === "pane",
                 }}
               >
-                <Switch>
-                  <Match when={session.id}>
-                    <div class="flex items-start justify-start h-full min-h-0">
-                      <SessionMessageRail
-                        messages={session.messages.user()}
-                        current={session.messages.active()}
-                        onMessageSelect={session.messages.setActive}
-                        working={session.working()}
-                        wide={wide()}
-                      />
-                      <SessionTurn
-                        sessionID={session.id!}
-                        messageID={session.messages.active()?.id!}
-                        classes={{
-                          root: "pb-20 flex-1 min-w-0",
-                          content: "pb-20",
-                          container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
-                        }}
-                      />
-                    </div>
-                  </Match>
-                  <Match when={true}>
-                    <div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-146 mx-auto px-6">
-                      <div class="text-20-medium text-text-weaker">New session</div>
-                      <div class="flex justify-center items-center gap-3">
-                        <Icon name="folder" size="small" />
-                        <div class="text-12-medium text-text-weak">
-                          {getDirectory(sync.data.path.directory)}
-                          <span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
-                        </div>
+                <div
+                  classList={{
+                    "relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
+                    "max-w-146 mx-auto": !wide(),
+                  }}
+                >
+                  <Switch>
+                    <Match when={session.id}>
+                      <div class="flex items-start justify-start h-full min-h-0">
+                        <SessionMessageRail
+                          messages={session.messages.user()}
+                          current={session.messages.active()}
+                          onMessageSelect={session.messages.setActive}
+                          working={session.working()}
+                          wide={wide()}
+                        />
+                        <SessionTurn
+                          sessionID={session.id!}
+                          messageID={session.messages.active()?.id!}
+                          classes={{
+                            root: "pb-20 flex-1 min-w-0",
+                            content: "pb-20",
+                            container:
+                              "w-full " +
+                              (wide()
+                                ? "max-w-146 mx-auto px-6"
+                                : session.messages.user().length > 1
+                                  ? "pr-6 pl-18"
+                                  : "px-6"),
+                          }}
+                          diffComponent={Diff}
+                        />
                       </div>
-                      <div class="flex justify-center items-center gap-3">
-                        <Icon name="pencil-line" size="small" />
-                        <div class="text-12-medium text-text-weak">
-                          Last modified&nbsp;
-                          <span class="text-text-strong">
-                            {DateTime.fromMillis(sync.data.project.time.created).toRelative()}
-                          </span>
+                    </Match>
+                    <Match when={true}>
+                      <div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-146 mx-auto px-6">
+                        <div class="text-20-medium text-text-weaker">New session</div>
+                        <div class="flex justify-center items-center gap-3">
+                          <Icon name="folder" size="small" />
+                          <div class="text-12-medium text-text-weak">
+                            {getDirectory(sync.data.path.directory)}
+                            <span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
+                          </div>
+                        </div>
+                        <div class="flex justify-center items-center gap-3">
+                          <Icon name="pencil-line" size="small" />
+                          <div class="text-12-medium text-text-weak">
+                            Last modified&nbsp;
+                            <span class="text-text-strong">
+                              {DateTime.fromMillis(sync.data.project.time.created).toRelative()}
+                            </span>
+                          </div>
                         </div>
                       </div>
+                    </Match>
+                  </Switch>
+                  <div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
+                    <div class="w-full max-w-146 px-6">
+                      <PromptInput
+                        ref={(el) => {
+                          inputRef = el
+                        }}
+                      />
                     </div>
-                  </Match>
-                </Switch>
-                <div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
-                  <div class="w-full max-w-146 px-6">
-                    <PromptInput
-                      ref={(el) => {
-                        inputRef = el
+                  </div>
+                </div>
+                <Show when={layout.review.state() === "pane" && session.diffs().length}>
+                  <div
+                    classList={{
+                      "relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base": true,
+                    }}
+                  >
+                    <SessionReview
+                      classes={{
+                        root: "pb-20",
+                        header: "px-6",
+                        container: "px-6",
                       }}
+                      diffs={session.diffs()}
+                      diffComponent={Diff}
+                      actions={
+                        <Tooltip value="Open in tab">
+                          <IconButton
+                            icon="expand"
+                            variant="ghost"
+                            onClick={() => {
+                              layout.review.tab()
+                              session.layout.setActiveTab("review")
+                            }}
+                          />
+                        </Tooltip>
+                      }
                     />
                   </div>
-                </div>
+                </Show>
               </div>
-              <Show when={layout.review.state() === "pane" && session.diffs().length}>
+            </Tabs.Content>
+            <Show when={layout.review.state() === "tab" && session.diffs().length}>
+              <Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
                 <div
                   classList={{
-                    "relative grow pt-3 flex-1 min-h-0 border-l border-border-weak-base": true,
+                    "relative pt-3 flex-1 min-h-0 overflow-hidden": true,
                   }}
                 >
                   <SessionReview
                     classes={{
-                      root: "pb-20",
+                      root: "pb-40",
                       header: "px-6",
                       container: "px-6",
                     }}
                     diffs={session.diffs()}
-                    actions={
-                      <Tooltip value="Open in tab">
-                        <IconButton
-                          icon="expand"
-                          variant="ghost"
-                          onClick={() => {
-                            layout.review.tab()
-                            session.layout.setActiveTab("review")
-                          }}
-                        />
-                      </Tooltip>
-                    }
+                    diffComponent={Diff}
+                    split
                   />
                 </div>
-              </Show>
-            </div>
-          </Tabs.Content>
-          <Show when={layout.review.state() === "tab" && session.diffs().length}>
-            <Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden">
+              </Tabs.Content>
+            </Show>
+            <For each={session.layout.tabs.all}>
+              {(tab) => {
+                const [file] = createResource(
+                  () => tab,
+                  async (tab) => {
+                    if (tab.startsWith("file://")) {
+                      return local.file.node(tab.replace("file://", ""))
+                    }
+                    return undefined
+                  },
+                )
+                return (
+                  <Tabs.Content value={tab} class="select-text mt-3">
+                    <Switch>
+                      <Match when={file()}>
+                        {(f) => (
+                          <Code
+                            file={{ name: f().path, contents: f().content?.content ?? "" }}
+                            overflow="scroll"
+                            class="pb-40"
+                          />
+                        )}
+                      </Match>
+                    </Switch>
+                  </Tabs.Content>
+                )
+              }}
+            </For>
+          </Tabs>
+          <DragOverlay>
+            <Show when={store.activeDraggable}>
+              {(draggedFile) => {
+                const [file] = createResource(
+                  () => draggedFile(),
+                  async (tab) => {
+                    if (tab.startsWith("file://")) {
+                      return local.file.node(tab.replace("file://", ""))
+                    }
+                    return undefined
+                  },
+                )
+                return (
+                  <div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
+                    <Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
+                  </div>
+                )
+              }}
+            </Show>
+          </DragOverlay>
+        </DragDropProvider>
+        <Show when={session.layout.tabs.active}>
+          <div class="absolute inset-x-0 px-6 max-w-146 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
+            <PromptInput
+              ref={(el) => {
+                inputRef = el
+              }}
+            />
+          </div>
+        </Show>
+        <div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
+          {/* <FileTree path="" onFileClick={ handleTabClick} /> */}
+        </div>
+        <div class="hidden shrink-0 w-56 p-2">
+          <Show
+            when={local.file.changes().length}
+            fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}
+          >
+            <ul class="">
+              <For each={local.file.changes()}>
+                {(path) => (
+                  <li>
+                    <button
+                      onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
+                      class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 hover:bg-background-element"
+                    >
+                      <FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
+                      <span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
+                      <span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
+                        {getDirectory(path)}
+                      </span>
+                    </button>
+                  </li>
+                )}
+              </For>
+            </ul>
+          </Show>
+        </div>
+        <Show when={store.fileSelectOpen}>
+          <SelectDialog
+            defaultOpen
+            title="Select file"
+            placeholder="Search files"
+            emptyMessage="No files found"
+            items={local.file.searchFiles}
+            key={(x) => x}
+            onOpenChange={(open) => setStore("fileSelectOpen", open)}
+            onSelect={(x) => {
+              if (x) {
+                local.file.open(x)
+                return session.layout.openTab("file://" + x)
+              }
+              return undefined
+            }}
+          >
+            {(i) => (
               <div
                 classList={{
-                  "relative pt-3 flex-1 min-h-0 overflow-hidden": true,
+                  "w-full flex items-center justify-between rounded-md": true,
                 }}
               >
-                <SessionReview
-                  classes={{
-                    root: "pb-40",
-                    header: "px-6",
-                    container: "px-6",
-                  }}
-                  diffs={session.diffs()}
-                  split
-                />
-              </div>
-            </Tabs.Content>
-          </Show>
-          <For each={session.layout.tabs.opened}>
-            {(tab) => {
-              const [file] = createResource(
-                () => tab,
-                async (tab) => {
-                  if (tab.startsWith("file://")) {
-                    return local.file.node(tab.replace("file://", ""))
-                  }
-                  return undefined
-                },
-              )
-              return (
-                <Tabs.Content value={tab} class="select-text mt-3">
-                  <Switch>
-                    <Match when={file()}>
-                      {(f) => (
-                        <Code
-                          file={{ name: f().path, contents: f().content?.content ?? "" }}
-                          overflow="scroll"
-                          class="pb-40"
-                        />
-                      )}
-                    </Match>
-                  </Switch>
-                </Tabs.Content>
-              )
-            }}
-          </For>
-        </Tabs>
-        <DragOverlay>
-          <Show when={store.activeDraggable}>
-            {(draggedFile) => {
-              const [file] = createResource(
-                () => draggedFile(),
-                async (tab) => {
-                  if (tab.startsWith("file://")) {
-                    return local.file.node(tab.replace("file://", ""))
-                  }
-                  return undefined
-                },
-              )
-              return (
-                <div class="relative px-6 h-12 flex items-center bg-background-stronger border-x border-border-weak-base border-b border-b-transparent">
-                  <Show when={file()}>{(f) => <FileVisual active file={f()} />}</Show>
+                <div class="flex items-center gap-x-2 grow min-w-0">
+                  <FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
+                  <div class="flex items-center text-14-regular">
+                    <span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
+                      {getDirectory(i)}
+                    </span>
+                    <span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
+                  </div>
                 </div>
-              )
-            }}
-          </Show>
-        </DragOverlay>
-      </DragDropProvider>
-      <Show when={session.layout.tabs.active}>
-        <div class="absolute inset-x-0 px-6 max-w-146 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
-          <PromptInput
-            ref={(el) => {
-              inputRef = el
+                <div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
+              </div>
+            )}
+          </SelectDialog>
+        </Show>
+      </div>
+      <Show when={layout.terminal.opened()}>
+        <div
+          class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base"
+          style={{ height: `${layout.terminal.height()}px` }}
+        >
+          <div
+            class="absolute inset-x-0 top-0 z-10 h-2 -translate-y-1/2 cursor-ns-resize"
+            onMouseDown={(e) => {
+              e.preventDefault()
+              const startY = e.clientY
+              const startHeight = layout.terminal.height()
+              const maxHeight = window.innerHeight * 0.6
+              const minHeight = 100
+              const collapseThreshold = 50
+              let currentHeight = startHeight
+
+              document.body.style.userSelect = "none"
+              document.body.style.overflow = "hidden"
+
+              const onMouseMove = (moveEvent: MouseEvent) => {
+                const deltaY = startY - moveEvent.clientY
+                currentHeight = startHeight + deltaY
+                const clampedHeight = Math.min(maxHeight, Math.max(minHeight, currentHeight))
+                layout.terminal.resize(clampedHeight)
+              }
+
+              const onMouseUp = () => {
+                document.body.style.userSelect = ""
+                document.body.style.overflow = ""
+                document.removeEventListener("mousemove", onMouseMove)
+                document.removeEventListener("mouseup", onMouseUp)
+
+                if (currentHeight < collapseThreshold) {
+                  layout.terminal.close()
+                }
+              }
+
+              document.addEventListener("mousemove", onMouseMove)
+              document.addEventListener("mouseup", onMouseUp)
             }}
           />
-        </div>
-      </Show>
-      <div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
-        {/* <FileTree path="" onFileClick={ handleTabClick} /> */}
-      </div>
-      <div class="hidden shrink-0 w-56 p-2">
-        <Show when={local.file.changes().length} fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}>
-          <ul class="">
-            <For each={local.file.changes()}>
-              {(path) => (
-                <li>
-                  <button
-                    onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
-                    class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 hover:bg-background-element"
+          <Tabs variant="alt" value={session.terminal.active()} onChange={session.terminal.open}>
+            <Tabs.List class="h-10">
+              <For each={session.terminal.all()}>
+                {(terminal) => (
+                  <Tabs.Trigger
+                    value={terminal.id}
+                    closeButton={
+                      session.terminal.all().length > 1 && (
+                        <IconButton icon="close" variant="ghost" onClick={() => session.terminal.close(terminal.id)} />
+                      )
+                    }
                   >
-                    <FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
-                    <span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
-                    <span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
-                      {getDirectory(path)}
-                    </span>
-                  </button>
-                </li>
+                    {terminal.title}
+                  </Tabs.Trigger>
+                )}
+              </For>
+              <div class="h-full flex items-center justify-center">
+                <Tooltip value="Open file" class="flex items-center">
+                  <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={session.terminal.new} />
+                </Tooltip>
+              </div>
+            </Tabs.List>
+            <For each={session.terminal.all()}>
+              {(terminal) => (
+                <Tabs.Content value={terminal.id}>
+                  <Terminal pty={terminal} onCleanup={session.terminal.update} />
+                </Tabs.Content>
               )}
             </For>
-          </ul>
-        </Show>
-      </div>
-      <Show when={store.fileSelectOpen}>
-        <SelectDialog
-          defaultOpen
-          title="Select file"
-          placeholder="Search files"
-          emptyMessage="No files found"
-          items={local.file.searchFiles}
-          key={(x) => x}
-          onOpenChange={(open) => setStore("fileSelectOpen", open)}
-          onSelect={(x) => {
-            if (x) {
-              local.file.open(x)
-              return session.layout.openTab("file://" + x)
-            }
-            return undefined
-          }}
-        >
-          {(i) => (
-            <div
-              classList={{
-                "w-full flex items-center justify-between rounded-md": true,
-              }}
-            >
-              <div class="flex items-center gap-x-2 grow min-w-0">
-                <FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
-                <div class="flex items-center text-14-regular">
-                  <span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
-                    {getDirectory(i)}
-                  </span>
-                  <span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
-                </div>
-              </div>
-              <div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
-            </div>
-          )}
-        </SelectDialog>
+          </Tabs>
+        </div>
       </Show>
     </div>
   )

+ 3 - 1
packages/desktop/src/sst-env.d.ts

@@ -2,7 +2,9 @@
 /* tslint:disable */
 /* eslint-disable */
 /// <reference types="vite/client" />
-interface ImportMetaEnv {}
+interface ImportMetaEnv {
+
+}
 interface ImportMeta {
   readonly env: ImportMetaEnv
 }

+ 3 - 0
packages/desktop/vite.config.ts

@@ -18,4 +18,7 @@ export default defineConfig({
   build: {
     target: "esnext",
   },
+  worker: {
+    format: "es",
+  },
 })

+ 1 - 1
packages/enterprise/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "private": true,
   "type": "module",
   "scripts": {

+ 75 - 25
packages/enterprise/src/core/share.ts

@@ -1,8 +1,10 @@
 import { FileDiff, Message, Model, Part, Session, SessionStatus } from "@opencode-ai/sdk"
 import { fn } from "@opencode-ai/util/fn"
 import { iife } from "@opencode-ai/util/iife"
+import { Identifier } from "@opencode-ai/util/identifier"
 import z from "zod"
 import { Storage } from "./storage"
+import { Binary } from "@opencode-ai/util/binary"
 
 export namespace Share {
   export const Info = z.object({
@@ -37,15 +39,15 @@ export namespace Share {
   export type Data = z.infer<typeof Data>
 
   export const create = fn(z.object({ sessionID: z.string() }), async (body) => {
+    const isTest = process.env.NODE_ENV === "test" || body.sessionID.startsWith("test_")
     const info: Info = {
-      id: body.sessionID.slice(-8),
+      id: (isTest ? "test_" : "") + body.sessionID.slice(-8),
       sessionID: body.sessionID,
       secret: crypto.randomUUID(),
     }
     const exists = await get(info.id)
     if (exists) throw new Errors.AlreadyExists(info.id)
     await Storage.write(["share", info.id], info)
-    console.log("created share", info.id)
     return info
   })
 
@@ -58,30 +60,77 @@ export namespace Share {
     if (!share) throw new Errors.NotFound(body.id)
     if (share.secret !== body.secret) throw new Errors.InvalidSecret(body.id)
     await Storage.remove(["share", body.id])
-    const list = await Storage.list(["share_data", body.id])
+    const list = await Storage.list({ prefix: ["share_data", body.id] })
     for (const item of list) {
       await Storage.remove(item)
     }
   })
 
-  export async function data(id: string) {
-    const list = await Storage.list(["share_data", id])
-    const promises = []
-    for (const item of list) {
-      promises.push(
-        iife(async () => {
-          const [, , type] = item
-          return {
-            type: type as any,
-            data: await Storage.read<any>(item),
-          } as Data
-        }),
-      )
+  export const sync = fn(
+    z.object({
+      share: Info.pick({ id: true, secret: true }),
+      data: Data.array(),
+    }),
+    async (input) => {
+      const share = await get(input.share.id)
+      if (!share) throw new Errors.NotFound(input.share.id)
+      if (share.secret !== input.share.secret) throw new Errors.InvalidSecret(input.share.id)
+      await Storage.write(["share_event", input.share.id, Identifier.descending()], input.data)
+    },
+  )
+
+  type Compaction = {
+    event?: string
+    data: Data[]
+  }
+
+  export async function data(shareID: string) {
+    console.log("reading compaction")
+    const compaction: Compaction = (await Storage.read<Compaction>(["share_compaction", shareID])) ?? {
+      data: [],
+      event: undefined,
     }
-    return await Promise.all(promises)
+    console.log("reading pending events")
+    const list = await Storage.list({
+      prefix: ["share_event", shareID],
+      before: compaction.event,
+    }).then((x) => x.toReversed())
+
+    console.log("compacting", list.length)
+
+    if (list.length > 0) {
+      const data = await Promise.all(list.map(async (event) => await Storage.read<Data[]>(event))).then((x) => x.flat())
+      for (const item of data) {
+        if (!item) continue
+        const key = (item: Data) => {
+          switch (item.type) {
+            case "session":
+              return "session"
+            case "message":
+              return `message/${item.data.id}`
+            case "part":
+              return `${item.data.messageID}/${item.data.id}`
+            case "session_diff":
+              return "session_diff"
+            case "model":
+              return "model"
+          }
+        }
+        const id = key(item)
+        const result = Binary.search(compaction.data, id, key)
+        if (result.found) {
+          compaction.data[result.index] = item
+        } else {
+          compaction.data.splice(result.index, 0, item)
+        }
+      }
+      compaction.event = list.at(-1)?.at(-1)
+      await Storage.write(["share_compaction", shareID], compaction)
+    }
+    return compaction.data
   }
 
-  export const sync = fn(
+  export const syncOld = fn(
     z.object({
       share: Info.pick({ id: true, secret: true }),
       data: Data.array(),
@@ -98,15 +147,16 @@ export namespace Share {
               case "session":
                 await Storage.write(["share_data", input.share.id, "session"], item.data)
                 break
-              case "message":
-                await Storage.write(["share_data", input.share.id, "message", item.data.id], item.data)
+              case "message": {
+                const data = item.data as Message
+                await Storage.write(["share_data", input.share.id, "message", data.id], item.data)
                 break
-              case "part":
-                await Storage.write(
-                  ["share_data", input.share.id, "part", item.data.messageID, item.data.id],
-                  item.data,
-                )
+              }
+              case "part": {
+                const data = item.data as Part
+                await Storage.write(["share_data", input.share.id, "part", data.messageID, data.id], item.data)
                 break
+              }
               case "session_diff":
                 await Storage.write(["share_data", input.share.id, "session_diff"], item.data)
                 break

+ 20 - 5
packages/enterprise/src/core/storage.ts

@@ -6,7 +6,7 @@ export namespace Storage {
     read(path: string): Promise<string | undefined>
     write(path: string, value: string): Promise<void>
     remove(path: string): Promise<void>
-    list(prefix: string): Promise<string[]>
+    list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]>
   }
 
   function createAdapter(client: AwsClient, endpoint: string, bucket: string): Adapter {
@@ -37,8 +37,14 @@ export namespace Storage {
         if (!response.ok) throw new Error(`Failed to remove ${path}: ${response.status}`)
       },
 
-      async list(prefix: string): Promise<string[]> {
+      async list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]> {
+        const prefix = options?.prefix || ""
         const params = new URLSearchParams({ "list-type": "2", prefix })
+        if (options?.limit) params.set("max-keys", options.limit.toString())
+        if (options?.after) {
+          const afterPath = prefix + options.after + ".json"
+          params.set("start-after", afterPath)
+        }
         const response = await client.fetch(`${base}?${params}`)
         if (!response.ok) throw new Error(`Failed to list ${prefix}: ${response.status}`)
         const xml = await response.text()
@@ -48,6 +54,10 @@ export namespace Storage {
         while ((match = regex.exec(xml)) !== null) {
           keys.push(match[1])
         }
+        if (options?.before) {
+          const beforePath = prefix + options.before + ".json"
+          return keys.filter((key) => key < beforePath)
+        }
         return keys
       },
     }
@@ -98,9 +108,14 @@ export namespace Storage {
     return adapter().remove(resolve(key))
   }
 
-  export async function list(prefix: string[]) {
-    const p = prefix.join("/") + (prefix.length ? "/" : "")
-    const result = await adapter().list(p)
+  export async function list(options?: { prefix?: string[]; limit?: number; after?: string; before?: string }) {
+    const p = options?.prefix ? options.prefix.join("/") + (options.prefix.length ? "/" : "") : ""
+    const result = await adapter().list({
+      prefix: p,
+      limit: options?.limit,
+      after: options?.after,
+      before: options?.before,
+    })
     return result.map((x) => x.replace(/\.json$/, "").split("/"))
   }
 

+ 62 - 13
packages/enterprise/src/routes/share/[shareID].tsx

@@ -18,6 +18,10 @@ import z from "zod"
 import NotFound from "../[...404]"
 import { Tabs } from "@opencode-ai/ui/tabs"
 import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
+import { Diff } from "@opencode-ai/ui/diff-ssr"
+import { clientOnly } from "@solidjs/start"
+
+const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
 
 const SessionDataMissingError = NamedError.create(
   "SessionDataMissingError",
@@ -41,6 +45,9 @@ const getData = query(async (shareID) => {
     session_diff_preload: {
       [sessionID: string]: PreloadMultiFileDiffResult<any>[]
     }
+    session_diff_preload_split: {
+      [sessionID: string]: PreloadMultiFileDiffResult<any>[]
+    }
     session_status: {
       [sessionID: string]: SessionStatus
     }
@@ -62,6 +69,9 @@ const getData = query(async (shareID) => {
     session_diff_preload: {
       [share.sessionID]: [],
     },
+    session_diff_preload_split: {
+      [share.sessionID]: [],
+    },
     session_status: {
       [share.sessionID]: {
         type: "idle",
@@ -78,16 +88,28 @@ const getData = query(async (shareID) => {
         break
       case "session_diff":
         result.session_diff[share.sessionID] = item.data
-        result.session_diff_preload[share.sessionID] = await Promise.all(
-          item.data.map(async (diff) =>
-            preloadMultiFileDiff<any>({
-              oldFile: { name: diff.file, contents: diff.before },
-              newFile: { name: diff.file, contents: diff.after },
-              options: createDefaultOptions("unified"),
-              // annotations,
-            }),
-          ),
-        )
+        await Promise.all([
+          Promise.all(
+            item.data.map(async (diff) =>
+              preloadMultiFileDiff<any>({
+                oldFile: { name: diff.file, contents: diff.before },
+                newFile: { name: diff.file, contents: diff.after },
+                options: createDefaultOptions("unified"),
+                // annotations,
+              }),
+            ),
+          ).then((r) => (result.session_diff_preload[share.sessionID] = r)),
+          Promise.all(
+            item.data.map(async (diff) =>
+              preloadMultiFileDiff<any>({
+                oldFile: { name: diff.file, contents: diff.before },
+                newFile: { name: diff.file, contents: diff.after },
+                options: createDefaultOptions("split"),
+                // annotations,
+              }),
+            ),
+          ).then((r) => (result.session_diff_preload_split[share.sessionID] = r)),
+        ])
         break
       case "message":
         result.message[item.data.sessionID] = result.message[item.data.sessionID] ?? []
@@ -169,6 +191,14 @@ export default function () {
                     preloaded: preloaded.find((d) => d.newFile.name === diff.file),
                   }))
                 })
+                const splitDiffs = createMemo(() => {
+                  const diffs = data().session_diff[data().sessionID] ?? []
+                  const preloaded = data().session_diff_preload_split[data().sessionID] ?? []
+                  return diffs.map((diff) => ({
+                    ...diff,
+                    preloaded: preloaded.find((d) => d.newFile.name === diff.file),
+                  }))
+                })
 
                 const title = () => (
                   <div class="flex flex-col gap-4">
@@ -204,6 +234,7 @@ export default function () {
                                 "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
                               container: "px-4",
                             }}
+                            diffComponent={ClientOnlyDiff}
                           />
                         )}
                       </For>
@@ -253,7 +284,8 @@ export default function () {
                             classList={{
                               "w-full flex justify-start items-start min-w-0": true,
                               "max-w-146 mx-auto px-6": wide(),
-                              "pr-6 pl-18": !wide(),
+                              "pr-6 pl-18": !wide() && messages().length > 1,
+                              "px-6": !wide() && messages().length === 1,
                             }}
                           >
                             {title()}
@@ -271,8 +303,11 @@ export default function () {
                               classes={{
                                 root: "grow",
                                 content: "flex flex-col justify-between items-start",
-                                container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
+                                container:
+                                  "w-full pb-20 " +
+                                  (wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"),
                               }}
+                              diffComponent={ClientOnlyDiff}
                             >
                               <div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
                                 <Logo class="w-58.5 opacity-12" />
@@ -281,9 +316,22 @@ export default function () {
                           </div>
                         </div>
                         <Show when={diffs().length > 0}>
-                          <div class="relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
+                          <div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
                             <SessionReview
+                              class="@4xl:hidden"
                               diffs={diffs()}
+                              diffComponent={Diff}
+                              classes={{
+                                root: "pb-20",
+                                header: "px-6",
+                                container: "px-6",
+                              }}
+                            />
+                            <SessionReview
+                              split
+                              class="hidden @4xl:flex"
+                              diffs={splitDiffs()}
+                              diffComponent={Diff}
                               classes={{
                                 root: "pb-20",
                                 header: "px-6",
@@ -315,6 +363,7 @@ export default function () {
                               <div class="relative h-full pt-8 overflow-y-auto no-scrollbar">
                                 <SessionReview
                                   diffs={diffs()}
+                                  diffComponent={Diff}
                                   classes={{
                                     root: "pb-20",
                                     header: "px-4",

+ 117 - 113
packages/enterprise/sst-env.d.ts

@@ -6,124 +6,128 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    ADMIN_SECRET: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    AWS_SES_ACCESS_KEY_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AWS_SES_SECRET_ACCESS_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_API_TOKEN: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
-    }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
-    }
-    Desktop: {
-      type: "sst.cloudflare.StaticSite"
-      url: string
-    }
-    EMAILOCTOPUS_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2AccessKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2SecretKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
-    }
-    ZEN_MODELS1: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS2: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS3: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS4: {
-      type: "sst.sst.Secret"
-      value: string
+    "ADMIN_SECRET": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "AWS_SES_ACCESS_KEY_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AWS_SES_SECRET_ACCESS_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_API_TOKEN": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
+    }
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
+    }
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Enterprise": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2AccessKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2SecretKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS1": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS2": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS3": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS4": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    ConsoleData: cloudflare.R2Bucket
-    EnterpriseStorage: cloudflare.R2Bucket
-    GatewayKv: cloudflare.KVNamespace
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "EnterpriseStorage": cloudflare.R2Bucket
+    "GatewayKv": cloudflare.KVNamespace
+    "LogProcessor": cloudflare.Service
+    "ZenData": cloudflare.R2Bucket
   }
 }
 

+ 40 - 0
packages/enterprise/test-debug.ts

@@ -0,0 +1,40 @@
+import { Share } from "./src/core/share"
+import { Storage } from "./src/core/storage"
+
+async function test() {
+  const shareInfo = await Share.create({ sessionID: "test-debug-" + Date.now() })
+
+  const batch1: Share.Data[] = [
+    { type: "part", data: { id: "part1", sessionID: "session1", messageID: "msg1", type: "text", text: "Hello" } },
+  ]
+
+  const batch2: Share.Data[] = [
+    {
+      type: "part",
+      data: { id: "part1", sessionID: "session1", messageID: "msg1", type: "text", text: "Hello Updated" },
+    },
+  ]
+
+  await Share.sync({
+    share: { id: shareInfo.id, secret: shareInfo.secret },
+    data: batch1,
+  })
+
+  await Share.sync({
+    share: { id: shareInfo.id, secret: shareInfo.secret },
+    data: batch2,
+  })
+
+  const events = await Storage.list({ prefix: ["share_event", shareInfo.id] })
+  console.log("Events (raw):", events)
+  console.log("Events (reversed):", events.toReversed())
+
+  for (const event of events.toReversed()) {
+    const data = await Storage.read(event)
+    console.log("Event data (reversed order):", event, data)
+  }
+
+  await Share.remove({ id: shareInfo.id, secret: shareInfo.secret })
+}
+
+test()

+ 262 - 0
packages/enterprise/test/core/share.test.ts

@@ -0,0 +1,262 @@
+import { describe, expect, test, afterAll } from "bun:test"
+import { Share } from "../../src/core/share"
+import { Storage } from "../../src/core/storage"
+import { Identifier } from "@opencode-ai/util/identifier"
+
+describe.concurrent("core.share", () => {
+  test("should create a share", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    expect(share.sessionID).toBe(sessionID)
+    expect(share.secret).toBeDefined()
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should sync data to a share", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
+      },
+    ]
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data,
+    })
+
+    const events = await Storage.list({ prefix: ["share_event", share.id] })
+    expect(events.length).toBe(1)
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should sync multiple batches of data", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data1: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
+      },
+    ]
+
+    const data2: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
+      },
+    ]
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data1,
+    })
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data2,
+    })
+
+    const events = await Storage.list({ prefix: ["share_event", share.id] })
+    expect(events.length).toBe(2)
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should retrieve synced data", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
+      },
+      {
+        type: "part",
+        data: { id: "part2", sessionID, messageID: "msg1", type: "text", text: "World" },
+      },
+    ]
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data,
+    })
+
+    const result = await Share.data(share.id)
+
+    expect(result.length).toBe(2)
+    expect(result[0].type).toBe("part")
+    expect(result[1].type).toBe("part")
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should retrieve data from multiple syncs", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data1: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
+      },
+    ]
+
+    const data2: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part2", sessionID, messageID: "msg2", type: "text", text: "World" },
+      },
+    ]
+
+    const data3: Share.Data[] = [
+      { type: "part", data: { id: "part3", sessionID, messageID: "msg3", type: "text", text: "!" } },
+    ]
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data1,
+    })
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data2,
+    })
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data3,
+    })
+
+    const result = await Share.data(share.id)
+
+    expect(result.length).toBe(3)
+    const parts = result.filter((d) => d.type === "part")
+    expect(parts.length).toBe(3)
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should return latest data when syncing duplicate parts", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data1: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
+      },
+    ]
+
+    const data2: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello Updated" },
+      },
+    ]
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data1,
+    })
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data: data2,
+    })
+
+    const result = await Share.data(share.id)
+
+    expect(result.length).toBe(1)
+    const [first] = result
+    expect(first.type).toBe("part")
+    expect(first.type === "part" && first.data.type === "text" && first.data.text).toBe("Hello Updated")
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should return empty array for share with no data", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const result = await Share.data(share.id)
+
+    expect(result).toEqual([])
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should throw error for invalid secret", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
+      },
+    ]
+
+    expect(async () => {
+      await Share.sync({
+        share: { id: share.id, secret: "invalid-secret" },
+        data,
+      })
+    }).toThrow()
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+
+  test("should throw error for non-existent share", async () => {
+    const sessionID = Identifier.descending()
+    const data: Share.Data[] = [
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Test" },
+      },
+    ]
+
+    expect(async () => {
+      await Share.sync({
+        share: { id: "non-existent-id", secret: "some-secret" },
+        data,
+      })
+    }).toThrow()
+  })
+
+  test("should handle different data types", async () => {
+    const sessionID = Identifier.descending()
+    const share = await Share.create({ sessionID })
+
+    const data: Share.Data[] = [
+      { type: "session", data: { id: sessionID, status: "running" } as any },
+      { type: "message", data: { id: "msg1", sessionID } as any },
+      {
+        type: "part",
+        data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
+      },
+    ]
+
+    await Share.sync({
+      share: { id: share.id, secret: share.secret },
+      data,
+    })
+
+    const result = await Share.data(share.id)
+
+    expect(result.length).toBe(3)
+    expect(result.some((d) => d.type === "session")).toBe(true)
+    expect(result.some((d) => d.type === "message")).toBe(true)
+    expect(result.some((d) => d.type === "part")).toBe(true)
+
+    await Share.remove({ id: share.id, secret: share.secret })
+  })
+})

+ 64 - 0
packages/enterprise/test/core/storage.test.ts

@@ -0,0 +1,64 @@
+import { describe, expect, test, afterAll } from "bun:test"
+import { Storage } from "../../src/core/storage"
+
+describe("core.storage", () => {
+  test("should list files with after and before range", async () => {
+    await Storage.write(["test", "users", "user1"], { name: "user1" })
+    await Storage.write(["test", "users", "user2"], { name: "user2" })
+    await Storage.write(["test", "users", "user3"], { name: "user3" })
+    await Storage.write(["test", "users", "user4"], { name: "user4" })
+    await Storage.write(["test", "users", "user5"], { name: "user5" })
+
+    const result = await Storage.list({ prefix: ["test", "users"], after: "user2", before: "user4" })
+
+    expect(result).toEqual([["test", "users", "user3"]])
+  })
+
+  test("should list files with after only", async () => {
+    const result = await Storage.list({ prefix: ["test", "users"], after: "user3" })
+
+    expect(result).toEqual([
+      ["test", "users", "user4"],
+      ["test", "users", "user5"],
+    ])
+  })
+
+  test("should list files with limit", async () => {
+    const result = await Storage.list({ prefix: ["test", "users"], limit: 3 })
+
+    expect(result).toEqual([
+      ["test", "users", "user1"],
+      ["test", "users", "user2"],
+      ["test", "users", "user3"],
+    ])
+  })
+
+  test("should list all files without prefix", async () => {
+    const result = await Storage.list()
+
+    expect(result.length).toBeGreaterThan(0)
+  })
+
+  test("should list all files with prefix", async () => {
+    const result = await Storage.list({ prefix: ["test", "users"] })
+
+    expect(result).toEqual([
+      ["test", "users", "user1"],
+      ["test", "users", "user2"],
+      ["test", "users", "user3"],
+      ["test", "users", "user4"],
+      ["test", "users", "user5"],
+    ])
+  })
+
+  afterAll(async () => {
+    const testFiles = await Storage.list({ prefix: ["test"] })
+
+    for (const file of testFiles) {
+      await Storage.remove(file)
+    }
+
+    const remainingFiles = await Storage.list({ prefix: ["test"] })
+    expect(remainingFiles).toEqual([])
+  })
+})

+ 3 - 0
packages/enterprise/vite.config.ts

@@ -23,4 +23,7 @@ export default defineConfig({
     host: "0.0.0.0",
     allowedHosts: true,
   },
+  worker: {
+    format: "es",
+  },
 })

+ 6 - 6
packages/extensions/zed/extension.toml

@@ -1,7 +1,7 @@
 id = "opencode"
 name = "OpenCode"
 description = "The AI coding agent built for the terminal"
-version = "1.0.121"
+version = "1.0.133"
 schema_version = 1
 authors = ["Anomaly"]
 repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
 icon = "./icons/opencode.svg"
 
 [agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.121/opencode-darwin-arm64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.121/opencode-darwin-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-darwin-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.121/opencode-linux-arm64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-linux-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.121/opencode-linux-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-linux-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/sst/opencode/releases/download/v1.0.121/opencode-windows-x64.zip"
+archive = "https://github.com/sst/opencode/releases/download/v1.0.133/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 args = ["acp"]

+ 1 - 1
packages/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 117 - 113
packages/function/sst-env.d.ts

@@ -6,124 +6,128 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    ADMIN_SECRET: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    AWS_SES_ACCESS_KEY_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    AWS_SES_SECRET_ACCESS_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_API_TOKEN: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
-    }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
-    }
-    Desktop: {
-      type: "sst.cloudflare.StaticSite"
-      url: string
-    }
-    EMAILOCTOPUS_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2AccessKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    R2SecretKey: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
-    }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
-    }
-    ZEN_MODELS1: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS2: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS3: {
-      type: "sst.sst.Secret"
-      value: string
-    }
-    ZEN_MODELS4: {
-      type: "sst.sst.Secret"
-      value: string
+    "ADMIN_SECRET": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "AWS_SES_ACCESS_KEY_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "AWS_SES_SECRET_ACCESS_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_API_TOKEN": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
+    }
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
+    }
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "Enterprise": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
+    }
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2AccessKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "R2SecretKey": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS1": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS2": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS3": {
+      "type": "sst.sst.Secret"
+      "value": string
+    }
+    "ZEN_MODELS4": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    ConsoleData: cloudflare.R2Bucket
-    EnterpriseStorage: cloudflare.R2Bucket
-    GatewayKv: cloudflare.KVNamespace
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "EnterpriseStorage": cloudflare.R2Bucket
+    "GatewayKv": cloudflare.KVNamespace
+    "LogProcessor": cloudflare.Service
+    "ZenData": cloudflare.R2Bucket
   }
 }
 

+ 6 - 3
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.0.121",
+  "version": "1.0.133",
   "name": "opencode",
   "type": "module",
   "private": true,
@@ -53,6 +53,8 @@
     "@ai-sdk/mcp": "0.0.8",
     "@ai-sdk/openai": "2.0.71",
     "@ai-sdk/openai-compatible": "1.0.27",
+    "@ai-sdk/provider": "2.0.0",
+    "@ai-sdk/provider-utils": "3.0.18",
     "@clack/prompts": "1.0.0-alpha.1",
     "@hono/standard-validator": "0.1.5",
     "@hono/zod-validator": "catalog:",
@@ -66,14 +68,15 @@
     "@opencode-ai/sdk": "workspace:*",
     "@opencode-ai/util": "workspace:*",
     "@openrouter/ai-sdk-provider": "1.2.8",
-    "@opentui/core": "0.1.52",
-    "@opentui/solid": "0.1.52",
+    "@opentui/core": "0.1.56",
+    "@opentui/solid": "0.1.56",
     "@parcel/watcher": "2.5.1",
     "@pierre/precision-diffs": "catalog:",
     "@solid-primitives/event-bus": "1.1.2",
     "@standard-schema/spec": "1.0.0",
     "@zip.js/zip.js": "2.7.62",
     "ai": "catalog:",
+    "bun-pty": "0.4.2",
     "chokidar": "4.0.3",
     "clipboardy": "4.0.0",
     "decimal.js": "10.5.0",

+ 10 - 3
packages/opencode/script/build.ts

@@ -60,6 +60,7 @@ async function generateEmbeddedWebGui() {
 await generateEmbeddedWebGui()
 
 const singleFlag = process.argv.includes("--single")
+const skipInstall = process.argv.includes("--skip-install")
 
 const allTargets: {
   os: string
@@ -96,8 +97,10 @@ const targets = singleFlag
 await fs.rm("dist", { recursive: true, force: true })
 
 const binaries: Record<string, string> = {}
-await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
-await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
+if (!skipInstall) {
+  await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
+  await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
+}
 for (const item of targets) {
   const name = [
     pkg.name,
@@ -116,6 +119,10 @@ for (const item of targets) {
   const parserWorker = nodefs.realpathSync(path.join(path.dirname(opentuiCoreEntry), "parser.worker.js"))
   const workerPath = "./src/cli/cmd/tui/worker.ts"
 
+  // Use platform-specific bunfs root path based on target OS
+  const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/"
+  const workerRelativePath = path.relative(dir, parserWorker).replaceAll("\\", "/")
+
   await Bun.build({
     conditions: ["browser"],
     tsconfig: "./tsconfig.json",
@@ -132,7 +139,7 @@ for (const item of targets) {
     entrypoints: ["./src/index.ts", parserWorker, workerPath],
     define: {
       OPENCODE_VERSION: `'${Script.version}'`,
-      OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(dir, parserWorker).replaceAll("\\", "/"),
+      OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
       OPENCODE_WORKER_PATH: workerPath,
       OPENCODE_CHANNEL: `'${Script.channel}'`,
     },

+ 2 - 1
packages/opencode/src/agent/agent.ts

@@ -224,6 +224,7 @@ export namespace Agent {
   export async function generate(input: { description: string }) {
     const defaultModel = await Provider.defaultModel()
     const model = await Provider.getModel(defaultModel.providerID, defaultModel.modelID)
+    const language = await Provider.getLanguage(model)
     const system = SystemPrompt.header(defaultModel.providerID)
     system.push(PROMPT_GENERATE)
     const existing = await list()
@@ -241,7 +242,7 @@ export namespace Agent {
           content: `Create an agent configuration based on this request: \"${input.description}\".\n\nIMPORTANT: The following identifiers already exist and must NOT be used: ${existing.map((i) => i.name).join(", ")}\n  Return ONLY the JSON object, no other text, do not wrap in backticks`,
         },
       ],
-      model: model.language,
+      model: language,
       schema: z.object({
         identifier: z.string(),
         whenToUse: z.string(),

+ 31 - 8
packages/opencode/src/bus/index.ts

@@ -7,19 +7,35 @@ import { GlobalBus } from "./global"
 export namespace Bus {
   const log = Log.create({ service: "bus" })
   type Subscription = (event: any) => void
-
-  const state = Instance.state(() => {
-    const subscriptions = new Map<any, Subscription[]>()
-
-    return {
-      subscriptions,
-    }
-  })
+  const disposedEventType = "server.instance.disposed"
 
   export type EventDefinition = ReturnType<typeof event>
 
   const registry = new Map<string, EventDefinition>()
 
+  const state = Instance.state(
+    () => {
+      const subscriptions = new Map<any, Subscription[]>()
+
+      return {
+        subscriptions,
+      }
+    },
+    async (entry) => {
+      const wildcard = entry.subscriptions.get("*")
+      if (!wildcard) return
+      const event = {
+        type: disposedEventType,
+        properties: {
+          directory: Instance.directory,
+        },
+      }
+      for (const sub of [...wildcard]) {
+        sub(event)
+      }
+    },
+  )
+
   export function event<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
     const result = {
       type,
@@ -29,6 +45,13 @@ export namespace Bus {
     return result
   }
 
+  export const InstanceDisposed = event(
+    disposedEventType,
+    z.object({
+      directory: z.string(),
+    }),
+  )
+
   export function payloads() {
     return z
       .discriminatedUnion(

+ 17 - 1
packages/opencode/src/cli/cmd/auth.ts

@@ -6,6 +6,7 @@ import { ModelsDev } from "../../provider/models"
 import { map, pipe, sortBy, values } from "remeda"
 import path from "path"
 import os from "os"
+import { Config } from "../../config/config"
 import { Global } from "../../global"
 import { Plugin } from "../../plugin"
 import { Instance } from "../../project/instance"
@@ -103,7 +104,22 @@ export const AuthLoginCommand = cmd({
           return
         }
         await ModelsDev.refresh().catch(() => {})
-        const providers = await ModelsDev.get()
+
+        const config = await Config.get()
+
+        const disabled = new Set(config.disabled_providers ?? [])
+        const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
+
+        const providers = await ModelsDev.get().then((x) => {
+          const filtered: Record<string, (typeof x)[string]> = {}
+          for (const [key, value] of Object.entries(x)) {
+            if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
+              filtered[key] = value
+            }
+          }
+          return filtered
+        })
+
         const priority: Record<string, number> = {
           opencode: 0,
           anthropic: 1,

+ 3 - 1
packages/opencode/src/cli/cmd/cmd.ts

@@ -1,5 +1,7 @@
 import type { CommandModule } from "yargs"
 
-export function cmd<T, U>(input: CommandModule<T, U>) {
+type WithDoubleDash<T> = T & { "--"?: string[] }
+
+export function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>) {
   return input
 }

+ 5 - 0
packages/opencode/src/cli/cmd/github.ts

@@ -562,6 +562,11 @@ export const GithubRunCommand = cmd({
       }
 
       async function getUserPrompt() {
+        const customPrompt = process.env["PROMPT"]
+        if (customPrompt) {
+          return { userPrompt: customPrompt, promptFiles: [] }
+        }
+
         const reviewContext = getReviewCommentContext()
         let prompt = (() => {
           const body = payload.comment.body.trim()

+ 1 - 1
packages/opencode/src/cli/cmd/models.ts

@@ -38,7 +38,7 @@ export const ModelsCommand = cmd({
 
         function printModels(providerID: string, verbose?: boolean) {
           const provider = providers[providerID]
-          const sortedModels = Object.entries(provider.info.models).sort(([a], [b]) => a.localeCompare(b))
+          const sortedModels = Object.entries(provider.models).sort(([a], [b]) => a.localeCompare(b))
           for (const [modelID, model] of sortedModels) {
             process.stdout.write(`${providerID}/${modelID}`)
             process.stdout.write(EOL)

+ 1 - 1
packages/opencode/src/cli/cmd/run.ts

@@ -88,7 +88,7 @@ export const RunCommand = cmd({
       })
   },
   handler: async (args) => {
-    let message = args.message.join(" ")
+    let message = [...args.message, ...(args["--"] || [])].join(" ")
 
     const fileParts: any[] = []
     if (args.file) {

+ 106 - 0
packages/opencode/src/cli/cmd/session.ts

@@ -0,0 +1,106 @@
+import type { Argv } from "yargs"
+import { cmd } from "./cmd"
+import { Session } from "../../session"
+import { bootstrap } from "../bootstrap"
+import { UI } from "../ui"
+import { Locale } from "../../util/locale"
+import { EOL } from "os"
+
+export const SessionCommand = cmd({
+  command: "session",
+  describe: "manage sessions",
+  builder: (yargs: Argv) => yargs.command(SessionListCommand).demandCommand(),
+  async handler() {},
+})
+
+export const SessionListCommand = cmd({
+  command: "list",
+  describe: "list sessions",
+  builder: (yargs: Argv) => {
+    return yargs
+      .option("max-count", {
+        alias: "n",
+        describe: "limit to N most recent sessions",
+        type: "number",
+      })
+      .option("format", {
+        describe: "output format",
+        type: "string",
+        choices: ["table", "json"],
+        default: "table",
+      })
+  },
+  handler: async (args) => {
+    await bootstrap(process.cwd(), async () => {
+      const sessions = []
+      for await (const session of Session.list()) {
+        if (!session.parentID) {
+          sessions.push(session)
+        }
+      }
+
+      sessions.sort((a, b) => b.time.updated - a.time.updated)
+
+      const limitedSessions = args.maxCount ? sessions.slice(0, args.maxCount) : sessions
+
+      if (limitedSessions.length === 0) {
+        return
+      }
+
+      let output: string
+      if (args.format === "json") {
+        output = formatSessionJSON(limitedSessions)
+      } else {
+        output = formatSessionTable(limitedSessions)
+      }
+
+      const shouldPaginate = process.stdout.isTTY && !args.maxCount && args.format === "table"
+
+      if (shouldPaginate) {
+        const proc = Bun.spawn({
+          cmd: ["less", "-R", "-S"],
+          stdin: "pipe",
+          stdout: "inherit",
+          stderr: "inherit",
+        })
+
+        proc.stdin.write(output)
+        proc.stdin.end()
+        await proc.exited
+      } else {
+        console.log(output)
+      }
+    })
+  },
+})
+
+function formatSessionTable(sessions: Session.Info[]): string {
+  const lines: string[] = []
+
+  const maxIdWidth = Math.max(20, ...sessions.map((s) => s.id.length))
+  const maxTitleWidth = Math.max(25, ...sessions.map((s) => s.title.length))
+
+  const header = `Session ID${" ".repeat(maxIdWidth - 10)}  Title${" ".repeat(maxTitleWidth - 5)}  Updated`
+  lines.push(header)
+  lines.push("─".repeat(header.length))
+  for (const session of sessions) {
+    const truncatedTitle = Locale.truncate(session.title, maxTitleWidth)
+    const timeStr = Locale.todayTimeOrDateTime(session.time.updated)
+    const line = `${session.id.padEnd(maxIdWidth)}  ${truncatedTitle.padEnd(maxTitleWidth)}  ${timeStr}`
+    lines.push(line)
+  }
+
+  return lines.join(EOL)
+}
+
+function formatSessionJSON(sessions: Session.Info[]): string {
+  const jsonData = sessions.map((session) => ({
+    id: session.id,
+    title: session.title,
+    updated: session.time.updated,
+    created: session.time.created,
+    projectId: session.projectID,
+    directory: session.directory,
+  }))
+  return JSON.stringify(jsonData, null, 2)
+}

+ 42 - 6
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -2,9 +2,10 @@ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentu
 import { Clipboard } from "@tui/util/clipboard"
 import { TextAttributes } from "@opentui/core"
 import { RouteProvider, useRoute } from "@tui/context/route"
-import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show } from "solid-js"
+import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
 import { Installation } from "@/installation"
 import { Global } from "@/global"
+import { Flag } from "@/flag/flag"
 import { DialogProvider, useDialog } from "@tui/ui/dialog"
 import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
 import { SDKProvider, useSDK } from "@tui/context/sdk"
@@ -30,6 +31,8 @@ import { TuiEvent } from "./event"
 import { KVProvider, useKV } from "./context/kv"
 import { Provider } from "@/provider/provider"
 import { ArgsProvider, useArgs, type Args } from "./context/args"
+import open from "open"
+import { PromptRefProvider, usePromptRef } from "./context/prompt"
 
 async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
   // can't set raw mode if not a TTY
@@ -117,7 +120,9 @@ export function tui(input: { url: string; args: Args; onExit?: () => Promise<voi
                                 <DialogProvider>
                                   <CommandProvider>
                                     <PromptHistoryProvider>
-                                      <App />
+                                      <PromptRefProvider>
+                                        <App />
+                                      </PromptRefProvider>
                                     </PromptHistoryProvider>
                                   </CommandProvider>
                                 </DialogProvider>
@@ -158,6 +163,7 @@ function App() {
   const { theme, mode, setMode } = useTheme()
   const sync = useSync()
   const exit = useExit()
+  const promptRef = usePromptRef()
 
   createEffect(() => {
     console.log(JSON.stringify(route.data))
@@ -196,6 +202,17 @@ function App() {
     }
   })
 
+  createEffect(
+    on(
+      () => sync.status === "complete" && sync.data.provider.length === 0,
+      (isEmpty, wasEmpty) => {
+        // only trigger when we transition into an empty-provider state
+        if (!isEmpty || wasEmpty) return
+        dialog.replace(() => <DialogProviderList />)
+      },
+    ),
+  )
+
   command.register(() => [
     {
       title: "Switch session",
@@ -212,8 +229,12 @@ function App() {
       keybind: "session_new",
       category: "Session",
       onSelect: () => {
+        const current = promptRef.current
+        // Don't require focus - if there's any text, preserve it
+        const currentPrompt = current?.current?.input ? current.current : undefined
         route.navigate({
           type: "home",
+          initialPrompt: currentPrompt,
         })
         dialog.clear()
       },
@@ -300,10 +321,11 @@ function App() {
       category: "System",
     },
     {
-      title: `Switch to ${mode() === "dark" ? "light" : "dark"} mode`,
+      title: "Toggle appearance",
       value: "theme.switch_mode",
-      onSelect: () => {
+      onSelect: (dialog) => {
         setMode(mode() === "dark" ? "light" : "dark")
+        dialog.clear()
       },
       category: "System",
     },
@@ -315,6 +337,15 @@ function App() {
       },
       category: "System",
     },
+    {
+      title: "Open docs",
+      value: "docs.open",
+      onSelect: () => {
+        open("https://opencode.ai/docs").catch(() => {})
+        dialog.clear()
+      },
+      category: "System",
+    },
     {
       title: "Exit the app",
       value: "app.exit",
@@ -357,8 +388,9 @@ function App() {
   ])
 
   createEffect(() => {
-    const providerID = local.model.current().providerID
-    if (providerID === "openrouter" && !kv.get("openrouter_warning", false)) {
+    const currentModel = local.model.current()
+    if (!currentModel) return
+    if (currentModel.providerID === "openrouter" && !kv.get("openrouter_warning", false)) {
       untrack(() => {
         DialogAlert.show(
           dialog,
@@ -438,6 +470,10 @@ function App() {
       height={dimensions().height}
       backgroundColor={theme.background}
       onMouseUp={async () => {
+        if (Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) {
+          renderer.clearSelection()
+          return
+        }
         const text = renderer.getSelection()?.getSelectedText()
         if (text && text.length > 0) {
           const base64 = Buffer.from(text).toString("base64")

+ 5 - 1
packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

@@ -352,8 +352,8 @@ export function Autocomplete(props: {
   function select() {
     const selected = options()[store.selected]
     if (!selected) return
-    selected.onSelect?.()
     hide()
+    selected.onSelect?.()
   }
 
   function show(mode: "@" | "/") {
@@ -374,6 +374,10 @@ export function Autocomplete(props: {
     if (store.visible === "/" && !text.endsWith(" ") && text.startsWith("/")) {
       const cursor = props.input().logicalCursor
       props.input().deleteRange(0, 0, cursor.row, cursor.col)
+      // Sync the prompt store immediately since onContentChange is async
+      props.setPrompt((draft) => {
+        draft.input = props.input().plainText
+      })
     }
     command.keybinds(true)
     setStore("visible", false)

+ 41 - 11
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -22,6 +22,9 @@ import { TuiEvent } from "../../event"
 import { iife } from "@/util/iife"
 import { Locale } from "@/util/locale"
 import { createColors, createFrames } from "../../ui/spinner.ts"
+import { useDialog } from "@tui/ui/dialog"
+import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
+import { useToast } from "../../ui/toast"
 
 export type PromptProps = {
   sessionID?: string
@@ -34,6 +37,7 @@ export type PromptProps = {
 
 export type PromptRef = {
   focused: boolean
+  current: PromptInfo
   set(prompt: PromptInfo): void
   reset(): void
   blur(): void
@@ -50,12 +54,25 @@ export function Prompt(props: PromptProps) {
   const sdk = useSDK()
   const route = useRoute()
   const sync = useSync()
+  const dialog = useDialog()
+  const toast = useToast()
   const status = createMemo(() => sync.data.session_status[props.sessionID ?? ""] ?? { type: "idle" })
   const history = usePromptHistory()
   const command = useCommandDialog()
   const renderer = useRenderer()
   const { theme, syntax } = useTheme()
 
+  function promptModelWarning() {
+    toast.show({
+      variant: "warning",
+      message: "Connect a provider to send prompts",
+      duration: 3000,
+    })
+    if (sync.data.provider.length === 0) {
+      dialog.replace(() => <DialogProviderConnect />)
+    }
+  }
+
   const textareaKeybindings = createMemo(() => {
     const newlineBindings = keybind.all.input_newline || []
     const submitBindings = keybind.all.input_submit || []
@@ -253,7 +270,7 @@ export function Prompt(props: PromptProps) {
 
   createEffect(() => {
     if (props.disabled) input.cursorColor = theme.backgroundElement
-    if (!props.disabled) input.cursorColor = theme.primary
+    if (!props.disabled) input.cursorColor = theme.text
   })
 
   const [store, setStore] = createStore<{
@@ -361,6 +378,9 @@ export function Prompt(props: PromptProps) {
     get focused() {
       return input.focused
     },
+    get current() {
+      return store.prompt
+    },
     focus() {
       input.focus()
     },
@@ -388,6 +408,11 @@ export function Prompt(props: PromptProps) {
     if (props.disabled) return
     if (autocomplete.visible) return
     if (!store.prompt.input) return
+    const selectedModel = local.model.current()
+    if (!selectedModel) {
+      promptModelWarning()
+      return
+    }
     const sessionID = props.sessionID
       ? props.sessionID
       : await (async () => {
@@ -424,8 +449,8 @@ export function Prompt(props: PromptProps) {
         body: {
           agent: local.agent.current().name,
           model: {
-            providerID: local.model.current().providerID,
-            modelID: local.model.current().modelID,
+            providerID: selectedModel.providerID,
+            modelID: selectedModel.modelID,
           },
           command: inputText,
         },
@@ -448,7 +473,7 @@ export function Prompt(props: PromptProps) {
           command: command.slice(1),
           arguments: args.join(" "),
           agent: local.agent.current().name,
-          model: `${local.model.current().providerID}/${local.model.current().modelID}`,
+          model: `${selectedModel.providerID}/${selectedModel.modelID}`,
           messageID,
         },
       })
@@ -458,10 +483,10 @@ export function Prompt(props: PromptProps) {
           id: sessionID,
         },
         body: {
-          ...local.model.current(),
+          ...selectedModel,
           messageID,
           agent: local.agent.current().name,
-          model: local.model.current(),
+          model: selectedModel,
           parts: [
             {
               id: Identifier.ascending("part"),
@@ -586,12 +611,16 @@ export function Prompt(props: PromptProps) {
       frames: createFrames({
         color,
         style: "blocks",
-        inactiveFactor: 0.25,
+        inactiveFactor: 0.6,
+        // enableFading: false,
+        minAlpha: 0.3,
       }),
       color: createColors({
         color,
         style: "blocks",
-        inactiveFactor: 0.25,
+        inactiveFactor: 0.6,
+        // enableFading: false,
+        minAlpha: 0.3,
       }),
     }
   })
@@ -780,12 +809,12 @@ export function Prompt(props: PromptProps) {
               ref={(r: TextareaRenderable) => {
                 input = r
                 setTimeout(() => {
-                  input.cursorColor = highlight()
+                  input.cursorColor = theme.text
                 }, 0)
               }}
               onMouseDown={(r: MouseEvent) => r.target?.focus()}
               focusedBackgroundColor={theme.backgroundElement}
-              cursorColor={highlight()}
+              cursorColor={theme.text}
               syntaxStyle={syntax()}
             />
             <box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
@@ -839,7 +868,8 @@ export function Prompt(props: PromptProps) {
               justifyContent={status().type === "retry" ? "space-between" : "flex-start"}
             >
               <box flexShrink={0} flexDirection="row" gap={1}>
-                <spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
+                {/* @ts-ignore // SpinnerOptions doesn't support marginLeft */}
+                <spinner marginLeft={1} color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
                 <box flexDirection="row" gap={1} flexShrink={0}>
                   {(() => {
                     const retry = createMemo(() => {

+ 27 - 11
packages/opencode/src/cli/cmd/tui/context/local.tsx

@@ -175,8 +175,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
             return item
           }
         }
+
         const provider = sync.data.provider[0]
-        const model = sync.data.provider_default[provider.id] ?? Object.values(provider.models)[0].id
+        if (!provider) return undefined
+        const defaultModel = sync.data.provider_default[provider.id]
+        const firstModel = Object.values(provider.models)[0]
+        const model = defaultModel ?? firstModel?.id
+        if (!model) return undefined
         return {
           providerID: provider.id,
           modelID: model,
@@ -185,11 +190,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
 
       const currentModel = createMemo(() => {
         const a = agent.current()
-        return getFirstValidModel(
-          () => modelStore.model[a.name],
-          () => a.model,
-          fallbackModel,
-        )!
+        return (
+          getFirstValidModel(
+            () => modelStore.model[a.name],
+            () => a.model,
+            fallbackModel,
+          ) ?? undefined
+        )
       })
 
       return {
@@ -205,11 +212,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
         },
         parsed: createMemo(() => {
           const value = currentModel()
-          const provider = sync.data.provider.find((x) => x.id === value.providerID)!
-          const model = provider.models[value.modelID]
+          if (!value) {
+            return {
+              provider: "Connect a provider",
+              model: "No provider selected",
+            }
+          }
+          const provider = sync.data.provider.find((x) => x.id === value.providerID)
+          const info = provider?.models[value.modelID]
           return {
-            provider: provider.name ?? value.providerID,
-            model: model.name ?? value.modelID,
+            provider: provider?.name ?? value.providerID,
+            model: info?.name ?? value.modelID,
           }
         }),
         cycle(direction: 1 | -1) {
@@ -236,7 +249,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
             return
           }
           const current = currentModel()
-          let index = favorites.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
+          let index = -1
+          if (current) {
+            index = favorites.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
+          }
           if (index === -1) {
             index = direction === 1 ? 0 : favorites.length - 1
           } else {

+ 18 - 0
packages/opencode/src/cli/cmd/tui/context/prompt.tsx

@@ -0,0 +1,18 @@
+import { createSimpleContext } from "./helper"
+import type { PromptRef } from "../component/prompt"
+
+export const { use: usePromptRef, provider: PromptRefProvider } = createSimpleContext({
+  name: "PromptRef",
+  init: () => {
+    let current: PromptRef | undefined
+
+    return {
+      get current() {
+        return current
+      },
+      set(ref: PromptRef | undefined) {
+        current = ref
+      },
+    }
+  },
+})

+ 2 - 0
packages/opencode/src/cli/cmd/tui/context/route.tsx

@@ -1,8 +1,10 @@
 import { createStore } from "solid-js/store"
 import { createSimpleContext } from "./helper"
+import type { PromptInfo } from "../component/prompt/history"
 
 export type HomeRoute = {
   type: "home"
+  initialPrompt?: PromptInfo
 }
 
 export type SessionRoute = {

+ 49 - 1
packages/opencode/src/cli/cmd/tui/context/theme.tsx

@@ -161,7 +161,7 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
 
       if (c.startsWith("#")) return RGBA.fromHex(c)
 
-      if (defs[c]) {
+      if (defs[c] != null) {
         return resolveColor(defs[c])
       } else if (theme.theme[c as keyof ThemeColors] !== undefined) {
         return resolveColor(theme.theme[c as keyof ThemeColors]!)
@@ -169,6 +169,9 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
         throw new Error(`Color reference "${c}" not found in defs or theme`)
       }
     }
+    if (typeof c === "number") {
+      return ansiToRgba(c)
+    }
     return resolveColor(c[mode])
   }
 
@@ -203,6 +206,51 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
   } as Theme
 }
 
+function ansiToRgba(code: number): RGBA {
+  // Standard ANSI colors (0-15)
+  if (code < 16) {
+    const ansiColors = [
+      "#000000", // Black
+      "#800000", // Red
+      "#008000", // Green
+      "#808000", // Yellow
+      "#000080", // Blue
+      "#800080", // Magenta
+      "#008080", // Cyan
+      "#c0c0c0", // White
+      "#808080", // Bright Black
+      "#ff0000", // Bright Red
+      "#00ff00", // Bright Green
+      "#ffff00", // Bright Yellow
+      "#0000ff", // Bright Blue
+      "#ff00ff", // Bright Magenta
+      "#00ffff", // Bright Cyan
+      "#ffffff", // Bright White
+    ]
+    return RGBA.fromHex(ansiColors[code] ?? "#000000")
+  }
+
+  // 6x6x6 Color Cube (16-231)
+  if (code < 232) {
+    const index = code - 16
+    const b = index % 6
+    const g = Math.floor(index / 6) % 6
+    const r = Math.floor(index / 36)
+
+    const val = (x: number) => (x === 0 ? 0 : x * 40 + 55)
+    return RGBA.fromInts(val(r), val(g), val(b))
+  }
+
+  // Grayscale Ramp (232-255)
+  if (code < 256) {
+    const gray = (code - 232) * 10 + 8
+    return RGBA.fromInts(gray, gray, gray)
+  }
+
+  // Fallback for invalid codes
+  return RGBA.fromInts(0, 0, 0)
+}
+
 export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
   name: "Theme",
   init: (props: { mode: "dark" | "light" }) => {

+ 16 - 6
packages/opencode/src/cli/cmd/tui/routes/home.tsx

@@ -1,15 +1,14 @@
 import { Prompt, type PromptRef } from "@tui/component/prompt"
-import { createMemo, Match, onMount, Show, Switch, type ParentProps } from "solid-js"
+import { createMemo, Match, onMount, Show, Switch } from "solid-js"
 import { useTheme } from "@tui/context/theme"
-import { useKeybind } from "../context/keybind"
-import type { KeybindsConfig } from "@opencode-ai/sdk"
 import { Logo } from "../component/logo"
 import { Locale } from "@/util/locale"
 import { useSync } from "../context/sync"
 import { Toast } from "../ui/toast"
 import { useArgs } from "../context/args"
-import { Global } from "@/global"
 import { useDirectory } from "../context/directory"
+import { useRoute, useRouteData } from "@tui/context/route"
+import { usePromptRef } from "../context/prompt"
 
 // TODO: what is the best way to do this?
 let once = false
@@ -17,6 +16,8 @@ let once = false
 export function Home() {
   const sync = useSync()
   const { theme } = useTheme()
+  const route = useRouteData("home")
+  const promptRef = usePromptRef()
   const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0)
   const mcpError = createMemo(() => {
     return Object.values(sync.data.mcp).some((x) => x.status === "failed")
@@ -45,7 +46,10 @@ export function Home() {
   const args = useArgs()
   onMount(() => {
     if (once) return
-    if (args.prompt) {
+    if (route.initialPrompt) {
+      prompt.set(route.initialPrompt)
+      once = true
+    } else if (args.prompt) {
       prompt.set({ input: args.prompt, parts: [] })
       once = true
     }
@@ -57,7 +61,13 @@ export function Home() {
       <box flexGrow={1} justifyContent="center" alignItems="center" paddingLeft={2} paddingRight={2} gap={1}>
         <Logo />
         <box width="100%" maxWidth={75} zIndex={1000} paddingTop={1}>
-          <Prompt ref={(r) => (prompt = r)} hint={Hint} />
+          <Prompt
+            ref={(r) => {
+              prompt = r
+              promptRef.set(r)
+            }}
+            hint={Hint}
+          />
         </box>
         <Toast />
       </box>

+ 108 - 8
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -63,6 +63,7 @@ import { useKV } from "../../context/kv.tsx"
 import { Editor } from "../../util/editor"
 import stripAnsi from "strip-ansi"
 import { Footer } from "./footer.tsx"
+import { usePromptRef } from "../../context/prompt"
 
 addDefaultParsers(parsers.parsers)
 
@@ -81,6 +82,8 @@ const context = createContext<{
   conceal: () => boolean
   showThinking: () => boolean
   showTimestamps: () => boolean
+  usernameVisible: () => boolean
+  showDetails: () => boolean
   diffWrapMode: () => "word" | "none"
   sync: ReturnType<typeof useSync>
 }>()
@@ -97,6 +100,7 @@ export function Session() {
   const sync = useSync()
   const kv = useKV()
   const { theme } = useTheme()
+  const promptRef = usePromptRef()
   const session = createMemo(() => sync.session.get(route.sessionID)!)
   const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
   const permissions = createMemo(() => sync.data.permission[route.sessionID] ?? [])
@@ -114,6 +118,8 @@ export function Session() {
   const [conceal, setConceal] = createSignal(true)
   const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
   const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
+  const [usernameVisible, setUsernameVisible] = createSignal(kv.get("username_visible", true))
+  const [showDetails, setShowDetails] = createSignal(kv.get("tool_details_visibility", true))
   const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
 
   const wide = createMemo(() => dimensions().width > 120)
@@ -134,7 +140,7 @@ export function Session() {
       return new CustomSpeedScroll(tui.scroll_speed)
     }
 
-    return new CustomSpeedScroll(process.platform === "win32" ? 3 : 1)
+    return new CustomSpeedScroll(3)
   })
 
   createEffect(async () => {
@@ -269,13 +275,22 @@ export function Session() {
       keybind: "session_compact",
       category: "Session",
       onSelect: (dialog) => {
+        const selectedModel = local.model.current()
+        if (!selectedModel) {
+          toast.show({
+            variant: "warning",
+            message: "Connect a provider to summarize this session",
+            duration: 3000,
+          })
+          return
+        }
         sdk.client.session.summarize({
           path: {
             id: route.sessionID,
           },
           body: {
-            modelID: local.model.current().modelID,
-            providerID: local.model.current().providerID,
+            modelID: selectedModel.modelID,
+            providerID: selectedModel.providerID,
           },
         })
         dialog.clear()
@@ -408,6 +423,20 @@ export function Session() {
         dialog.clear()
       },
     },
+    {
+      title: usernameVisible() ? "Hide username" : "Show username",
+      value: "session.username_visible.toggle",
+      keybind: "username_toggle",
+      category: "Session",
+      onSelect: (dialog) => {
+        setUsernameVisible((prev) => {
+          const next = !prev
+          kv.set("username_visible", next)
+          return next
+        })
+        dialog.clear()
+      },
+    },
     {
       title: "Toggle code concealment",
       value: "session.toggle.conceal",
@@ -453,6 +482,18 @@ export function Session() {
         dialog.clear()
       },
     },
+    {
+      title: showDetails() ? "Hide tool details" : "Show tool details",
+      value: "session.toggle.actions",
+      keybind: "tool_details",
+      category: "Session",
+      onSelect: (dialog) => {
+        const newValue = !showDetails()
+        setShowDetails(newValue)
+        kv.set("tool_details_visibility", newValue)
+        dialog.clear()
+      },
+    },
     {
       title: "Page up",
       value: "session.page.up",
@@ -519,6 +560,37 @@ export function Session() {
         dialog.clear()
       },
     },
+    {
+      title: "Jump to last user message",
+      value: "session.messages_last_user",
+      keybind: "messages_last_user",
+      category: "Session",
+      onSelect: () => {
+        const messages = sync.data.message[route.sessionID]
+        if (!messages || !messages.length) return
+
+        // Find the most recent user message with non-ignored, non-synthetic text parts
+        for (let i = messages.length - 1; i >= 0; i--) {
+          const message = messages[i]
+          if (!message || message.role !== "user") continue
+
+          const parts = sync.data.part[message.id]
+          if (!parts || !Array.isArray(parts)) continue
+
+          const hasValidTextPart = parts.some(
+            (part) => part && part.type === "text" && !part.synthetic && !part.ignored,
+          )
+
+          if (hasValidTextPart) {
+            const child = scroll.getChildren().find((child) => {
+              return child.id === message.id
+            })
+            if (child) scroll.scrollBy(child.y - scroll.y - 1)
+            break
+          }
+        }
+      },
+    },
     {
       title: "Copy last assistant message",
       value: "messages.copy",
@@ -754,6 +826,8 @@ export function Session() {
         conceal,
         showThinking,
         showTimestamps,
+        usernameVisible,
+        showDetails,
         diffWrapMode,
         sync,
       }}
@@ -877,7 +951,10 @@ export function Session() {
             </scrollbox>
             <box flexShrink={0}>
               <Prompt
-                ref={(r) => (prompt = r)}
+                ref={(r) => {
+                  prompt = r
+                  promptRef.set(r)
+                }}
                 disabled={permissions().length > 0}
                 onSubmit={() => {
                   toBottom()
@@ -917,13 +994,14 @@ function UserMessage(props: {
   pending?: string
 }) {
   const ctx = use()
+  const local = useLocal()
   const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
   const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
   const sync = useSync()
   const { theme } = useTheme()
   const [hover, setHover] = createSignal(false)
   const queued = createMemo(() => props.pending && props.message.id > props.pending)
-  const color = createMemo(() => (queued() ? theme.accent : theme.secondary))
+  const color = createMemo(() => (queued() ? theme.accent : local.agent.color(props.message.agent)))
 
   const compaction = createMemo(() => props.parts.find((x) => x.type === "compaction"))
 
@@ -972,7 +1050,7 @@ function UserMessage(props: {
               </box>
             </Show>
             <text fg={theme.textMuted}>
-              {sync.data.config.username ?? "You"}{" "}
+              {ctx.usernameVisible() ? `${sync.data.config.username ?? "You"} ` : "You"}{" "}
               <Show
                 when={queued()}
                 fallback={
@@ -1078,7 +1156,11 @@ const PART_MAPPING = {
 function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
   const { theme, subtleSyntax } = useTheme()
   const ctx = use()
-  const content = createMemo(() => props.part.text.trim())
+  const content = createMemo(() => {
+    // Filter out redacted reasoning chunks from OpenRouter
+    // OpenRouter sends encrypted reasoning data that appears as [REDACTED]
+    return props.part.text.replace("[REDACTED]", "").trim()
+  })
   return (
     <Show when={content() && ctx.showThinking()}>
       <box
@@ -1128,9 +1210,21 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
 
 function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMessage }) {
   const { theme } = useTheme()
+  const { showDetails } = use()
   const sync = useSync()
   const [margin, setMargin] = createSignal(0)
   const component = createMemo(() => {
+    // Hide tool if showDetails is false and tool completed successfully
+    // But always show if there's an error or permission is required
+    const shouldHide =
+      !showDetails() &&
+      props.part.state.status === "completed" &&
+      !sync.data.permission[props.message.sessionID]?.some((x) => x.callID === props.part.callID)
+
+    if (shouldHide) {
+      return undefined
+    }
+
     const render = ToolRegistry.render(props.part.tool) ?? GenericTool
 
     const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
@@ -1327,7 +1421,13 @@ ToolRegistry.register<typeof WriteTool>({
           Wrote {props.input.filePath}
         </ToolTitle>
         <line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
-          <code fg={theme.text} filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
+          <code
+            conceal={false}
+            fg={theme.text}
+            filetype={filetype(props.input.filePath!)}
+            syntaxStyle={syntax()}
+            content={code()}
+          />
         </line_number>
         <Show when={diagnostics().length}>
           <For each={diagnostics()}>

+ 4 - 2
packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx

@@ -299,11 +299,13 @@ function Option(props: {
         fg={props.active ? fg : props.current ? theme.primary : theme.text}
         attributes={props.active ? TextAttributes.BOLD : undefined}
         overflow="hidden"
-        wrapMode="none"
+        wrapMode="word"
         paddingLeft={3}
       >
         {Locale.truncate(props.title, 62)}
-        <span style={{ fg: props.active ? fg : theme.textMuted }}> {props.description}</span>
+        <Show when={props.description}>
+          <span style={{ fg: props.active ? fg : theme.textMuted }}> {props.description}</span>
+        </Show>
       </text>
       <Show when={props.footer}>
         <box flexShrink={0}>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません