mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-05-01 22:44:21 +08:00
Compare commits
36 Commits
kit/sessio
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ddf5f559 | ||
|
|
8c79c58c4d | ||
|
|
97ed9ba624 | ||
|
|
a6b6395c8a | ||
|
|
21f8027ef7 | ||
|
|
a5aa72bd7d | ||
|
|
563177c6ac | ||
|
|
4eae8ec037 | ||
|
|
08895c396e | ||
|
|
4e451a4b0f | ||
|
|
163290bcf0 | ||
|
|
c68c33d4fe | ||
|
|
3615d8e226 | ||
|
|
2283979199 | ||
|
|
33f7f593ee | ||
|
|
461e7345b3 | ||
|
|
6bd91c68e8 | ||
|
|
ff55a40749 | ||
|
|
8b56d77ea1 | ||
|
|
dd3aa96730 | ||
|
|
8b56d1712f | ||
|
|
3c24d22d42 | ||
|
|
4c70ea28d2 | ||
|
|
5ba68a28c0 | ||
|
|
bce4def2db | ||
|
|
3544ea0244 | ||
|
|
6434918794 | ||
|
|
5984d917dc | ||
|
|
c2a97a7a6c | ||
|
|
a083c88e87 | ||
|
|
ce3b0988c4 | ||
|
|
e8a194a2bb | ||
|
|
8aa8798e07 | ||
|
|
6d4629b566 | ||
|
|
a9d399699e | ||
|
|
bc805b3001 |
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -36,3 +36,9 @@ jobs:
|
||||
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
|
||||
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
||||
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: web@${{ github.sha }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_RELEASE: web@${{ github.sha }}
|
||||
|
||||
7
.github/workflows/publish.yml
vendored
7
.github/workflows/publish.yml
vendored
@@ -494,6 +494,13 @@ jobs:
|
||||
working-directory: packages/desktop-electron
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
|
||||
- name: Package and publish
|
||||
if: needs.version.outputs.release
|
||||
|
||||
149
bun.lock
149
bun.lock
@@ -29,12 +29,13 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/active-element": "2.1.3",
|
||||
"@solid-primitives/audio": "1.4.2",
|
||||
@@ -69,6 +70,7 @@
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "20.0.11",
|
||||
"@playwright/test": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -83,7 +85,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -117,7 +119,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -144,7 +146,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -168,7 +170,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -192,7 +194,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -226,10 +228,11 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -250,6 +253,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
@@ -259,7 +263,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -275,6 +279,8 @@
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -303,7 +309,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -332,7 +338,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -348,7 +354,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -456,7 +462,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.4",
|
||||
"@effect/language-service": "0.84.2",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
@@ -491,7 +496,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -526,7 +531,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -541,7 +546,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -576,7 +581,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -625,7 +630,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -689,6 +694,8 @@
|
||||
"@opentui/solid": "0.2.0",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@sentry/solid": "10.36.0",
|
||||
"@sentry/vite-plugin": "4.6.0",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
@@ -1069,8 +1076,6 @@
|
||||
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
|
||||
|
||||
"@effect/language-service": ["@effect/language-service@0.84.2", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-l04qNxpiA8rY5yXWckRPJ7Mk5MNerXuNymSFf+IdflfI5i8jgL1bpBNLuP6ijg7wgjdHc/KmTnCj2kT0SCntuA=="],
|
||||
|
||||
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.57", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="],
|
||||
|
||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.57", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="],
|
||||
@@ -1959,6 +1964,44 @@
|
||||
|
||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||
|
||||
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-WILVR8HQBWOxbqLRuTxjzRCMIACGsDTo6jXvzA8rz6ezElElLmIrn3CFAswrESLqEEUa4CQHl5bLgSVJCRNweA=="],
|
||||
|
||||
"@sentry-internal/feedback": ["@sentry-internal/feedback@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-zPjz7AbcxEyx8AHj8xvp28fYtPTPWU1XcNtymhAHJLS9CXOblqSC7W02Jxz6eo3eR1/pLyOo6kJBUjvLe9EoFA=="],
|
||||
|
||||
"@sentry-internal/replay": ["@sentry-internal/replay@10.36.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-nLMkJgvHq+uCCrQKV2KgSdVHxTsmDk0r2hsAoTcKCbzUpXyW5UhCziMRS6ULjBlzt5sbxoIIplE25ZpmIEeNgg=="],
|
||||
|
||||
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.36.0", "", { "dependencies": { "@sentry-internal/replay": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-DLGIwmT2LX+O6TyYPtOQL5GiTm2rN0taJPDJ/Lzg2KEJZrdd5sKkzTckhh2x+vr4JQyeaLmnb8M40Ch1hvG/vQ=="],
|
||||
|
||||
"@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.6.0", "", {}, "sha512-3soTX50JPQQ51FSbb4qvNBf4z/yP7jTdn43vMTp9E4IxvJ9HKJR7OEuKkCMszrZmWsVABXl02msqO7QisePdiQ=="],
|
||||
|
||||
"@sentry/browser": ["@sentry/browser@10.36.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.36.0", "@sentry-internal/feedback": "10.36.0", "@sentry-internal/replay": "10.36.0", "@sentry-internal/replay-canvas": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-yHhXbgdGY1s+m8CdILC9U/II7gb6+s99S2Eh8VneEn/JG9wHc+UOzrQCeFN0phFP51QbLkjkiQbbanjT1HP8UQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@4.6.0", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "4.6.0", "@sentry/cli": "^2.57.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^9.3.2", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-Fub2XQqrS258jjS8qAxLLU1k1h5UCNJ76i8m4qZJJdogWWaF8t00KnnTyp9TEDJzrVD64tRXS8+HHENxmeUo3g=="],
|
||||
|
||||
"@sentry/cli": ["@sentry/cli@2.58.5", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.5", "@sentry/cli-linux-arm": "2.58.5", "@sentry/cli-linux-arm64": "2.58.5", "@sentry/cli-linux-i686": "2.58.5", "@sentry/cli-linux-x64": "2.58.5", "@sentry/cli-win32-arm64": "2.58.5", "@sentry/cli-win32-i686": "2.58.5", "@sentry/cli-win32-x64": "2.58.5" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg=="],
|
||||
|
||||
"@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.5", "", { "os": "darwin" }, "sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ=="],
|
||||
|
||||
"@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw=="],
|
||||
|
||||
"@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ=="],
|
||||
|
||||
"@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw=="],
|
||||
|
||||
"@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.5", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g=="],
|
||||
|
||||
"@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA=="],
|
||||
|
||||
"@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g=="],
|
||||
|
||||
"@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.5", "", { "os": "win32", "cpu": "x64" }, "sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg=="],
|
||||
|
||||
"@sentry/core": ["@sentry/core@10.36.0", "", {}, "sha512-EYJjZvofI+D93eUsPLDIUV0zQocYqiBRyXS6CCV6dHz64P/Hob5NJQOwPa8/v6nD+UvJXvwsFfvXOHhYZhZJOQ=="],
|
||||
|
||||
"@sentry/solid": ["@sentry/solid@10.36.0", "", { "dependencies": { "@sentry/browser": "10.36.0", "@sentry/core": "10.36.0" }, "peerDependencies": { "@solidjs/router": "^0.13.4 || ^0.14.0 || ^0.15.0", "@tanstack/solid-router": "^1.132.27", "solid-js": "^1.8.4" }, "optionalPeers": ["@solidjs/router", "@tanstack/solid-router"] }, "sha512-AaDqz3JGBrQCm2YVqODVyJHwg7LRTNSJig9mjfProFyvkC7eUXQ/HBJrrhAD1Dct9ufmDH3G+f3/Ut9LgpItSg=="],
|
||||
|
||||
"@sentry/vite-plugin": ["@sentry/vite-plugin@4.6.0", "", { "dependencies": { "@sentry/bundler-plugin-core": "4.6.0", "unplugin": "1.0.1" } }, "sha512-fMR2d+EHwbzBa0S1fp45SNUTProxmyFBp+DeBWWQOSP9IU6AH6ea2rqrpMAnp/skkcdW4z4LSRrOEpMZ5rWXLw=="],
|
||||
|
||||
"@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="],
|
||||
|
||||
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="],
|
||||
@@ -3243,7 +3286,7 @@
|
||||
|
||||
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
||||
|
||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"finity": ["finity@0.5.4", "", {}, "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA=="],
|
||||
|
||||
@@ -3737,7 +3780,7 @@
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||
|
||||
@@ -4141,7 +4184,7 @@
|
||||
|
||||
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
|
||||
|
||||
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
|
||||
|
||||
@@ -4191,7 +4234,7 @@
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
|
||||
"path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
|
||||
|
||||
@@ -4951,7 +4994,7 @@
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
"unplugin": ["unplugin@1.0.1", "", { "dependencies": { "acorn": "^8.8.1", "chokidar": "^3.5.3", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.5.0" } }, "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA=="],
|
||||
|
||||
"unstorage": ["unstorage@2.0.0-alpha.7", "", { "peerDependencies": { "@azure/app-configuration": "^1.11.0", "@azure/cosmos": "^4.9.1", "@azure/data-tables": "^13.3.2", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.31.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.13.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.36.2", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.9.3", "lru-cache": "^11.2.6", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog=="],
|
||||
|
||||
@@ -5059,7 +5102,9 @@
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
"webpack-sources": ["webpack-sources@3.4.0", "", {}, "sha512-gHwIe1cgBvvfLeu1Yz/dcFpmHfKDVxxyqI+kzqmuxZED81z2ChxpyqPaWcNqigPywhaEke7AjSGga+kxY55gjQ=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.5.0", "", {}, "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
|
||||
|
||||
@@ -5611,6 +5656,16 @@
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
|
||||
|
||||
"@sentry/cli/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"@sentry/cli/proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"@sentry/cli/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"@shikijs/engine-oniguruma/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
@@ -5665,6 +5720,8 @@
|
||||
|
||||
"@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.48", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="],
|
||||
|
||||
"@storybook/csf-plugin/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
|
||||
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
@@ -5849,8 +5906,6 @@
|
||||
|
||||
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
@@ -5961,7 +6016,7 @@
|
||||
|
||||
"ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
|
||||
@@ -5973,6 +6028,8 @@
|
||||
|
||||
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
||||
|
||||
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"pkg-up/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
|
||||
|
||||
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
@@ -6069,6 +6126,10 @@
|
||||
|
||||
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||
|
||||
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"unused-filename/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
|
||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
@@ -6569,6 +6630,16 @@
|
||||
|
||||
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@sentry/cli/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||
|
||||
"@sentry/cli/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
@@ -6591,6 +6662,8 @@
|
||||
|
||||
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@storybook/csf-plugin/unplugin/webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
@@ -6737,8 +6810,12 @@
|
||||
|
||||
"ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"parse-bmfont-xml/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||
|
||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
@@ -6767,6 +6844,8 @@
|
||||
|
||||
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
@@ -6991,6 +7070,12 @@
|
||||
|
||||
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||
@@ -7087,6 +7172,8 @@
|
||||
|
||||
"ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||
@@ -7099,6 +7186,8 @@
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"unplugin/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@astrojs/check/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -7153,6 +7242,8 @@
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
|
||||
@@ -7179,6 +7270,8 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-cBfg4pJ4mjsfS4MFFASBaZZykArgIoeo/3woOcSGy1U=",
|
||||
"aarch64-linux": "sha256-Q6cqUwfqbscdrPW0uHcfshhQINjJi0HiyURMSdOOCf4=",
|
||||
"aarch64-darwin": "sha256-1AtfsD1D9YxWSEsecPJF9XsvsxsWTtVtkP5l6UW43og=",
|
||||
"x86_64-darwin": "sha256-YS5/8YTf9LymAUbjXVrGDfxtKVJrpZbPnnCtsGHSHoU="
|
||||
"x86_64-linux": "sha256-OtyfKTBEHsJpjzAjN9vCR0PzGzdK6CDHdyU7eZ6Gl1s=",
|
||||
"aarch64-linux": "sha256-3eHJs3S/+uDUPAouWPsdBOlEvAOhOYx5bJzahL0tAJk=",
|
||||
"aarch64-darwin": "sha256-rFXzrkhPVb3yM20J8R8m7GqroNNk1vAEz+o/Ks+iAI4=",
|
||||
"x86_64-darwin": "sha256-lb1IGgbpxg723Qxj2WVPkxKUUmyOIsFOAhA5LoZ8GwY="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
"@sentry/solid": "10.36.0",
|
||||
"@sentry/vite-plugin": "4.6.0",
|
||||
"solid-js": "1.9.10",
|
||||
"vite-plugin-solid": "2.11.10",
|
||||
"@lydell/node-pty": "1.2.0-beta.10"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -27,6 +27,7 @@
|
||||
"devDependencies": {
|
||||
"@happy-dom/global-registrator": "20.0.11",
|
||||
"@playwright/test": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tailwindcss/vite": "catalog:",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -40,6 +41,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@/index.css"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { I18nProvider } from "@opencode-ai/ui/context"
|
||||
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
||||
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
|
||||
@@ -148,12 +149,19 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
||||
>
|
||||
<LanguageProvider locale={props.locale}>
|
||||
<UiI18nBridge>
|
||||
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
<ErrorBoundary
|
||||
fallback={(error) => {
|
||||
Sentry.captureException(error)
|
||||
return <ErrorPage error={error} />
|
||||
}}
|
||||
>
|
||||
<QueryProvider>
|
||||
<DialogProvider>
|
||||
<MarkedProvider>
|
||||
<FileComponentProvider component={File}>{props.children}</FileComponentProvider>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
</QueryProvider>
|
||||
</ErrorBoundary>
|
||||
</UiI18nBridge>
|
||||
</LanguageProvider>
|
||||
|
||||
@@ -329,6 +329,7 @@ export const SettingsGeneral: Component = () => {
|
||||
label={(o) => o.label}
|
||||
onSelect={(option) => {
|
||||
if (!option) return
|
||||
if (option.value === currentShell()) return
|
||||
globalSync.updateConfig({ shell: option.value })
|
||||
}}
|
||||
variant="secondary"
|
||||
|
||||
@@ -204,6 +204,9 @@ function createGlobalSync() {
|
||||
},
|
||||
translate: language.t,
|
||||
getSdk: sdkFor,
|
||||
global: {
|
||||
provider: globalStore.provider,
|
||||
},
|
||||
})
|
||||
|
||||
async function loadSessions(directory: string) {
|
||||
|
||||
@@ -260,9 +260,6 @@ export async function bootstrapDirectory(input: {
|
||||
const seededPath = input.global.path.directory === input.directory ? input.global.path : undefined
|
||||
if (seededProject) input.setStore("project", seededProject)
|
||||
if (seededPath) input.setStore("path", seededPath)
|
||||
if (input.store.provider.all.length === 0 && input.global.provider.all.length > 0) {
|
||||
input.setStore("provider", input.global.provider)
|
||||
}
|
||||
if (Object.keys(input.store.config).length === 0 && Object.keys(input.global.config).length > 0) {
|
||||
input.setStore("config", reconcile(input.global.config, { merge: false }))
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("createChildStoreManager", () => {
|
||||
onDispose() {},
|
||||
translate: (key) => key,
|
||||
getSdk: () => null!,
|
||||
global: { provider: null! },
|
||||
})
|
||||
|
||||
Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
|
||||
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { OpencodeClient, VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import type { OpencodeClient, ProviderListResponse, VcsInfo } from "@opencode-ai/sdk/v2/client"
|
||||
import {
|
||||
DIR_IDLE_TTL_MS,
|
||||
MAX_DIR_STORES,
|
||||
@@ -27,6 +27,9 @@ export function createChildStoreManager(input: {
|
||||
onDispose: (directory: string) => void
|
||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||
getSdk: (directory: string) => OpencodeClient
|
||||
global: {
|
||||
provider: ProviderListResponse
|
||||
}
|
||||
}) {
|
||||
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
|
||||
const vcsCache = new Map<string, VcsCache>()
|
||||
@@ -189,7 +192,13 @@ export function createChildStoreManager(input: {
|
||||
get provider_ready() {
|
||||
return !providerQuery.isLoading
|
||||
},
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
get provider() {
|
||||
const EMPTY = { all: [], connected: [], default: {} }
|
||||
if (providerQuery.isLoading) return EMPTY
|
||||
if (providerQuery.data?.all.length === 0 && input.global.provider.all.length > 0)
|
||||
return input.global.provider
|
||||
return providerQuery.data ?? EMPTY
|
||||
},
|
||||
config: {},
|
||||
get path() {
|
||||
if (pathQuery.isLoading || !pathQuery.data)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @refresh reload
|
||||
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { render } from "solid-js/web"
|
||||
import { AppBaseProviders, AppInterface } from "@/app"
|
||||
import { type Platform, PlatformProvider } from "@/context/platform"
|
||||
@@ -125,6 +126,25 @@ const platform: Platform = {
|
||||
setDefaultServer: writeDefaultServerUrl,
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE,
|
||||
release: import.meta.env.VITE_SENTRY_RELEASE ?? `web@${pkg.version}`,
|
||||
initialScope: {
|
||||
tags: {
|
||||
platform: "web",
|
||||
},
|
||||
},
|
||||
integrations: (integrations) => {
|
||||
return integrations.filter(
|
||||
(i) =>
|
||||
i.name !== "Breadcrumbs" && !(import.meta.env.OPENCODE_CHANNEL === "prod" && i.name === "GlobalHandlers"),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (root instanceof HTMLElement) {
|
||||
const server: ServerConnection.Http = { type: "http", http: { url: getCurrentUrl() } }
|
||||
render(
|
||||
|
||||
4
packages/app/src/env.d.ts
vendored
4
packages/app/src/env.d.ts
vendored
@@ -2,6 +2,10 @@ interface ImportMetaEnv {
|
||||
readonly VITE_OPENCODE_SERVER_HOST: string
|
||||
readonly VITE_OPENCODE_SERVER_PORT: string
|
||||
readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod"
|
||||
|
||||
readonly VITE_SENTRY_DSN?: string
|
||||
readonly VITE_SENTRY_ENVIRONMENT?: string
|
||||
readonly VITE_SENTRY_RELEASE?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@@ -402,6 +402,8 @@ export const dict = {
|
||||
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
|
||||
"error.page.details.label": "تفاصيل الخطأ",
|
||||
"error.page.action.restart": "إعادة تشغيل",
|
||||
"error.page.action.report": "الإبلاغ عن الخطأ",
|
||||
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
|
||||
"error.page.action.checking": "جارٍ التحقق...",
|
||||
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
|
||||
"error.page.action.updateTo": "تحديث إلى {{version}}",
|
||||
|
||||
@@ -403,6 +403,8 @@ export const dict = {
|
||||
"error.page.description": "Ocorreu um erro ao carregar a aplicação.",
|
||||
"error.page.details.label": "Detalhes do Erro",
|
||||
"error.page.action.restart": "Reiniciar",
|
||||
"error.page.action.report": "Reportar erro",
|
||||
"error.page.action.reported": "Erro reportado",
|
||||
"error.page.action.checking": "Verificando...",
|
||||
"error.page.action.checkUpdates": "Verificar atualizações",
|
||||
"error.page.action.updateTo": "Atualizar para {{version}}",
|
||||
|
||||
@@ -449,6 +449,8 @@ export const dict = {
|
||||
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
|
||||
"error.page.details.label": "Detalji greške",
|
||||
"error.page.action.restart": "Restartuj",
|
||||
"error.page.action.report": "Prijavi grešku",
|
||||
"error.page.action.reported": "Greška prijavljena",
|
||||
"error.page.action.checking": "Provjera...",
|
||||
"error.page.action.checkUpdates": "Provjeri ažuriranja",
|
||||
"error.page.action.updateTo": "Ažuriraj na {{version}}",
|
||||
|
||||
@@ -446,6 +446,8 @@ export const dict = {
|
||||
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
|
||||
"error.page.details.label": "Fejldetaljer",
|
||||
"error.page.action.restart": "Genstart",
|
||||
"error.page.action.report": "Rapportér fejl",
|
||||
"error.page.action.reported": "Fejl rapporteret",
|
||||
"error.page.action.checking": "Tjekker...",
|
||||
"error.page.action.checkUpdates": "Tjek for opdateringer",
|
||||
"error.page.action.updateTo": "Opdater til {{version}}",
|
||||
|
||||
@@ -410,6 +410,8 @@ export const dict = {
|
||||
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
|
||||
"error.page.details.label": "Fehlerdetails",
|
||||
"error.page.action.restart": "Neustart",
|
||||
"error.page.action.report": "Fehler melden",
|
||||
"error.page.action.reported": "Fehler gemeldet",
|
||||
"error.page.action.checking": "Prüfen...",
|
||||
"error.page.action.checkUpdates": "Nach Updates suchen",
|
||||
"error.page.action.updateTo": "Auf {{version}} aktualisieren",
|
||||
|
||||
@@ -465,6 +465,8 @@ export const dict = {
|
||||
"error.page.description": "An error occurred while loading the application.",
|
||||
"error.page.details.label": "Error Details",
|
||||
"error.page.action.restart": "Restart",
|
||||
"error.page.action.report": "Report Error",
|
||||
"error.page.action.reported": "Error Reported",
|
||||
"error.page.action.checking": "Checking...",
|
||||
"error.page.action.checkUpdates": "Check for updates",
|
||||
"error.page.action.updateTo": "Update to {{version}}",
|
||||
|
||||
@@ -449,6 +449,8 @@ export const dict = {
|
||||
"error.page.description": "Ocurrió un error al cargar la aplicación.",
|
||||
"error.page.details.label": "Detalles del error",
|
||||
"error.page.action.restart": "Reiniciar",
|
||||
"error.page.action.report": "Informar error",
|
||||
"error.page.action.reported": "Error informado",
|
||||
"error.page.action.checking": "Comprobando...",
|
||||
"error.page.action.checkUpdates": "Buscar actualizaciones",
|
||||
"error.page.action.updateTo": "Actualizar a {{version}}",
|
||||
|
||||
@@ -406,6 +406,8 @@ export const dict = {
|
||||
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
|
||||
"error.page.details.label": "Détails de l'erreur",
|
||||
"error.page.action.restart": "Redémarrer",
|
||||
"error.page.action.report": "Signaler l'erreur",
|
||||
"error.page.action.reported": "Erreur signalée",
|
||||
"error.page.action.checking": "Vérification...",
|
||||
"error.page.action.checkUpdates": "Vérifier les mises à jour",
|
||||
"error.page.action.updateTo": "Mettre à jour vers {{version}}",
|
||||
|
||||
@@ -402,6 +402,8 @@ export const dict = {
|
||||
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
|
||||
"error.page.details.label": "エラー詳細",
|
||||
"error.page.action.restart": "再起動",
|
||||
"error.page.action.report": "エラーを報告",
|
||||
"error.page.action.reported": "エラーを報告しました",
|
||||
"error.page.action.checking": "確認中...",
|
||||
"error.page.action.checkUpdates": "アップデートを確認",
|
||||
"error.page.action.updateTo": "{{version}}にアップデート",
|
||||
|
||||
@@ -401,6 +401,8 @@ export const dict = {
|
||||
"error.page.description": "애플리케이션을 로드하는 동안 오류가 발생했습니다.",
|
||||
"error.page.details.label": "오류 세부 정보",
|
||||
"error.page.action.restart": "다시 시작",
|
||||
"error.page.action.report": "오류 신고",
|
||||
"error.page.action.reported": "오류가 신고됨",
|
||||
"error.page.action.checking": "확인 중...",
|
||||
"error.page.action.checkUpdates": "업데이트 확인",
|
||||
"error.page.action.updateTo": "{{version}} 버전으로 업데이트",
|
||||
|
||||
@@ -450,6 +450,8 @@ export const dict = {
|
||||
"error.page.description": "Det oppstod en feil under lasting av applikasjonen.",
|
||||
"error.page.details.label": "Feildetaljer",
|
||||
"error.page.action.restart": "Start på nytt",
|
||||
"error.page.action.report": "Rapporter feil",
|
||||
"error.page.action.reported": "Feil rapportert",
|
||||
"error.page.action.checking": "Sjekker...",
|
||||
"error.page.action.checkUpdates": "Se etter oppdateringer",
|
||||
"error.page.action.updateTo": "Oppdater til {{version}}",
|
||||
|
||||
@@ -403,6 +403,8 @@ export const dict = {
|
||||
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
|
||||
"error.page.details.label": "Szczegóły błędu",
|
||||
"error.page.action.restart": "Restartuj",
|
||||
"error.page.action.report": "Zgłoś błąd",
|
||||
"error.page.action.reported": "Błąd zgłoszony",
|
||||
"error.page.action.checking": "Sprawdzanie...",
|
||||
"error.page.action.checkUpdates": "Sprawdź aktualizacje",
|
||||
"error.page.action.updateTo": "Zaktualizuj do {{version}}",
|
||||
|
||||
@@ -448,6 +448,8 @@ export const dict = {
|
||||
"error.page.description": "Произошла ошибка при загрузке приложения.",
|
||||
"error.page.details.label": "Детали ошибки",
|
||||
"error.page.action.restart": "Перезапустить",
|
||||
"error.page.action.report": "Сообщить об ошибке",
|
||||
"error.page.action.reported": "Об ошибке сообщено",
|
||||
"error.page.action.checking": "Проверка...",
|
||||
"error.page.action.checkUpdates": "Проверить обновления",
|
||||
"error.page.action.updateTo": "Обновить до {{version}}",
|
||||
|
||||
@@ -447,6 +447,8 @@ export const dict = {
|
||||
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
|
||||
"error.page.details.label": "รายละเอียดข้อผิดพลาด",
|
||||
"error.page.action.restart": "รีสตาร์ท",
|
||||
"error.page.action.report": "รายงานข้อผิดพลาด",
|
||||
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
|
||||
"error.page.action.checking": "กำลังตรวจสอบ...",
|
||||
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
|
||||
"error.page.action.updateTo": "อัปเดตเป็น {{version}}",
|
||||
|
||||
@@ -452,6 +452,8 @@ export const dict = {
|
||||
"error.page.description": "Uygulama yüklenirken bir hata oluştu.",
|
||||
"error.page.details.label": "Hata Detayları",
|
||||
"error.page.action.restart": "Yeniden Başlat",
|
||||
"error.page.action.report": "Hatayı Bildir",
|
||||
"error.page.action.reported": "Hata Bildirildi",
|
||||
"error.page.action.checking": "Kontrol ediliyor...",
|
||||
"error.page.action.checkUpdates": "Güncellemeleri kontrol et",
|
||||
"error.page.action.updateTo": "{{version}} sürümüne güncelle",
|
||||
|
||||
@@ -452,6 +452,8 @@ export const dict = {
|
||||
"error.page.description": "加载应用程序时发生错误。",
|
||||
"error.page.details.label": "错误详情",
|
||||
"error.page.action.restart": "重启",
|
||||
"error.page.action.report": "上报错误",
|
||||
"error.page.action.reported": "错误已上报",
|
||||
"error.page.action.checking": "检查中...",
|
||||
"error.page.action.checkUpdates": "检查更新",
|
||||
"error.page.action.updateTo": "更新到 {{version}}",
|
||||
|
||||
@@ -445,6 +445,8 @@ export const dict = {
|
||||
"error.page.description": "載入應用程式時發生錯誤。",
|
||||
"error.page.details.label": "錯誤詳情",
|
||||
"error.page.action.restart": "重新啟動",
|
||||
"error.page.action.report": "回報錯誤",
|
||||
"error.page.action.reported": "已回報錯誤",
|
||||
"error.page.action.checking": "檢查中...",
|
||||
"error.page.action.checkUpdates": "檢查更新",
|
||||
"error.page.action.updateTo": "更新到 {{version}}",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import { Logo } from "@opencode-ai/ui/logo"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Component, Show } from "solid-js"
|
||||
import { Component, createSignal, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useLanguage } from "@/context/language"
|
||||
@@ -270,10 +271,27 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
label={language.t("error.page.details.label")}
|
||||
hideLabel
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex flex-row items-center justify-center gap-3 flex-wrap max-w-64">
|
||||
<Button size="large" onClick={platform.restart}>
|
||||
{language.t("error.page.action.restart")}
|
||||
</Button>
|
||||
<Show when={Sentry.isEnabled}>
|
||||
{(_) => {
|
||||
const [reported, setReported] = createSignal(false)
|
||||
return (
|
||||
<Button
|
||||
size="large"
|
||||
disabled={reported()}
|
||||
onClick={() => {
|
||||
Sentry.captureException(props.error)
|
||||
setReported(true)
|
||||
}}
|
||||
>
|
||||
{language.t(reported() ? "error.page.action.reported" : "error.page.action.report")}
|
||||
</Button>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
<Show when={platform.checkUpdate}>
|
||||
<Show
|
||||
when={store.version}
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
||||
import { defineConfig } from "vite"
|
||||
import desktopPlugin from "./vite"
|
||||
|
||||
const sentry =
|
||||
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
|
||||
? sentryVitePlugin({
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
telemetry: false,
|
||||
release: {
|
||||
name: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
|
||||
},
|
||||
sourcemaps: {
|
||||
assets: "./dist/**",
|
||||
filesToDeleteAfterUpload: "./dist/**/*.map",
|
||||
},
|
||||
})
|
||||
: false
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [desktopPlugin] as any,
|
||||
plugins: [desktopPlugin, sentry] as any,
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
allowedHosts: true,
|
||||
@@ -10,6 +28,6 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
target: "esnext",
|
||||
// sourcemap: true,
|
||||
sourcemap: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -11,6 +11,7 @@ const data = path.join(xdgData!, app)
|
||||
const cache = path.join(xdgCache!, app)
|
||||
const config = path.join(xdgConfig!, app)
|
||||
const state = path.join(xdgState!, app)
|
||||
const tmp = path.join(os.tmpdir(), app)
|
||||
|
||||
const paths = {
|
||||
get home() {
|
||||
@@ -22,6 +23,7 @@ const paths = {
|
||||
cache,
|
||||
config,
|
||||
state,
|
||||
tmp,
|
||||
}
|
||||
|
||||
export const Path = paths
|
||||
@@ -32,6 +34,7 @@ await Promise.all([
|
||||
fs.mkdir(Path.data, { recursive: true }),
|
||||
fs.mkdir(Path.config, { recursive: true }),
|
||||
fs.mkdir(Path.state, { recursive: true }),
|
||||
fs.mkdir(Path.tmp, { recursive: true }),
|
||||
fs.mkdir(Path.log, { recursive: true }),
|
||||
fs.mkdir(Path.bin, { recursive: true }),
|
||||
])
|
||||
@@ -44,6 +47,7 @@ export interface Interface {
|
||||
readonly cache: string
|
||||
readonly config: string
|
||||
readonly state: string
|
||||
readonly tmp: string
|
||||
readonly bin: string
|
||||
readonly log: string
|
||||
}
|
||||
@@ -55,6 +59,7 @@ export function make(input: Partial<Interface> = {}): Interface {
|
||||
cache: Path.cache,
|
||||
config: Flag.OPENCODE_CONFIG_DIR ?? Path.config,
|
||||
state: Path.state,
|
||||
tmp: Path.tmp,
|
||||
bin: Path.bin,
|
||||
log: Path.log,
|
||||
...input,
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@effect/language-service",
|
||||
"transform": "@effect/language-service/transform",
|
||||
"namespaceImportPackages": ["effect", "@effect/*"]
|
||||
}
|
||||
]
|
||||
"noUncheckedIndexedAccess": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sentryVitePlugin } from "@sentry/vite-plugin"
|
||||
import { defineConfig } from "electron-vite"
|
||||
import appPlugin from "@opencode-ai/app/vite"
|
||||
import * as fs from "node:fs/promises"
|
||||
@@ -12,6 +13,23 @@ const OPENCODE_SERVER_DIST = "../opencode/dist/node"
|
||||
|
||||
const nodePtyPkg = `@lydell/node-pty-${process.platform}-${process.arch}`
|
||||
|
||||
const sentry =
|
||||
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
|
||||
? sentryVitePlugin({
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
telemetry: false,
|
||||
release: {
|
||||
name: process.env.SENTRY_RELEASE ?? process.env.VITE_SENTRY_RELEASE,
|
||||
},
|
||||
sourcemaps: {
|
||||
assets: "./out/renderer/**",
|
||||
filesToDeleteAfterUpload: "./out/renderer/**/*.map",
|
||||
},
|
||||
})
|
||||
: false
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
define: {
|
||||
@@ -61,13 +79,14 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
plugins: [appPlugin],
|
||||
plugins: [appPlugin, sentry],
|
||||
publicDir: "../../../app/public",
|
||||
root: "src/renderer",
|
||||
define: {
|
||||
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: "src/renderer/index.html",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
@@ -38,6 +38,8 @@
|
||||
"@lydell/node-pty": "catalog:",
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@solidjs/meta": "catalog:",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ServerConnection,
|
||||
useCommand,
|
||||
} from "@opencode-ai/app"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import type { AsyncStorage } from "@solid-primitives/storage"
|
||||
import { MemoryRouter } from "@solidjs/router"
|
||||
import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js"
|
||||
@@ -29,6 +30,25 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(t("error.dev.rootNotFound"))
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE,
|
||||
release: import.meta.env.VITE_SENTRY_RELEASE ?? `desktop-electron@${pkg.version}`,
|
||||
initialScope: {
|
||||
tags: {
|
||||
platform: "desktop-electron",
|
||||
},
|
||||
},
|
||||
integrations: (integrations) => {
|
||||
return integrations.filter(
|
||||
(i) =>
|
||||
i.name !== "Breadcrumbs" && !(import.meta.env.OPENCODE_CHANNEL === "prod" && i.name === "GlobalHandlers"),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
void initI18n()
|
||||
|
||||
const deepLinkEvent = "opencode:deep-link"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -15,6 +15,7 @@
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@sentry/solid": "catalog:",
|
||||
"@solid-primitives/i18n": "2.2.1",
|
||||
"@solid-primitives/storage": "catalog:",
|
||||
"@tauri-apps/api": "^2",
|
||||
@@ -35,6 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "4.0.0",
|
||||
"@sentry/vite-plugin": "catalog:",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
|
||||
9
packages/desktop/src/env.d.ts
vendored
Normal file
9
packages/desktop/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_SENTRY_DSN?: string
|
||||
readonly VITE_SENTRY_ENVIRONMENT?: string
|
||||
readonly VITE_SENTRY_RELEASE?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ServerConnection,
|
||||
useCommand,
|
||||
} from "@opencode-ai/app"
|
||||
import * as Sentry from "@sentry/solid"
|
||||
import type { AsyncStorage } from "@solid-primitives/storage"
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||
import { readImage } from "@tauri-apps/plugin-clipboard-manager"
|
||||
|
||||
@@ -15,9 +15,9 @@ export default defineConfig({
|
||||
// Improves production stack traces
|
||||
keepNames: true,
|
||||
},
|
||||
// build: {
|
||||
// sourcemap: true,
|
||||
// },
|
||||
build: {
|
||||
sourcemap: true,
|
||||
},
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.14.30"
|
||||
version = "1.14.31"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.30/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.31/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.30/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.31/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.30/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.31/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.30/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.31/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.30/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.31/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.30",
|
||||
"version": "1.14.31",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prepare": "effect-language-service patch || true",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"test": "bun test --timeout 30000",
|
||||
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
|
||||
@@ -42,7 +41,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.4",
|
||||
"@effect/language-service": "0.84.2",
|
||||
"@octokit/webhooks-types": "7.6.1",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
|
||||
@@ -198,7 +198,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
|
||||
| `project` | `bridged` | list, current, git init, update |
|
||||
| `file` | `bridged` partial | find text/file/symbol, list/content/status |
|
||||
| `mcp` | `bridged` | status, add, OAuth, connect/disconnect |
|
||||
| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore |
|
||||
| `workspace` | `bridged` | adapter/list/status/create/remove/session-restore |
|
||||
| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose |
|
||||
| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list |
|
||||
| `session` | `bridged` | read, lifecycle, prompt, message/part mutations, revert, permission reply |
|
||||
@@ -290,7 +290,7 @@ This checklist tracks bridge parity only. Checked routes are available through t
|
||||
|
||||
### Workspace Routes
|
||||
|
||||
- [x] `GET /experimental/workspace/adaptor` - list workspace adaptors.
|
||||
- [x] `GET /experimental/workspace/adapter` - list workspace adapters.
|
||||
- [x] `POST /experimental/workspace` - create workspace.
|
||||
- [x] `GET /experimental/workspace` - list workspaces.
|
||||
- [x] `GET /experimental/workspace/status` - workspace status.
|
||||
|
||||
@@ -353,7 +353,7 @@ piecewise.
|
||||
- [ ] `src/cli/cmd/tui/event.ts`
|
||||
- [ ] `src/cli/ui.ts`
|
||||
- [ ] `src/command/index.ts`
|
||||
- [x] `src/control-plane/adaptors/worktree.ts`
|
||||
- [x] `src/control-plane/adapters/worktree.ts`
|
||||
- [x] `src/control-plane/types.ts`
|
||||
- [x] `src/control-plane/workspace.ts`
|
||||
- [ ] `src/file/index.ts`
|
||||
|
||||
@@ -81,7 +81,11 @@ export const layer = Layer.effect(
|
||||
Effect.fn("Agent.state")(function* (ctx) {
|
||||
const cfg = yield* config.get()
|
||||
const skillDirs = yield* skill.dirs()
|
||||
const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
|
||||
const whitelistedDirs = [
|
||||
Truncate.GLOB,
|
||||
path.join(Global.Path.tmp, "*"),
|
||||
...skillDirs.map((dir) => path.join(dir, "*")),
|
||||
]
|
||||
|
||||
const defaults = Permission.fromConfig({
|
||||
"*": "allow",
|
||||
|
||||
@@ -245,10 +245,7 @@ export const ExportCommand = cmd({
|
||||
output: process.stderr,
|
||||
})
|
||||
|
||||
const sessions = []
|
||||
for await (const session of Session.list()) {
|
||||
sessions.push(session)
|
||||
}
|
||||
const sessions = await AppRuntime.runPromise(Session.Service.use((svc) => svc.list()))
|
||||
|
||||
if (sessions.length === 0) {
|
||||
prompts.log.error("No sessions found", {
|
||||
|
||||
@@ -91,7 +91,9 @@ export const SessionListCommand = cmd({
|
||||
},
|
||||
handler: async (args) => {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const sessions = [...Session.list({ roots: true, limit: args.maxCount })]
|
||||
const sessions = await AppRuntime.runPromise(
|
||||
Session.Service.use((svc) => svc.list({ roots: true, limit: args.maxCount })),
|
||||
)
|
||||
|
||||
if (sessions.length === 0) {
|
||||
return
|
||||
|
||||
@@ -51,7 +51,7 @@ export function createDialogProviderOptions() {
|
||||
}[provider.id],
|
||||
footer: consoleManaged ? sync.data.console_state.activeOrgName : undefined,
|
||||
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
|
||||
gutter: connected && onboarded() ? <text fg={theme.success}>✓</text> : undefined,
|
||||
gutter: connected && onboarded() ? () => <text fg={theme.success}>✓</text> : undefined,
|
||||
async onSelect() {
|
||||
if (consoleManaged) return
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ export function DialogSessionList() {
|
||||
value: x.id,
|
||||
category,
|
||||
footer,
|
||||
gutter: isWorking ? <Spinner /> : undefined,
|
||||
gutter: isWorking ? () => <Spinner /> : undefined,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import { errorMessage } from "@/util/error"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { useToast } from "../ui/toast"
|
||||
|
||||
type Adaptor = {
|
||||
type Adapter = {
|
||||
type: string
|
||||
name: string
|
||||
description: string
|
||||
@@ -108,26 +108,26 @@ export function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) =
|
||||
const sdk = useSDK()
|
||||
const toast = useToast()
|
||||
const [creating, setCreating] = createSignal<string>()
|
||||
const [adaptors, setAdaptors] = createSignal<Adaptor[]>()
|
||||
const [adapters, setAdapters] = createSignal<Adapter[]>()
|
||||
|
||||
onMount(() => {
|
||||
dialog.setSize("medium")
|
||||
void (async () => {
|
||||
const dir = sync.path.directory || sdk.directory
|
||||
const url = new URL("/experimental/workspace/adaptor", sdk.url)
|
||||
const url = new URL("/experimental/workspace/adapter", sdk.url)
|
||||
if (dir) url.searchParams.set("directory", dir)
|
||||
const res = await sdk
|
||||
.fetch(url)
|
||||
.then((x) => x.json() as Promise<Adaptor[]>)
|
||||
.then((x) => x.json() as Promise<Adapter[]>)
|
||||
.catch(() => undefined)
|
||||
if (!res) {
|
||||
toast.show({
|
||||
message: "Failed to load workspace adaptors",
|
||||
message: "Failed to load workspace adapters",
|
||||
variant: "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
setAdaptors(res)
|
||||
setAdapters(res)
|
||||
})()
|
||||
})
|
||||
|
||||
@@ -142,13 +142,13 @@ export function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) =
|
||||
},
|
||||
]
|
||||
}
|
||||
const list = adaptors()
|
||||
const list = adapters()
|
||||
if (!list) {
|
||||
return [
|
||||
{
|
||||
title: "Loading workspaces...",
|
||||
value: "loading" as const,
|
||||
description: "Fetching available workspace adaptors",
|
||||
description: "Fetching available workspace adapters",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BoxRenderable, MouseButton, MouseEvent, RGBA, TextAttributes } from "@opentui/core"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { For, createMemo, createSignal, onCleanup, onMount, type JSX } from "solid-js"
|
||||
import { useTheme, tint } from "@tui/context/theme"
|
||||
import * as Sound from "@tui/util/sound"
|
||||
@@ -554,6 +555,7 @@ function buildIdleState(t: number, ctx: LogoContext): IdleState {
|
||||
export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = {}) {
|
||||
const ctx = props.shape ? build(props.shape) : DEFAULT
|
||||
const { theme } = useTheme()
|
||||
const renderer = useRenderer()
|
||||
const [rings, setRings] = createSignal<Ring[]>([])
|
||||
const [hold, setHold] = createSignal<Hold>()
|
||||
const [release, setRelease] = createSignal<Release>()
|
||||
@@ -684,6 +686,7 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } =
|
||||
})
|
||||
|
||||
const idleState = createMemo(() => (props.idle ? buildIdleState(frame().t, ctx) : undefined))
|
||||
const useSubpixelBlocks = () => renderer.capabilities?.rgb === true
|
||||
|
||||
const renderLine = (
|
||||
line: string,
|
||||
@@ -789,7 +792,7 @@ export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } =
|
||||
}
|
||||
|
||||
// Solid █: render as ▀ so the top pixel (fg) and bottom pixel (bg) can carry independent shimmer values
|
||||
if (char === "█") {
|
||||
if (char === "█" && useSubpixelBlocks()) {
|
||||
return (
|
||||
<text
|
||||
fg={shade(inkTop, theme, n + p + e + b)}
|
||||
|
||||
@@ -189,13 +189,20 @@ export function resolveZedDbPath() {
|
||||
path.join(os.homedir(), ".local", "share", "zed", "db", "0-stable", "db.sqlite"),
|
||||
].filter((item): item is string => Boolean(item))
|
||||
|
||||
return candidates.find((item) => Filesystem.stat(item)?.isFile())
|
||||
return candidates.find((item) => isFile(item))
|
||||
}
|
||||
|
||||
function isFile(item: string) {
|
||||
try {
|
||||
return Filesystem.stat(item)?.isFile() === true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function scoreZedWorkspace(workspacePaths: string | null, cwd: string) {
|
||||
return zedWorkspacePaths(workspacePaths).reduce((score, item) => {
|
||||
if (pathContains(item, cwd)) return Math.max(score, 2)
|
||||
if (pathContains(cwd, item)) return Math.max(score, 1)
|
||||
if (pathContains(item, cwd)) return Math.max(score, path.resolve(item).length)
|
||||
return score
|
||||
}, 0)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface DialogSelectOption<T = any> {
|
||||
categoryView?: JSX.Element
|
||||
disabled?: boolean
|
||||
bg?: RGBA
|
||||
gutter?: JSX.Element
|
||||
gutter?: () => JSX.Element
|
||||
margin?: JSX.Element
|
||||
onSelect?: (ctx: DialogContext) => void
|
||||
}
|
||||
@@ -407,7 +407,7 @@ function Option(props: {
|
||||
active?: boolean
|
||||
current?: boolean
|
||||
footer?: JSX.Element | string
|
||||
gutter?: JSX.Element
|
||||
gutter?: () => JSX.Element
|
||||
onMouseOver?: () => void
|
||||
}) {
|
||||
const { theme } = useTheme()
|
||||
@@ -422,7 +422,7 @@ function Option(props: {
|
||||
</Show>
|
||||
<Show when={!props.current && props.gutter}>
|
||||
<box flexShrink={0} marginRight={0}>
|
||||
{props.gutter}
|
||||
{props.gutter?.()}
|
||||
</box>
|
||||
</Show>
|
||||
<text
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import os from "os"
|
||||
import z from "zod"
|
||||
import { mergeDeep, pipe } from "remeda"
|
||||
import { mergeDeep } from "remeda"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import fsNode from "fs/promises"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
@@ -47,8 +47,13 @@ import { Npm } from "@opencode-ai/core/npm"
|
||||
const log = Log.create({ service: "config" })
|
||||
|
||||
// Custom merge function that concatenates array fields instead of replacing them
|
||||
// Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
|
||||
function mergeConfig(target: Info, source: Info): Info {
|
||||
return mergeDeep(target, source) as Info
|
||||
}
|
||||
|
||||
function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
||||
const merged = mergeDeep(target, source)
|
||||
const merged = mergeConfig(target, source)
|
||||
if (target.instructions && source.instructions) {
|
||||
merged.instructions = Array.from(new Set([...target.instructions, ...source.instructions]))
|
||||
}
|
||||
@@ -387,12 +392,10 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const loadGlobal = Effect.fnUntraced(function* () {
|
||||
let result: Info = pipe(
|
||||
{},
|
||||
mergeDeep(yield* loadFile(path.join(Global.Path.config, "config.json"))),
|
||||
mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.json"))),
|
||||
mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
|
||||
)
|
||||
let result: Info = {}
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json")))
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.json")))
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.jsonc")))
|
||||
|
||||
const legacy = path.join(Global.Path.config, "config")
|
||||
if (existsSync(legacy)) {
|
||||
@@ -402,7 +405,7 @@ export const layer = Layer.effect(
|
||||
const { provider, model, ...rest } = mod.default
|
||||
if (provider && model) result.model = `${provider}/${model}`
|
||||
result["$schema"] = "https://opencode.ai/config.json"
|
||||
result = mergeDeep(result, rest)
|
||||
result = mergeConfig(result, rest)
|
||||
await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
||||
await fsNode.unlink(legacy)
|
||||
})
|
||||
@@ -759,18 +762,23 @@ export const layer = Layer.effect(
|
||||
const patch = writableGlobal(config)
|
||||
|
||||
let next: Info
|
||||
let changed: boolean
|
||||
if (!file.endsWith(".jsonc")) {
|
||||
const existing = ConfigParse.effectSchema(Info, ConfigParse.jsonc(before, file), file)
|
||||
const merged = mergeDeep(writable(existing), patch)
|
||||
yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie)
|
||||
const serialized = JSON.stringify(merged, null, 2)
|
||||
changed = serialized !== before
|
||||
if (changed) yield* fs.writeFileString(file, serialized).pipe(Effect.orDie)
|
||||
next = merged
|
||||
} else {
|
||||
const updated = patchJsonc(before, patch)
|
||||
next = ConfigParse.effectSchema(Info, ConfigParse.jsonc(updated, file), file)
|
||||
yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
|
||||
changed = updated !== before
|
||||
if (changed) yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
|
||||
}
|
||||
|
||||
yield* invalidate()
|
||||
// Only tear down running instances if the config actually changed.
|
||||
if (changed) yield* invalidate()
|
||||
return next
|
||||
})
|
||||
|
||||
|
||||
45
packages/opencode/src/control-plane/adapters/index.ts
Normal file
45
packages/opencode/src/control-plane/adapters/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { ProjectID } from "@/project/schema"
|
||||
import type { WorkspaceAdapter, WorkspaceAdapterEntry } from "../types"
|
||||
import { WorktreeAdapter } from "./worktree"
|
||||
|
||||
const BUILTIN: Record<string, WorkspaceAdapter> = {
|
||||
worktree: WorktreeAdapter,
|
||||
}
|
||||
|
||||
const state = new Map<ProjectID, Map<string, WorkspaceAdapter>>()
|
||||
|
||||
export function getAdapter(projectID: ProjectID, type: string): WorkspaceAdapter {
|
||||
const custom = state.get(projectID)?.get(type)
|
||||
if (custom) return custom
|
||||
|
||||
const builtin = BUILTIN[type]
|
||||
if (builtin) return builtin
|
||||
|
||||
throw new Error(`Unknown workspace adapter: ${type}`)
|
||||
}
|
||||
|
||||
export async function listAdapters(projectID: ProjectID): Promise<WorkspaceAdapterEntry[]> {
|
||||
const builtin = await Promise.all(
|
||||
Object.entries(BUILTIN).map(async ([type, adapter]) => {
|
||||
return {
|
||||
type,
|
||||
name: adapter.name,
|
||||
description: adapter.description,
|
||||
}
|
||||
}),
|
||||
)
|
||||
const custom = [...(state.get(projectID)?.entries() ?? [])].map(([type, adapter]) => ({
|
||||
type,
|
||||
name: adapter.name,
|
||||
description: adapter.description,
|
||||
}))
|
||||
return [...builtin, ...custom]
|
||||
}
|
||||
|
||||
// Plugins can be loaded per-project so we need to scope them. If you
|
||||
// want to install a global one pass `ProjectID.global`
|
||||
export function registerAdapter(projectID: ProjectID, type: string, adapter: WorkspaceAdapter) {
|
||||
const adapters = state.get(projectID) ?? new Map<string, WorkspaceAdapter>()
|
||||
adapters.set(type, adapter)
|
||||
state.set(projectID, adapters)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Schema } from "effect"
|
||||
import { type WorkspaceAdaptor, WorkspaceInfo } from "../types"
|
||||
import { type WorkspaceAdapter, WorkspaceInfo } from "../types"
|
||||
|
||||
const WorktreeConfig = Schema.Struct({
|
||||
name: WorkspaceInfo.fields.name,
|
||||
@@ -13,7 +13,7 @@ async function loadWorktree() {
|
||||
return { AppRuntime, Worktree }
|
||||
}
|
||||
|
||||
export const WorktreeAdaptor: WorkspaceAdaptor = {
|
||||
export const WorktreeAdapter: WorkspaceAdapter = {
|
||||
name: "Worktree",
|
||||
description: "Create a git worktree",
|
||||
async configure(info) {
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { ProjectID } from "@/project/schema"
|
||||
import type { WorkspaceAdaptor, WorkspaceAdaptorEntry } from "../types"
|
||||
import { WorktreeAdaptor } from "./worktree"
|
||||
|
||||
const BUILTIN: Record<string, WorkspaceAdaptor> = {
|
||||
worktree: WorktreeAdaptor,
|
||||
}
|
||||
|
||||
const state = new Map<ProjectID, Map<string, WorkspaceAdaptor>>()
|
||||
|
||||
export function getAdaptor(projectID: ProjectID, type: string): WorkspaceAdaptor {
|
||||
const custom = state.get(projectID)?.get(type)
|
||||
if (custom) return custom
|
||||
|
||||
const builtin = BUILTIN[type]
|
||||
if (builtin) return builtin
|
||||
|
||||
throw new Error(`Unknown workspace adaptor: ${type}`)
|
||||
}
|
||||
|
||||
export async function listAdaptors(projectID: ProjectID): Promise<WorkspaceAdaptorEntry[]> {
|
||||
const builtin = await Promise.all(
|
||||
Object.entries(BUILTIN).map(async ([type, adaptor]) => {
|
||||
return {
|
||||
type,
|
||||
name: adaptor.name,
|
||||
description: adaptor.description,
|
||||
}
|
||||
}),
|
||||
)
|
||||
const custom = [...(state.get(projectID)?.entries() ?? [])].map(([type, adaptor]) => ({
|
||||
type,
|
||||
name: adaptor.name,
|
||||
description: adaptor.description,
|
||||
}))
|
||||
return [...builtin, ...custom]
|
||||
}
|
||||
|
||||
// Plugins can be loaded per-project so we need to scope them. If you
|
||||
// want to install a global one pass `ProjectID.global`
|
||||
export function registerAdaptor(projectID: ProjectID, type: string, adaptor: WorkspaceAdaptor) {
|
||||
const adaptors = state.get(projectID) ?? new Map<string, WorkspaceAdaptor>()
|
||||
adaptors.set(type, adaptor)
|
||||
state.set(projectID, adaptors)
|
||||
}
|
||||
@@ -17,12 +17,12 @@ export const WorkspaceInfo = Schema.Struct({
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type WorkspaceInfo = DeepMutable<Schema.Schema.Type<typeof WorkspaceInfo>>
|
||||
|
||||
export const WorkspaceAdaptorEntry = Schema.Struct({
|
||||
export const WorkspaceAdapterEntry = Schema.Struct({
|
||||
type: Schema.String,
|
||||
name: Schema.String,
|
||||
description: Schema.String,
|
||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type WorkspaceAdaptorEntry = Schema.Schema.Type<typeof WorkspaceAdaptorEntry>
|
||||
export type WorkspaceAdapterEntry = Schema.Schema.Type<typeof WorkspaceAdapterEntry>
|
||||
|
||||
export type Target =
|
||||
| {
|
||||
@@ -35,7 +35,7 @@ export type Target =
|
||||
headers?: HeadersInit
|
||||
}
|
||||
|
||||
export type WorkspaceAdaptor = {
|
||||
export type WorkspaceAdapter = {
|
||||
name: string
|
||||
description: string
|
||||
configure(info: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Filesystem } from "@/util/filesystem"
|
||||
import { ProjectID } from "@/project/schema"
|
||||
import { Slug } from "@opencode-ai/core/util/slug"
|
||||
import { WorkspaceTable } from "./workspace.sql"
|
||||
import { getAdaptor } from "./adaptors"
|
||||
import { getAdapter } from "./adapters"
|
||||
import { type WorkspaceInfo, WorkspaceInfo as WorkspaceInfoSchema } from "./types"
|
||||
import { WorkspaceID } from "./schema"
|
||||
import { Session } from "@/session/session"
|
||||
@@ -335,8 +335,8 @@ export const layer = Layer.effect(
|
||||
})
|
||||
|
||||
const syncWorkspaceLoop = Effect.fn("Workspace.syncWorkspaceLoop")(function* (space: Info) {
|
||||
const adaptor = getAdaptor(space.projectID, space.type)
|
||||
const target = yield* Effect.promise(() => Promise.resolve(adaptor.target(space)))
|
||||
const adapter = getAdapter(space.projectID, space.type)
|
||||
const target = yield* Effect.promise(() => Promise.resolve(adapter.target(space)))
|
||||
|
||||
if (target.type === "local") return
|
||||
|
||||
@@ -419,8 +419,8 @@ export const layer = Layer.effect(
|
||||
const startSync = Effect.fn("Workspace.startSync")(function* (space: Info) {
|
||||
if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return
|
||||
|
||||
const adaptor = getAdaptor(space.projectID, space.type)
|
||||
const target = yield* Effect.promise(() => Promise.resolve(adaptor.target(space)))
|
||||
const adapter = getAdapter(space.projectID, space.type)
|
||||
const target = yield* Effect.promise(() => Promise.resolve(adapter.target(space)))
|
||||
|
||||
if (target.type === "local") {
|
||||
setStatus(space.id, (yield* Effect.promise(() => Filesystem.exists(target.directory))) ? "connected" : "error")
|
||||
@@ -458,9 +458,9 @@ export const layer = Layer.effect(
|
||||
|
||||
const create = Effect.fn("Workspace.create")(function* (input: CreateInput) {
|
||||
const id = WorkspaceID.ascending(input.id)
|
||||
const adaptor = getAdaptor(input.projectID, input.type)
|
||||
const adapter = getAdapter(input.projectID, input.type)
|
||||
const config = yield* Effect.promise(() =>
|
||||
Promise.resolve(adaptor.configure({ ...input, id, name: Slug.create(), directory: null })),
|
||||
Promise.resolve(adapter.configure({ ...input, id, name: Slug.create(), directory: null })),
|
||||
)
|
||||
|
||||
const info: Info = {
|
||||
@@ -496,7 +496,7 @@ export const layer = Layer.effect(
|
||||
OTEL_RESOURCE_ATTRIBUTES: process.env.OTEL_RESOURCE_ATTRIBUTES,
|
||||
}
|
||||
|
||||
yield* Effect.promise(() => adaptor.create(config, env))
|
||||
yield* Effect.promise(() => adapter.create(config, env))
|
||||
yield* Effect.all(
|
||||
[
|
||||
waitEvent({
|
||||
@@ -531,8 +531,8 @@ export const layer = Layer.effect(
|
||||
workspaceID: input.workspaceID,
|
||||
})
|
||||
|
||||
const adaptor = getAdaptor(space.projectID, space.type)
|
||||
const target = yield* Effect.promise(() => Promise.resolve(adaptor.target(space)))
|
||||
const adapter = getAdapter(space.projectID, space.type)
|
||||
const target = yield* Effect.promise(() => Promise.resolve(adapter.target(space)))
|
||||
|
||||
yield* sync.run(Session.Event.Updated, {
|
||||
sessionID: input.sessionID,
|
||||
@@ -726,12 +726,12 @@ export const layer = Layer.effect(
|
||||
const info = fromRow(row)
|
||||
yield* Effect.catch(
|
||||
Effect.gen(function* () {
|
||||
const adaptor = getAdaptor(info.projectID, row.type)
|
||||
yield* Effect.tryPromise(() => Promise.resolve(adaptor.remove(info)))
|
||||
const adapter = getAdapter(info.projectID, row.type)
|
||||
yield* Effect.tryPromise(() => Promise.resolve(adapter.remove(info)))
|
||||
}),
|
||||
() =>
|
||||
Effect.sync(() => {
|
||||
log.error("adaptor not available when removing workspace", { type: row.type })
|
||||
log.error("adapter not available when removing workspace", { type: row.type })
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { BusEvent } from "@/bus/bus-event"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Git } from "@/git"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Config } from "@/config/config"
|
||||
import { FileIgnore } from "./ignore"
|
||||
@@ -76,25 +75,27 @@ export const layer = Layer.effect(
|
||||
function* () {
|
||||
if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return
|
||||
|
||||
log.info("init", { directory: Instance.directory })
|
||||
const ctx = yield* InstanceState.context
|
||||
|
||||
log.info("init", { directory: ctx.directory })
|
||||
|
||||
const backend = getBackend()
|
||||
if (!backend) {
|
||||
log.error("watcher backend not supported", { directory: Instance.directory, platform: process.platform })
|
||||
log.error("watcher backend not supported", { directory: ctx.directory, platform: process.platform })
|
||||
return
|
||||
}
|
||||
|
||||
const w = watcher()
|
||||
if (!w) return
|
||||
|
||||
log.info("watcher backend", { directory: Instance.directory, platform: process.platform, backend })
|
||||
log.info("watcher backend", { directory: ctx.directory, platform: process.platform, backend })
|
||||
|
||||
const subs: ParcelWatcher.AsyncSubscription[] = []
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
|
||||
)
|
||||
|
||||
const cb: ParcelWatcher.SubscribeCallback = Instance.bind((err, evts) => {
|
||||
const cb: ParcelWatcher.SubscribeCallback = InstanceState.bind((err, evts) => {
|
||||
if (err) return
|
||||
for (const evt of evts) {
|
||||
if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
|
||||
@@ -122,19 +123,14 @@ export const layer = Layer.effect(
|
||||
const cfgIgnores = cfg.watcher?.ignore ?? []
|
||||
|
||||
if (yield* Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
|
||||
yield* subscribe(Instance.directory, [
|
||||
...FileIgnore.PATTERNS,
|
||||
...cfgIgnores,
|
||||
...protecteds(Instance.directory),
|
||||
])
|
||||
yield* subscribe(ctx.directory, [...FileIgnore.PATTERNS, ...cfgIgnores, ...protecteds(ctx.directory)])
|
||||
}
|
||||
|
||||
if (Instance.project.vcs === "git") {
|
||||
if (ctx.project.vcs === "git") {
|
||||
const result = yield* git.run(["rev-parse", "--git-dir"], {
|
||||
cwd: Instance.project.worktree,
|
||||
cwd: ctx.worktree,
|
||||
})
|
||||
const vcsDir =
|
||||
result.exitCode === 0 ? path.resolve(Instance.project.worktree, result.text().trim()) : undefined
|
||||
const vcsDir = result.exitCode === 0 ? path.resolve(ctx.worktree, result.text().trim()) : undefined
|
||||
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
|
||||
const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter(
|
||||
(entry) => entry !== "HEAD",
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
PluginInput,
|
||||
Plugin as PluginInstance,
|
||||
PluginModule,
|
||||
WorkspaceAdaptor as PluginWorkspaceAdaptor,
|
||||
WorkspaceAdapter as PluginWorkspaceAdapter,
|
||||
} from "@opencode-ai/plugin"
|
||||
import { Config } from "@/config/config"
|
||||
import { Bus } from "../bus"
|
||||
@@ -24,8 +24,8 @@ import { InstanceState } from "@/effect/instance-state"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { PluginLoader } from "./loader"
|
||||
import { parsePluginSpecifier, readPluginId, readV1Plugin, resolvePluginId } from "./shared"
|
||||
import { registerAdaptor } from "@/control-plane/adaptors"
|
||||
import type { WorkspaceAdaptor } from "@/control-plane/types"
|
||||
import { registerAdapter } from "@/control-plane/adapters"
|
||||
import type { WorkspaceAdapter } from "@/control-plane/types"
|
||||
|
||||
const log = Log.create({ service: "plugin" })
|
||||
|
||||
@@ -138,8 +138,8 @@ export const layer = Layer.effect(
|
||||
worktree: ctx.worktree,
|
||||
directory: ctx.directory,
|
||||
experimental_workspace: {
|
||||
register(type: string, adaptor: PluginWorkspaceAdaptor) {
|
||||
registerAdaptor(ctx.project.id, type, adaptor as WorkspaceAdaptor)
|
||||
register(type: string, adapter: PluginWorkspaceAdapter) {
|
||||
registerAdapter(ctx.project.id, type, adapter as WorkspaceAdapter)
|
||||
},
|
||||
},
|
||||
get serverUrl(): URL {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as Project from "./project"
|
||||
import * as Vcs from "./vcs"
|
||||
import { Bus } from "../bus"
|
||||
import { Command } from "../command"
|
||||
import { Instance } from "./instance"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { ShareNext } from "@/share/share-next"
|
||||
@@ -15,7 +15,8 @@ import * as Effect from "effect/Effect"
|
||||
import { Config } from "@/config/config"
|
||||
|
||||
export const InstanceBootstrap = Effect.gen(function* () {
|
||||
Log.Default.info("bootstrapping", { directory: Instance.directory })
|
||||
const ctx = yield* InstanceState.context
|
||||
Log.Default.info("bootstrapping", { directory: ctx.directory })
|
||||
// everything depends on config so eager load it for nice traces
|
||||
yield* Config.Service.use((svc) => svc.get())
|
||||
// Plugin can mutate config so it has to be initialized before anything else.
|
||||
@@ -32,10 +33,11 @@ export const InstanceBootstrap = Effect.gen(function* () {
|
||||
].map((s) => Effect.forkDetach(s.use((i) => i.init()))),
|
||||
).pipe(Effect.withSpan("InstanceBootstrap.init"))
|
||||
|
||||
const projectID = ctx.project.id
|
||||
yield* Bus.Service.use((svc) =>
|
||||
svc.subscribeCallback(Command.Event.Executed, async (payload) => {
|
||||
if (payload.properties.name === Command.Default.INIT) {
|
||||
Project.setInitialized(Instance.project.id)
|
||||
Project.setInitialized(projectID)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ import { NodePath } from "@effect/platform-node"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { NonNegativeInt, withStatics } from "@/util/schema"
|
||||
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
|
||||
const log = Log.create({ service: "project" })
|
||||
@@ -24,13 +24,13 @@ const log = Log.create({ service: "project" })
|
||||
const ProjectVcs = Schema.Literal("git")
|
||||
|
||||
const ProjectIcon = Schema.Struct({
|
||||
url: Schema.optional(Schema.String),
|
||||
override: Schema.optional(Schema.String),
|
||||
color: Schema.optional(Schema.String),
|
||||
url: optionalOmitUndefined(Schema.String),
|
||||
override: optionalOmitUndefined(Schema.String),
|
||||
color: optionalOmitUndefined(Schema.String),
|
||||
})
|
||||
|
||||
const ProjectCommands = Schema.Struct({
|
||||
start: Schema.optional(
|
||||
start: optionalOmitUndefined(
|
||||
Schema.String.annotate({ description: "Startup script to run when creating a new workspace (worktree)" }),
|
||||
),
|
||||
})
|
||||
@@ -38,16 +38,16 @@ const ProjectCommands = Schema.Struct({
|
||||
const ProjectTime = Schema.Struct({
|
||||
created: NonNegativeInt,
|
||||
updated: NonNegativeInt,
|
||||
initialized: Schema.optional(NonNegativeInt),
|
||||
initialized: optionalOmitUndefined(NonNegativeInt),
|
||||
})
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
id: ProjectID,
|
||||
worktree: Schema.String,
|
||||
vcs: Schema.optional(ProjectVcs),
|
||||
name: Schema.optional(Schema.String),
|
||||
icon: Schema.optional(ProjectIcon),
|
||||
commands: Schema.optional(ProjectCommands),
|
||||
vcs: optionalOmitUndefined(ProjectVcs),
|
||||
name: optionalOmitUndefined(Schema.String),
|
||||
icon: optionalOmitUndefined(ProjectIcon),
|
||||
commands: optionalOmitUndefined(ProjectCommands),
|
||||
time: ProjectTime,
|
||||
sandboxes: Schema.Array(Schema.String),
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Auth } from "@/auth"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { namedSchemaError } from "@/util/named-schema-error"
|
||||
import { withStatics } from "@/util/schema"
|
||||
import { optionalOmitUndefined, withStatics } from "@/util/schema"
|
||||
import { Plugin } from "../plugin"
|
||||
import { ProviderID } from "./schema"
|
||||
import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect"
|
||||
@@ -18,14 +18,14 @@ const TextPrompt = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
key: Schema.String,
|
||||
message: Schema.String,
|
||||
placeholder: Schema.optional(Schema.String),
|
||||
when: Schema.optional(When),
|
||||
placeholder: optionalOmitUndefined(Schema.String),
|
||||
when: optionalOmitUndefined(When),
|
||||
})
|
||||
|
||||
const SelectOption = Schema.Struct({
|
||||
label: Schema.String,
|
||||
value: Schema.String,
|
||||
hint: Schema.optional(Schema.String),
|
||||
hint: optionalOmitUndefined(Schema.String),
|
||||
})
|
||||
|
||||
const SelectPrompt = Schema.Struct({
|
||||
@@ -33,7 +33,7 @@ const SelectPrompt = Schema.Struct({
|
||||
key: Schema.String,
|
||||
message: Schema.String,
|
||||
options: Schema.Array(SelectOption),
|
||||
when: Schema.optional(When),
|
||||
when: optionalOmitUndefined(When),
|
||||
})
|
||||
|
||||
const Prompt = Schema.Union([TextPrompt, SelectPrompt])
|
||||
@@ -41,7 +41,7 @@ const Prompt = Schema.Union([TextPrompt, SelectPrompt])
|
||||
export class Method extends Schema.Class<Method>("ProviderAuthMethod")({
|
||||
type: Schema.Literals(["oauth", "api"]),
|
||||
label: Schema.String,
|
||||
prompts: Schema.optional(Schema.Array(Prompt)),
|
||||
prompts: optionalOmitUndefined(Schema.Array(Prompt)),
|
||||
}) {
|
||||
static readonly zod = zod(this)
|
||||
}
|
||||
@@ -135,23 +135,25 @@ export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> =
|
||||
item.methods.map((method) => ({
|
||||
type: method.type,
|
||||
label: method.label,
|
||||
prompts: method.prompts?.map((prompt) => {
|
||||
if (prompt.type === "select") {
|
||||
...(method.prompts && {
|
||||
prompts: method.prompts.map((prompt) => {
|
||||
if (prompt.type === "select") {
|
||||
return {
|
||||
type: "select" as const,
|
||||
key: prompt.key,
|
||||
message: prompt.message,
|
||||
options: prompt.options,
|
||||
...(prompt.when && { when: prompt.when }),
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "select" as const,
|
||||
type: "text" as const,
|
||||
key: prompt.key,
|
||||
message: prompt.message,
|
||||
options: prompt.options,
|
||||
when: prompt.when,
|
||||
...(prompt.placeholder && { placeholder: prompt.placeholder }),
|
||||
...(prompt.when && { when: prompt.when }),
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "text" as const,
|
||||
key: prompt.key,
|
||||
message: prompt.message,
|
||||
placeholder: prompt.placeholder,
|
||||
when: prompt.when,
|
||||
}
|
||||
}),
|
||||
}),
|
||||
})),
|
||||
),
|
||||
|
||||
@@ -24,7 +24,7 @@ import { EffectBridge } from "@/effect/bridge"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { withStatics } from "@/util/schema"
|
||||
import { optionalOmitUndefined, withStatics } from "@/util/schema"
|
||||
|
||||
import * as ProviderTransform from "./transform"
|
||||
import { ModelID, ProviderID } from "./schema"
|
||||
@@ -875,7 +875,7 @@ const ProviderCost = Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
cache: ProviderCacheCost,
|
||||
experimentalOver200K: Schema.optional(
|
||||
experimentalOver200K: optionalOmitUndefined(
|
||||
Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
@@ -886,7 +886,7 @@ const ProviderCost = Schema.Struct({
|
||||
|
||||
const ProviderLimit = Schema.Struct({
|
||||
context: Schema.Finite,
|
||||
input: Schema.optional(Schema.Finite),
|
||||
input: optionalOmitUndefined(Schema.Finite),
|
||||
output: Schema.Finite,
|
||||
})
|
||||
|
||||
@@ -895,7 +895,7 @@ export const Model = Schema.Struct({
|
||||
providerID: ProviderID,
|
||||
api: ProviderApiInfo,
|
||||
name: Schema.String,
|
||||
family: Schema.optional(Schema.String),
|
||||
family: optionalOmitUndefined(Schema.String),
|
||||
capabilities: ProviderCapabilities,
|
||||
cost: ProviderCost,
|
||||
limit: ProviderLimit,
|
||||
@@ -903,7 +903,7 @@ export const Model = Schema.Struct({
|
||||
options: Schema.Record(Schema.String, Schema.Any),
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
release_date: Schema.String,
|
||||
variants: Schema.optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
|
||||
variants: optionalOmitUndefined(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
|
||||
})
|
||||
.annotate({ identifier: "Model" })
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
@@ -914,7 +914,7 @@ export const Info = Schema.Struct({
|
||||
name: Schema.String,
|
||||
source: Schema.Literals(["env", "config", "custom", "api"]),
|
||||
env: Schema.Array(Schema.String),
|
||||
key: Schema.optional(Schema.String),
|
||||
key: optionalOmitUndefined(Schema.String),
|
||||
options: Schema.Record(Schema.String, Schema.Any),
|
||||
models: Schema.Record(Schema.String, Model),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const opencodeOrigin = /^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/
|
||||
|
||||
export function isAllowedCorsOrigin(input: string | undefined, opts?: { cors?: string[] }) {
|
||||
export type CorsOptions = { readonly cors?: ReadonlyArray<string> }
|
||||
|
||||
export function isAllowedCorsOrigin(input: string | undefined, opts?: CorsOptions) {
|
||||
if (!input) return true
|
||||
if (input.startsWith("http://localhost:")) return true
|
||||
if (input.startsWith("http://127.0.0.1:")) return true
|
||||
|
||||
@@ -11,7 +11,7 @@ import { basicAuth } from "hono/basic-auth"
|
||||
import { cors } from "hono/cors"
|
||||
import { compress } from "hono/compress"
|
||||
import * as ServerBackend from "./backend"
|
||||
import { isAllowedCorsOrigin } from "./cors"
|
||||
import { isAllowedCorsOrigin, type CorsOptions } from "./cors"
|
||||
|
||||
const log = Log.create({ service: "server" })
|
||||
|
||||
@@ -67,7 +67,7 @@ export function LoggerMiddleware(backendAttributes: ServerBackend.Attributes): M
|
||||
}
|
||||
}
|
||||
|
||||
export function CorsMiddleware(opts?: { cors?: string[] }): MiddlewareHandler {
|
||||
export function CorsMiddleware(opts?: CorsOptions): MiddlewareHandler {
|
||||
return cors({
|
||||
maxAge: 86_400,
|
||||
origin(input) {
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Hono } from "hono"
|
||||
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||
import z from "zod"
|
||||
import { Effect } from "effect"
|
||||
import { listAdaptors } from "@/control-plane/adaptors"
|
||||
import { listAdapters } from "@/control-plane/adapters"
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { WorkspaceAdaptorEntry } from "@/control-plane/types"
|
||||
import { WorkspaceAdapterEntry } from "@/control-plane/types"
|
||||
import { zodObject } from "@/util/effect-zod"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { errors } from "../../error"
|
||||
@@ -18,24 +18,24 @@ const log = Log.create({ service: "server.workspace" })
|
||||
export const WorkspaceRoutes = lazy(() =>
|
||||
new Hono()
|
||||
.get(
|
||||
"/adaptor",
|
||||
"/adapter",
|
||||
describeRoute({
|
||||
summary: "List workspace adaptors",
|
||||
description: "List all available workspace adaptors for the current project.",
|
||||
operationId: "experimental.workspace.adaptor.list",
|
||||
summary: "List workspace adapters",
|
||||
description: "List all available workspace adapters for the current project.",
|
||||
operationId: "experimental.workspace.adapter.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Workspace adaptors",
|
||||
description: "Workspace adapters",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.array(zodObject(WorkspaceAdaptorEntry))),
|
||||
schema: resolver(z.array(zodObject(WorkspaceAdapterEntry))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(await listAdaptors(Instance.project.id))
|
||||
return c.json(await listAdapters(Instance.project.id))
|
||||
},
|
||||
)
|
||||
.post(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
import { WorkspaceAdaptorEntry } from "@/control-plane/types"
|
||||
import { WorkspaceAdapterEntry } from "@/control-plane/types"
|
||||
import { NonNegativeInt } from "@/util/schema"
|
||||
import { Schema, Struct } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
@@ -16,7 +16,7 @@ export const SessionRestoreResponse = Schema.Struct({
|
||||
})
|
||||
|
||||
export const WorkspacePaths = {
|
||||
adaptors: `${root}/adaptor`,
|
||||
adapters: `${root}/adapter`,
|
||||
list: root,
|
||||
status: `${root}/status`,
|
||||
remove: `${root}/:id`,
|
||||
@@ -27,13 +27,13 @@ export const WorkspaceApi = HttpApi.make("workspace")
|
||||
.add(
|
||||
HttpApiGroup.make("workspace")
|
||||
.add(
|
||||
HttpApiEndpoint.get("adaptors", WorkspacePaths.adaptors, {
|
||||
success: described(Schema.Array(WorkspaceAdaptorEntry), "Workspace adaptors"),
|
||||
HttpApiEndpoint.get("adapters", WorkspacePaths.adapters, {
|
||||
success: described(Schema.Array(WorkspaceAdapterEntry), "Workspace adapters"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.workspace.adaptor.list",
|
||||
summary: "List workspace adaptors",
|
||||
description: "List all available workspace adaptors for the current project.",
|
||||
identifier: "experimental.workspace.adapter.list",
|
||||
summary: "List workspace adapters",
|
||||
description: "List all available workspace adapters for the current project.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("list", WorkspacePaths.list, {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Bus } from "@/bus"
|
||||
import { Command } from "@/command"
|
||||
import { Permission } from "@/permission"
|
||||
import { PermissionID } from "@/permission/schema"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { SessionShare } from "@/share/session"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionCompaction } from "@/session/compaction"
|
||||
@@ -19,7 +18,7 @@ import { Todo } from "@/session/todo"
|
||||
import { MessageID, PartID, SessionID } from "@/session/schema"
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Cause, Effect, Schema } from "effect"
|
||||
import { Cause, Effect, Schema, Scope } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiBuilder, HttpApiError, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
@@ -61,22 +60,18 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
const todoSvc = yield* Todo.Service
|
||||
const summary = yield* SessionSummary.Service
|
||||
const bus = yield* Bus.Service
|
||||
const scope = yield* Scope.Scope
|
||||
|
||||
const list = Effect.fn("SessionHttpApi.list")(function* (ctx: { query: typeof ListQuery.Type }) {
|
||||
const instance = yield* InstanceState.context
|
||||
return Instance.restore(instance, () =>
|
||||
Array.from(
|
||||
Session.list({
|
||||
directory: ctx.query.scope === "project" ? undefined : ctx.query.directory,
|
||||
scope: ctx.query.scope,
|
||||
path: ctx.query.path,
|
||||
roots: ctx.query.roots,
|
||||
start: ctx.query.start,
|
||||
search: ctx.query.search,
|
||||
limit: ctx.query.limit,
|
||||
}),
|
||||
),
|
||||
)
|
||||
return yield* session.list({
|
||||
directory: ctx.query.scope === "project" ? undefined : ctx.query.directory,
|
||||
scope: ctx.query.scope,
|
||||
path: ctx.query.path,
|
||||
roots: ctx.query.roots,
|
||||
start: ctx.query.start,
|
||||
search: ctx.query.search,
|
||||
limit: ctx.query.limit,
|
||||
})
|
||||
})
|
||||
|
||||
const status = Effect.fn("SessionHttpApi.status")(function* () {
|
||||
@@ -259,15 +254,16 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
params: { sessionID: SessionID }
|
||||
payload: typeof PromptPayload.Type
|
||||
}) {
|
||||
const bridge = yield* EffectBridge.make()
|
||||
const instance = yield* InstanceState.context
|
||||
const workspace = yield* InstanceState.workspaceID
|
||||
return HttpServerResponse.stream(
|
||||
Stream.fromEffect(
|
||||
bridge.run(
|
||||
promptSvc.prompt({
|
||||
promptSvc
|
||||
.prompt({
|
||||
...ctx.payload,
|
||||
sessionID: ctx.params.sessionID,
|
||||
}),
|
||||
),
|
||||
})
|
||||
.pipe(Effect.provideService(InstanceRef, instance), Effect.provideService(WorkspaceRef, workspace)),
|
||||
).pipe(
|
||||
Stream.map((message) => JSON.stringify(message)),
|
||||
Stream.encodeText,
|
||||
@@ -280,22 +276,18 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
params: { sessionID: SessionID }
|
||||
payload: typeof PromptPayload.Type
|
||||
}) {
|
||||
const bridge = yield* EffectBridge.make()
|
||||
yield* Effect.sync(() => {
|
||||
bridge.fork(
|
||||
promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe(
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.logError("prompt_async failed", { sessionID: ctx.params.sessionID, cause })
|
||||
yield* bus.publish(Session.Event.Error, {
|
||||
sessionID: ctx.params.sessionID,
|
||||
error: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
yield* promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe(
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.logError("prompt_async failed", { sessionID: ctx.params.sessionID, cause })
|
||||
yield* bus.publish(Session.Event.Error, {
|
||||
sessionID: ctx.params.sessionID,
|
||||
error: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
Effect.forkIn(scope, { startImmediately: true }),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { listAdaptors } from "@/control-plane/adaptors"
|
||||
import { listAdapters } from "@/control-plane/adapters"
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
import * as InstanceState from "@/effect/instance-state"
|
||||
import { Effect } from "effect"
|
||||
@@ -10,9 +10,9 @@ export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspac
|
||||
Effect.gen(function* () {
|
||||
const workspace = yield* Workspace.Service
|
||||
|
||||
const adaptors = Effect.fn("WorkspaceHttpApi.adaptors")(function* () {
|
||||
const adapters = Effect.fn("WorkspaceHttpApi.adapters")(function* () {
|
||||
const instance = yield* InstanceState.context
|
||||
return yield* Effect.promise(() => listAdaptors(instance.project.id))
|
||||
return yield* Effect.promise(() => listAdapters(instance.project.id))
|
||||
})
|
||||
|
||||
const list = Effect.fn("WorkspaceHttpApi.list")(function* () {
|
||||
@@ -51,7 +51,7 @@ export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspac
|
||||
})
|
||||
|
||||
return handlers
|
||||
.handle("adaptors", adaptors)
|
||||
.handle("adapters", adapters)
|
||||
.handle("list", list)
|
||||
.handle("create", create)
|
||||
.handle("status", status)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAdaptor } from "@/control-plane/adaptors"
|
||||
import { getAdapter } from "@/control-plane/adapters"
|
||||
import { WorkspaceID } from "@/control-plane/schema"
|
||||
import type { Target } from "@/control-plane/types"
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
@@ -89,8 +89,8 @@ function missingWorkspaceResponse(id: WorkspaceID): HttpServerResponse.HttpServe
|
||||
|
||||
function resolveTarget(workspace: Workspace.Info): Effect.Effect<Target> {
|
||||
return Effect.gen(function* () {
|
||||
const adaptor = yield* Effect.sync(() => getAdaptor(workspace.projectID, workspace.type))
|
||||
return yield* Effect.promise(() => Promise.resolve(adaptor.target(workspace)))
|
||||
const adapter = yield* Effect.sync(() => getAdapter(workspace.projectID, workspace.type))
|
||||
return yield* Effect.promise(() => Promise.resolve(adapter.target(workspace)))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import { lazy } from "@/util/lazy"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
import { Worktree } from "@/worktree"
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
import { isAllowedCorsOrigin } from "@/server/cors"
|
||||
import { isAllowedCorsOrigin, type CorsOptions } from "@/server/cors"
|
||||
import { serveUIEffect } from "@/server/routes/ui"
|
||||
import { InstanceHttpApi, RootHttpApi } from "./api"
|
||||
import { ServerAuthConfig, authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
|
||||
@@ -77,13 +77,14 @@ const runtime = HttpRouter.middleware()(
|
||||
),
|
||||
).layer
|
||||
|
||||
const cors = HttpRouter.middleware(
|
||||
HttpMiddleware.cors({
|
||||
allowedOrigins: isAllowedCorsOrigin,
|
||||
maxAge: 86_400,
|
||||
}),
|
||||
{ global: true },
|
||||
)
|
||||
const cors = (corsOptions?: CorsOptions) =>
|
||||
HttpRouter.middleware(
|
||||
HttpMiddleware.cors({
|
||||
allowedOrigins: (origin) => isAllowedCorsOrigin(origin, corsOptions),
|
||||
maxAge: 86_400,
|
||||
}),
|
||||
{ global: true },
|
||||
)
|
||||
|
||||
const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(Layer.provide([controlHandlers, globalHandlers]))
|
||||
const instanceRouterLayer = authorizationRouterMiddleware
|
||||
@@ -130,55 +131,68 @@ const uiRoute = HttpRouter.use((router) =>
|
||||
}),
|
||||
).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))))
|
||||
|
||||
export const routes = Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
||||
Layer.provide([
|
||||
cors,
|
||||
runtime,
|
||||
Account.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Auth.defaultLayer,
|
||||
Command.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
File.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Installation.defaultLayer,
|
||||
MCP.defaultLayer,
|
||||
Permission.defaultLayer,
|
||||
Project.defaultLayer,
|
||||
ProviderAuth.defaultLayer,
|
||||
Provider.defaultLayer,
|
||||
Pty.defaultLayer,
|
||||
Question.defaultLayer,
|
||||
Ripgrep.defaultLayer,
|
||||
Session.defaultLayer,
|
||||
SessionCompaction.defaultLayer,
|
||||
SessionPrompt.defaultLayer,
|
||||
SessionRevert.defaultLayer,
|
||||
SessionShare.defaultLayer,
|
||||
SessionRunState.defaultLayer,
|
||||
SessionStatus.defaultLayer,
|
||||
SessionSummary.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
Skill.defaultLayer,
|
||||
Todo.defaultLayer,
|
||||
ToolRegistry.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
Workspace.defaultLayer,
|
||||
Worktree.defaultLayer,
|
||||
Bus.layer,
|
||||
AppFileSystem.defaultLayer,
|
||||
FetchHttpClient.layer,
|
||||
HttpServer.layerServices,
|
||||
]),
|
||||
Layer.provideMerge(Observability.layer),
|
||||
)
|
||||
export function createRoutes(corsOptions?: CorsOptions) {
|
||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
|
||||
Layer.provide([
|
||||
cors(corsOptions),
|
||||
runtime,
|
||||
Account.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Auth.defaultLayer,
|
||||
Command.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
File.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Installation.defaultLayer,
|
||||
MCP.defaultLayer,
|
||||
Permission.defaultLayer,
|
||||
Project.defaultLayer,
|
||||
ProviderAuth.defaultLayer,
|
||||
Provider.defaultLayer,
|
||||
Pty.defaultLayer,
|
||||
Question.defaultLayer,
|
||||
Ripgrep.defaultLayer,
|
||||
Session.defaultLayer,
|
||||
SessionCompaction.defaultLayer,
|
||||
SessionPrompt.defaultLayer,
|
||||
SessionRevert.defaultLayer,
|
||||
SessionShare.defaultLayer,
|
||||
SessionRunState.defaultLayer,
|
||||
SessionStatus.defaultLayer,
|
||||
SessionSummary.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
Skill.defaultLayer,
|
||||
Todo.defaultLayer,
|
||||
ToolRegistry.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
Workspace.defaultLayer,
|
||||
Worktree.defaultLayer,
|
||||
Bus.layer,
|
||||
AppFileSystem.defaultLayer,
|
||||
FetchHttpClient.layer,
|
||||
HttpServer.layerServices,
|
||||
]),
|
||||
Layer.provideMerge(Observability.layer),
|
||||
)
|
||||
}
|
||||
|
||||
export const webHandler = lazy(() =>
|
||||
export const routes = createRoutes()
|
||||
|
||||
const defaultWebHandler = lazy(() =>
|
||||
HttpRouter.toWebHandler(routes, {
|
||||
memoMap,
|
||||
middleware: disposeMiddleware,
|
||||
}),
|
||||
)
|
||||
|
||||
export function webHandler(corsOptions?: CorsOptions) {
|
||||
if (!corsOptions?.cors?.length) return defaultWebHandler()
|
||||
return HttpRouter.toWebHandler(createRoutes(corsOptions), {
|
||||
// Server-level CORS options are dynamic; don't reuse the default route layer memoized without them.
|
||||
memoMap: Layer.makeMemoMapUnsafe(),
|
||||
middleware: disposeMiddleware,
|
||||
})
|
||||
}
|
||||
|
||||
export * as ExperimentalHttpApiServer from "./server"
|
||||
|
||||
@@ -78,18 +78,22 @@ export const SessionRoutes = lazy(() =>
|
||||
),
|
||||
async (c) => {
|
||||
const query = c.req.valid("query")
|
||||
const sessions: Session.Info[] = []
|
||||
for await (const session of Session.list({
|
||||
directory: query.scope === "project" ? undefined : query.directory,
|
||||
path: query.path,
|
||||
roots: queryBoolean(query.roots),
|
||||
start: query.start,
|
||||
search: query.search,
|
||||
limit: query.limit,
|
||||
})) {
|
||||
sessions.push(session)
|
||||
}
|
||||
return c.json(sessions)
|
||||
return c.json(
|
||||
await runRequest(
|
||||
"SessionRoutes.list",
|
||||
c,
|
||||
Session.Service.use((svc) =>
|
||||
svc.list({
|
||||
directory: query.scope === "project" ? undefined : query.directory,
|
||||
path: query.path,
|
||||
roots: queryBoolean(query.roots),
|
||||
start: query.start,
|
||||
search: query.search,
|
||||
limit: query.limit,
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
|
||||
@@ -18,6 +18,7 @@ import { InstanceMiddleware } from "./routes/instance/middleware"
|
||||
import { WorkspaceRoutes } from "./routes/control/workspace"
|
||||
import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server"
|
||||
import * as ServerBackend from "./backend"
|
||||
import type { CorsOptions } from "./cors"
|
||||
|
||||
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||
@@ -38,6 +39,13 @@ type ServerApp = {
|
||||
request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
|
||||
}
|
||||
|
||||
type ListenOptions = CorsOptions & {
|
||||
port: number
|
||||
hostname: string
|
||||
mdns?: boolean
|
||||
mdnsDomain?: string
|
||||
}
|
||||
|
||||
const DefaultHono = lazy(() =>
|
||||
withBackend({ backend: "hono", reason: "stable" }, createHono({}, { backend: "hono", reason: "stable" })),
|
||||
)
|
||||
@@ -54,14 +62,14 @@ export const Default = () => {
|
||||
return selected.backend === "effect-httpapi" ? DefaultHttpApi() : DefaultHono()
|
||||
}
|
||||
|
||||
function create(opts: { cors?: string[] }) {
|
||||
function create(opts: ListenOptions) {
|
||||
const selected = select()
|
||||
return selected.backend === "effect-httpapi"
|
||||
? withBackend(selected, createHttpApi())
|
||||
? withBackend(selected, createHttpApi(opts))
|
||||
: withBackend(selected, createHono(opts, selected))
|
||||
}
|
||||
|
||||
export function Legacy(opts: { cors?: string[] } = {}) {
|
||||
export function Legacy(opts: CorsOptions = {}) {
|
||||
return withBackend({ backend: "hono", reason: "explicit" }, createHono(opts, { backend: "hono", reason: "explicit" }))
|
||||
}
|
||||
|
||||
@@ -74,8 +82,8 @@ function withBackend<T extends { app: ServerApp; runtime: unknown }>(selection:
|
||||
return built
|
||||
}
|
||||
|
||||
function createHttpApi() {
|
||||
const handler = ExperimentalHttpApiServer.webHandler().handler
|
||||
function createHttpApi(corsOptions?: CorsOptions) {
|
||||
const handler = ExperimentalHttpApiServer.webHandler(corsOptions).handler
|
||||
const app: ServerApp = {
|
||||
fetch: (request: Request) => handler(request, ExperimentalHttpApiServer.context),
|
||||
request(input, init) {
|
||||
@@ -88,10 +96,7 @@ function createHttpApi() {
|
||||
}
|
||||
}
|
||||
|
||||
function createHono(
|
||||
opts: { cors?: string[] },
|
||||
selection: ServerBackend.Selection = ServerBackend.force(select(), "hono"),
|
||||
) {
|
||||
function createHono(opts: CorsOptions, selection: ServerBackend.Selection = ServerBackend.force(select(), "hono")) {
|
||||
const backendAttributes = ServerBackend.attributes(selection)
|
||||
const app = new Hono()
|
||||
.onError(ErrorMiddleware)
|
||||
@@ -151,13 +156,7 @@ export async function openapi() {
|
||||
|
||||
export let url: URL
|
||||
|
||||
export async function listen(opts: {
|
||||
port: number
|
||||
hostname: string
|
||||
mdns?: boolean
|
||||
mdnsDomain?: string
|
||||
cors?: string[]
|
||||
}): Promise<Listener> {
|
||||
export async function listen(opts: ListenOptions): Promise<Listener> {
|
||||
const built = create(opts)
|
||||
const server = await built.runtime.listen(opts)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MiddlewareHandler } from "hono"
|
||||
import type { UpgradeWebSocket } from "hono/ws"
|
||||
import { getAdaptor } from "@/control-plane/adaptors"
|
||||
import { getAdapter } from "@/control-plane/adapters"
|
||||
import { WorkspaceID } from "@/control-plane/schema"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
@@ -91,8 +91,8 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware
|
||||
return next()
|
||||
}
|
||||
|
||||
const adaptor = getAdaptor(workspace.projectID, workspace.type)
|
||||
const target = await adaptor.target(workspace)
|
||||
const adapter = getAdapter(workspace.projectID, workspace.type)
|
||||
const target = await adapter.target(workspace)
|
||||
|
||||
if (target.type === "local") {
|
||||
return WorkspaceContext.provide({
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Context, Effect, Layer, Record } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { streamText, wrapLanguageModel, type ModelMessage, type Tool, tool, jsonSchema } from "ai"
|
||||
import { mergeDeep, pipe } from "remeda"
|
||||
import { mergeDeep } from "remeda"
|
||||
import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { Config } from "@/config/config"
|
||||
@@ -29,6 +29,10 @@ const log = Log.create({ service: "llm" })
|
||||
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
|
||||
type Result = Awaited<ReturnType<typeof streamText>>
|
||||
|
||||
// Avoid re-instantiating remeda's deep merge types in this hot LLM path; the runtime behavior is still mergeDeep.
|
||||
const mergeOptions = (target: Record<string, any>, source: Record<string, any> | undefined): Record<string, any> =>
|
||||
mergeDeep(target, source ?? {}) as Record<string, any>
|
||||
|
||||
export type StreamInput = {
|
||||
user: MessageV2.User
|
||||
sessionID: string
|
||||
@@ -134,12 +138,7 @@ const live: Layer.Layer<
|
||||
sessionID: input.sessionID,
|
||||
providerOptions: item.options,
|
||||
})
|
||||
const options: Record<string, any> = pipe(
|
||||
base,
|
||||
mergeDeep(input.model.options),
|
||||
mergeDeep(input.agent.options),
|
||||
mergeDeep(variant),
|
||||
)
|
||||
const options = mergeOptions(mergeOptions(mergeOptions(base, input.model.options), input.agent.options), variant)
|
||||
if (isOpenaiOauth) {
|
||||
options.instructions = system.join("\n")
|
||||
}
|
||||
|
||||
@@ -772,7 +772,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
|
||||
return {
|
||||
type: "content",
|
||||
value: [
|
||||
{ type: "text", text: outputObject.text },
|
||||
...(outputObject.text ? [{ type: "text", text: outputObject.text }] : []),
|
||||
...attachments.map((attachment) => ({
|
||||
type: "media",
|
||||
mediaType: attachment.mime,
|
||||
|
||||
@@ -1443,7 +1443,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
|
||||
const [skills, env, instructions, modelMsgs] = yield* Effect.all([
|
||||
sys.skills(agent),
|
||||
Effect.sync(() => sys.environment(model)),
|
||||
sys.environment(model),
|
||||
instruction.system().pipe(Effect.orDie),
|
||||
MessageV2.toModelMessagesEffect(msgs, model),
|
||||
])
|
||||
|
||||
@@ -26,7 +26,7 @@ import { ProjectTable } from "../project/project.sql"
|
||||
import { Storage } from "@/storage/storage"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { Instance, type InstanceContext } from "../project/instance"
|
||||
import type { InstanceContext } from "../project/instance"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { ProjectID } from "../project/schema"
|
||||
@@ -142,9 +142,9 @@ const Share = Schema.Struct({
|
||||
url: Schema.String,
|
||||
})
|
||||
|
||||
// Legacy HTTP accepted any number here, and persisted data may already contain
|
||||
// negative values. Keep archive timestamps permissive while other clocks stay non-negative.
|
||||
export const ArchivedTimestamp = Schema.Number
|
||||
// Legacy HTTP accepted negative values here. Keep archive timestamps permissive
|
||||
// while excluding non-finite values that cannot round-trip through JSON.
|
||||
export const ArchivedTimestamp = Schema.Finite
|
||||
|
||||
const Time = Schema.Struct({
|
||||
created: NonNegativeInt,
|
||||
@@ -234,6 +234,16 @@ export const MessagesInput = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
limit: Schema.optional(NonNegativeInt),
|
||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type ListInput = {
|
||||
directory?: string
|
||||
scope?: "project"
|
||||
path?: string
|
||||
workspaceID?: WorkspaceID
|
||||
roots?: boolean
|
||||
start?: number
|
||||
search?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
const CreatedEventSchema = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
@@ -390,6 +400,7 @@ export class BusyError extends Error {
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly list: (input?: ListInput) => Effect.Effect<Info[]>
|
||||
readonly create: (input?: {
|
||||
parentID?: SessionID
|
||||
title?: string
|
||||
@@ -498,6 +509,11 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
|
||||
return fromRow(row)
|
||||
})
|
||||
|
||||
const list = Effect.fn("Session.list")(function* (input?: ListInput) {
|
||||
const ctx = yield* InstanceState.context
|
||||
return Array.from(listByProject({ projectID: ctx.project.id, ...(input ?? {}) }))
|
||||
})
|
||||
|
||||
const children = Effect.fn("Session.children")(function* (parentID: SessionID) {
|
||||
const rows = yield* db((d) =>
|
||||
d
|
||||
@@ -731,6 +747,7 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
|
||||
})
|
||||
|
||||
return Service.of({
|
||||
list,
|
||||
create,
|
||||
fork,
|
||||
touch,
|
||||
@@ -762,23 +779,17 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(SyncEvent.defaultLayer),
|
||||
)
|
||||
|
||||
export function* list(input?: {
|
||||
directory?: string
|
||||
scope?: "project"
|
||||
path?: string
|
||||
workspaceID?: WorkspaceID
|
||||
roots?: boolean
|
||||
start?: number
|
||||
search?: string
|
||||
limit?: number
|
||||
}) {
|
||||
const project = Instance.project
|
||||
const conditions = [eq(SessionTable.project_id, project.id)]
|
||||
function* listByProject(
|
||||
input: ListInput & {
|
||||
projectID: ProjectID
|
||||
},
|
||||
) {
|
||||
const conditions = [eq(SessionTable.project_id, input.projectID)]
|
||||
|
||||
if (input?.workspaceID) {
|
||||
if (input.workspaceID) {
|
||||
conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
||||
}
|
||||
if (input?.path !== undefined) {
|
||||
if (input.path !== undefined) {
|
||||
if (input.path) {
|
||||
const conds = [eq(SessionTable.path, input.path), like(SessionTable.path, `${input.path}/%`)]
|
||||
|
||||
@@ -788,22 +799,22 @@ export function* list(input?: {
|
||||
: or(...conds)!,
|
||||
)
|
||||
}
|
||||
} else if (input?.scope !== "project" && !Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
|
||||
if (input?.directory) {
|
||||
} else if (input.scope !== "project" && !Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
|
||||
if (input.directory) {
|
||||
conditions.push(eq(SessionTable.directory, input.directory))
|
||||
}
|
||||
}
|
||||
if (input?.roots) {
|
||||
if (input.roots) {
|
||||
conditions.push(isNull(SessionTable.parent_id))
|
||||
}
|
||||
if (input?.start) {
|
||||
if (input.start) {
|
||||
conditions.push(gte(SessionTable.time_updated, input.start))
|
||||
}
|
||||
if (input?.search) {
|
||||
if (input.search) {
|
||||
conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||
}
|
||||
|
||||
const limit = input?.limit ?? 100
|
||||
const limit = input.limit ?? 100
|
||||
|
||||
const rows = Database.use((db) =>
|
||||
db
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
|
||||
import { Instance } from "../project/instance"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
||||
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
|
||||
import PROMPT_DEFAULT from "./prompt/default.txt"
|
||||
@@ -33,7 +33,7 @@ export function provider(model: Provider.Model) {
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly environment: (model: Provider.Model) => string[]
|
||||
readonly environment: (model: Provider.Model) => Effect.Effect<string[]>
|
||||
readonly skills: (agent: Agent.Info) => Effect.Effect<string | undefined>
|
||||
}
|
||||
|
||||
@@ -45,22 +45,22 @@ export const layer = Layer.effect(
|
||||
const skill = yield* Skill.Service
|
||||
|
||||
return Service.of({
|
||||
environment(model) {
|
||||
const project = Instance.project
|
||||
environment: Effect.fn("SystemPrompt.environment")(function* (model: Provider.Model) {
|
||||
const ctx = yield* InstanceState.context
|
||||
return [
|
||||
[
|
||||
`You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`,
|
||||
`Here is some useful information about the environment you are running in:`,
|
||||
`<env>`,
|
||||
` Working directory: ${Instance.directory}`,
|
||||
` Workspace root folder: ${Instance.worktree}`,
|
||||
` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
|
||||
` Working directory: ${ctx.directory}`,
|
||||
` Workspace root folder: ${ctx.worktree}`,
|
||||
` Is directory a git repo: ${ctx.project.vcs === "git" ? "yes" : "no"}`,
|
||||
` Platform: ${process.platform}`,
|
||||
` Today's date: ${new Date().toDateString()}`,
|
||||
`</env>`,
|
||||
].join("\n"),
|
||||
]
|
||||
},
|
||||
}),
|
||||
|
||||
skills: Effect.fn("SystemPrompt.skills")(function* (agent: Agent.Info) {
|
||||
if (Permission.disabled(["skill"], agent.permission).has("skill")) return
|
||||
|
||||
@@ -48,6 +48,13 @@ type Client = SQLiteBunDatabase
|
||||
|
||||
type Journal = { sql: string; timestamp: number; name: string }[]
|
||||
|
||||
// Drizzle's migrate overloads trigger expensive variance checks here; narrow to the journal overload we actually use.
|
||||
const migrateFromJournal = migrate as unknown as (db: SQLiteBunDatabase, entries: Journal) => void
|
||||
|
||||
function applyMigrations(db: SQLiteBunDatabase, entries: Journal) {
|
||||
migrateFromJournal(db, entries)
|
||||
}
|
||||
|
||||
function time(tag: string) {
|
||||
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(tag)
|
||||
if (!match) return 0
|
||||
@@ -108,7 +115,7 @@ export const Client = lazy(() => {
|
||||
item.sql = "select 1;"
|
||||
}
|
||||
}
|
||||
migrate(db, entries)
|
||||
applyMigrations(db, entries)
|
||||
}
|
||||
|
||||
return db
|
||||
|
||||
@@ -94,7 +94,7 @@ Importantly, **sync events automatically re-publish as bus events**. This makes
|
||||
|
||||
### Event shape
|
||||
|
||||
- The shape of the events are slightly different. A sync event has the `type`, `id`, `seq`, `aggregateID`, and `data` fields. A bus event has the `type` and `properties` fields. `data` and `properties` are largely the same thing. This conversion is automatically handled when the sync system re-published the event throught the bus.
|
||||
- The shape of the events are slightly different. A sync event has the `type`, `id`, `seq`, `aggregateID`, and `data` fields. A bus event has the `type` and `properties` fields. `data` and `properties` are largely the same thing. This conversion is automatically handled when the sync system re-published the event through the bus.
|
||||
|
||||
The reason for this is because sync events need to track more information. I chose not to copy the `properties` naming to more clearly disambiguate the event types.
|
||||
|
||||
@@ -112,9 +112,9 @@ The system install projectors in `server/projectors.js`. It calls `SyncEvent.ini
|
||||
|
||||
This allows you to "reshape" an event from the sync system before it's published to the bus. This should be avoided, but might be necessary for temporary backwards compat.
|
||||
|
||||
The only time we use this is the `session.updated` event. Previously this event contained the entire session object. The sync even only contains the fields updated. We convert the event to contain to full object for backwards compatibility (but ideally we'd remove this).
|
||||
The only time we use this is the `session.updated` event. Previously this event contained the entire session object. The sync event only contains the fields updated. We convert the event to contain the full object for backwards compatibility (but ideally we'd remove this).
|
||||
|
||||
It's very important that types are correct when working with events. Event definitions have a `schema` which carries the defintiion of the event shape (provided by a zod schema, inferred into a TypeScript type). Examples:
|
||||
It's very important that types are correct when working with events. Event definitions have a `schema` which carries the definition of the event shape (provided by a zod schema, inferred into a TypeScript type). Examples:
|
||||
|
||||
```ts
|
||||
// The schema from `Updated` typechecks the object correctly
|
||||
|
||||
@@ -4,9 +4,9 @@ import { eq } from "drizzle-orm"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { Bus as ProjectBus } from "@/bus"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Instance } from "@/project/instance"
|
||||
import type { InstanceContext } from "@/project/instance"
|
||||
import { EventSequenceTable, EventTable } from "./event.sql"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
import type { WorkspaceID } from "@/control-plane/schema"
|
||||
import { EventID } from "./schema"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
|
||||
@@ -14,6 +14,7 @@ import { zodObject } from "@/util/effect-zod"
|
||||
import type { DeepMutable } from "@/util/schema"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
||||
// Keep `Event["data"]` mutable because projectors mutate the persisted shape
|
||||
// when writing to the database. Bus payloads (`Properties`) stay readonly —
|
||||
@@ -47,6 +48,10 @@ export type SerializedEvent<Def extends Definition = Definition> = Event<Def> &
|
||||
|
||||
type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void
|
||||
type ConvertEvent = (type: string, data: Event["data"]) => unknown | Promise<unknown>
|
||||
type PublishContext = {
|
||||
instance?: InstanceContext
|
||||
workspace?: WorkspaceID
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly run: <Def extends Definition>(
|
||||
@@ -87,7 +92,14 @@ export const layer = Layer.effect(Service)(
|
||||
)
|
||||
}
|
||||
|
||||
process(def, event, { publish: !!options?.publish })
|
||||
const publish = !!options?.publish
|
||||
const context = publish
|
||||
? {
|
||||
instance: yield* InstanceState.context,
|
||||
workspace: yield* InstanceState.workspaceID,
|
||||
}
|
||||
: undefined
|
||||
process(def, event, { publish, context })
|
||||
})
|
||||
|
||||
const replayAll: Interface["replayAll"] = Effect.fn("SyncEvent.replayAll")(function* (events, options) {
|
||||
@@ -122,6 +134,12 @@ export const layer = Layer.effect(Service)(
|
||||
}
|
||||
|
||||
const { publish = true } = options || {}
|
||||
const context = publish
|
||||
? {
|
||||
instance: yield* InstanceState.context,
|
||||
workspace: yield* InstanceState.workspaceID,
|
||||
}
|
||||
: undefined
|
||||
|
||||
// Note that this is an "immediate" transaction which is critical.
|
||||
// We need to make sure we can safely read and write with nothing
|
||||
@@ -137,7 +155,7 @@ export const layer = Layer.effect(Service)(
|
||||
const seq = row?.seq != null ? row.seq + 1 : 0
|
||||
|
||||
const event = { id, seq, aggregateID: agg, data }
|
||||
process(def, event, { publish })
|
||||
process(def, event, { publish, context })
|
||||
},
|
||||
{
|
||||
behavior: "immediate",
|
||||
@@ -242,7 +260,11 @@ export function project<Def extends Definition>(
|
||||
return [def, func as ProjectorFunc]
|
||||
}
|
||||
|
||||
function process<Def extends Definition>(def: Def, event: Event<Def>, options: { publish: boolean }) {
|
||||
function process<Def extends Definition>(
|
||||
def: Def,
|
||||
event: Event<Def>,
|
||||
options: { publish: boolean; context?: PublishContext },
|
||||
) {
|
||||
if (projectors == null) {
|
||||
throw new Error("No projectors available. Call `SyncEvent.init` to install projectors")
|
||||
}
|
||||
@@ -281,6 +303,10 @@ function process<Def extends Definition>(def: Def, event: Event<Def>, options: {
|
||||
|
||||
Database.effect(() => {
|
||||
if (options?.publish) {
|
||||
if (!options.context?.instance) {
|
||||
throw new Error("SyncEvent.process: publish requires instance context")
|
||||
}
|
||||
|
||||
const result = convertEvent(def.type, event.data)
|
||||
const publish = (data: unknown) => ProjectBus.publish(def, data as Properties<Def>)
|
||||
if (result instanceof Promise) {
|
||||
@@ -290,9 +316,9 @@ function process<Def extends Definition>(def: Def, event: Event<Def>, options: {
|
||||
}
|
||||
|
||||
GlobalBus.emit("event", {
|
||||
directory: Instance.directory,
|
||||
project: Instance.project.id,
|
||||
workspace: WorkspaceContext.workspaceID,
|
||||
directory: options.context.instance.directory,
|
||||
project: options.context.instance.project.id,
|
||||
workspace: options.context.workspace,
|
||||
payload: {
|
||||
type: "sync",
|
||||
syncEvent: {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { fileURLToPath } from "url"
|
||||
import { Config } from "@/config/config"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Shell } from "@/shell/shell"
|
||||
|
||||
import { BashArity } from "@/permission/arity"
|
||||
@@ -587,6 +588,7 @@ export const BashTool = Tool.define(
|
||||
|
||||
return {
|
||||
description: DESCRIPTION.replaceAll("${directory}", instance.directory)
|
||||
.replaceAll("${tmp}", Global.Path.tmp)
|
||||
.replaceAll("${os}", process.platform)
|
||||
.replaceAll("${shell}", name)
|
||||
.replaceAll("${chaining}", chain)
|
||||
|
||||
@@ -4,6 +4,8 @@ Be aware: OS: ${os}, Shell: ${shell}
|
||||
|
||||
All commands run in the current working directory by default. Use the `workdir` parameter if you need to run a command in a different directory. AVOID using `cd <directory> && <command>` patterns - use `workdir` instead.
|
||||
|
||||
Use `${tmp}` for temporary work outside the workspace. This directory has already been created, already exists, and is pre-approved for external directory access.
|
||||
|
||||
IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
|
||||
|
||||
Before executing the command, please follow these steps:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Database } from "bun:sqlite"
|
||||
import { mkdir, symlink } from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import { expect, test } from "bun:test"
|
||||
import { offsetToPosition, resolveZedSelection } from "../../../src/cli/cmd/tui/context/editor-zed"
|
||||
import { expect, spyOn, test } from "bun:test"
|
||||
import { offsetToPosition, resolveZedDbPath, resolveZedSelection } from "../../../src/cli/cmd/tui/context/editor-zed"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
|
||||
type ZedFixtureOptions = {
|
||||
@@ -66,6 +68,23 @@ test("offsetToPosition converts Zed offsets to 1-based editor positions", () =>
|
||||
})
|
||||
})
|
||||
|
||||
test("resolveZedDbPath skips candidates that cannot be stated", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const loop = path.join(tmp.path, "loop")
|
||||
await symlink(loop, loop)
|
||||
const home = spyOn(os, "homedir").mockImplementation(() => tmp.path)
|
||||
const previous = process.env.OPENCODE_ZED_DB
|
||||
process.env.OPENCODE_ZED_DB = loop
|
||||
|
||||
try {
|
||||
expect(resolveZedDbPath()).toBeUndefined()
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.OPENCODE_ZED_DB
|
||||
else process.env.OPENCODE_ZED_DB = previous
|
||||
home.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
test("resolveZedSelection returns active editor selection", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const fixture = await writeZedFixture(tmp.path)
|
||||
@@ -251,6 +270,71 @@ test("resolveZedSelection returns empty when no workspace matches", async () =>
|
||||
expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "empty" })
|
||||
})
|
||||
|
||||
test("resolveZedSelection matches a Zed workspace that contains the session directory", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const fixture = await writeZedFixture(tmp.path)
|
||||
|
||||
expect(await resolveZedSelection(fixture.dbPath, path.join(tmp.path, "packages", "app"))).toEqual({
|
||||
type: "selection",
|
||||
selection: {
|
||||
filePath: fixture.filePath,
|
||||
source: "zed",
|
||||
ranges: [
|
||||
{
|
||||
text: "two",
|
||||
selection: {
|
||||
start: { line: 2, character: 1 },
|
||||
end: { line: 2, character: 4 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("resolveZedSelection prefers the most specific containing Zed workspace", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const fixture = await writeZedFixture(tmp.path)
|
||||
const child = path.join(tmp.path, "packages")
|
||||
const childFile = path.join(child, "child.ts")
|
||||
await mkdir(child, { recursive: true })
|
||||
await Bun.write(childFile, "child")
|
||||
|
||||
const db = new Database(fixture.dbPath)
|
||||
db.run("insert into workspaces values (2, ?, ?)", [JSON.stringify([child]), "2026-01-01"])
|
||||
db.run("insert into panes values (2, 2, 1)")
|
||||
db.run("insert into items values (2, 2, 2, 1, ?)", ["Editor"])
|
||||
db.run("insert into editors values (2, 2, ?, ?)", [childFile, "child"])
|
||||
db.run("insert into editor_selections values (2, 2, 0, 5)")
|
||||
db.close()
|
||||
|
||||
expect(await resolveZedSelection(fixture.dbPath, path.join(child, "app"))).toEqual({
|
||||
type: "selection",
|
||||
selection: {
|
||||
filePath: childFile,
|
||||
source: "zed",
|
||||
ranges: [
|
||||
{
|
||||
text: "child",
|
||||
selection: {
|
||||
start: { line: 1, character: 1 },
|
||||
end: { line: 1, character: 6 },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("resolveZedSelection ignores a Zed workspace nested inside the session directory", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const child = path.join(tmp.path, "effect-lab")
|
||||
await mkdir(child, { recursive: true })
|
||||
const fixture = await writeZedFixture(child)
|
||||
|
||||
expect(await resolveZedSelection(fixture.dbPath, tmp.path)).toEqual({ type: "empty" })
|
||||
})
|
||||
|
||||
test("resolveZedSelection returns unavailable when a Zed terminal is active", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const fixture = await writeZedFixture(tmp.path, { itemKind: "Terminal", editor: false })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { getAdaptor, registerAdaptor } from "../../src/control-plane/adaptors"
|
||||
import { getAdapter, registerAdapter } from "../../src/control-plane/adapters"
|
||||
import { ProjectID } from "../../src/project/schema"
|
||||
import type { WorkspaceInfo } from "../../src/control-plane/types"
|
||||
|
||||
@@ -15,7 +15,7 @@ function info(projectID: WorkspaceInfo["projectID"], type: string): WorkspaceInf
|
||||
}
|
||||
}
|
||||
|
||||
function adaptor(dir: string) {
|
||||
function adapter(dir: string) {
|
||||
return {
|
||||
name: dir,
|
||||
description: dir,
|
||||
@@ -33,19 +33,19 @@ function adaptor(dir: string) {
|
||||
}
|
||||
}
|
||||
|
||||
describe("control-plane/adaptors", () => {
|
||||
test("isolates custom adaptors by project", async () => {
|
||||
describe("control-plane/adapters", () => {
|
||||
test("isolates custom adapters by project", async () => {
|
||||
const type = `demo-${Math.random().toString(36).slice(2)}`
|
||||
const one = ProjectID.make(`project-${Math.random().toString(36).slice(2)}`)
|
||||
const two = ProjectID.make(`project-${Math.random().toString(36).slice(2)}`)
|
||||
registerAdaptor(one, type, adaptor("/one"))
|
||||
registerAdaptor(two, type, adaptor("/two"))
|
||||
registerAdapter(one, type, adapter("/one"))
|
||||
registerAdapter(two, type, adapter("/two"))
|
||||
|
||||
expect(await (await getAdaptor(one, type)).target(info(one, type))).toEqual({
|
||||
expect(await (await getAdapter(one, type)).target(info(one, type))).toEqual({
|
||||
type: "local",
|
||||
directory: "/one",
|
||||
})
|
||||
expect(await (await getAdaptor(two, type)).target(info(two, type))).toEqual({
|
||||
expect(await (await getAdapter(two, type)).target(info(two, type))).toEqual({
|
||||
type: "local",
|
||||
directory: "/two",
|
||||
})
|
||||
@@ -54,16 +54,16 @@ describe("control-plane/adaptors", () => {
|
||||
test("latest install wins within a project", async () => {
|
||||
const type = `demo-${Math.random().toString(36).slice(2)}`
|
||||
const id = ProjectID.make(`project-${Math.random().toString(36).slice(2)}`)
|
||||
registerAdaptor(id, type, adaptor("/one"))
|
||||
registerAdapter(id, type, adapter("/one"))
|
||||
|
||||
expect(await (await getAdaptor(id, type)).target(info(id, type))).toEqual({
|
||||
expect(await (await getAdapter(id, type)).target(info(id, type))).toEqual({
|
||||
type: "local",
|
||||
directory: "/one",
|
||||
})
|
||||
|
||||
registerAdaptor(id, type, adaptor("/two"))
|
||||
registerAdapter(id, type, adapter("/two"))
|
||||
|
||||
expect(await (await getAdaptor(id, type)).target(info(id, type))).toEqual({
|
||||
expect(await (await getAdapter(id, type)).target(info(id, type))).toEqual({
|
||||
type: "local",
|
||||
directory: "/two",
|
||||
})
|
||||
@@ -23,10 +23,10 @@ import { EventSequenceTable, EventTable } from "@/sync/event.sql"
|
||||
import { resetDatabase } from "../fixture/db"
|
||||
import { provideTmpdirInstance, tmpdir } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { registerAdaptor } from "../../src/control-plane/adaptors"
|
||||
import { registerAdapter } from "../../src/control-plane/adapters"
|
||||
import { WorkspaceID } from "../../src/control-plane/schema"
|
||||
import { WorkspaceTable } from "../../src/control-plane/workspace.sql"
|
||||
import type { Target, WorkspaceAdaptor, WorkspaceInfo } from "../../src/control-plane/types"
|
||||
import type { Target, WorkspaceAdapter, WorkspaceInfo } from "../../src/control-plane/types"
|
||||
import * as WorkspaceOld from "../../src/control-plane/workspace"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
|
||||
@@ -53,8 +53,8 @@ type RecordedCreate = {
|
||||
from?: WorkspaceInfo
|
||||
}
|
||||
|
||||
type RecordedAdaptor = {
|
||||
adaptor: WorkspaceAdaptor
|
||||
type RecordedAdapter = {
|
||||
adapter: WorkspaceAdapter
|
||||
calls: {
|
||||
configure: WorkspaceInfo[]
|
||||
create: RecordedCreate[]
|
||||
@@ -165,13 +165,13 @@ function eventuallyEffect(effect: Effect.Effect<void>, timeout = 1500) {
|
||||
})
|
||||
}
|
||||
|
||||
function recordedAdaptor(input: {
|
||||
function recordedAdapter(input: {
|
||||
target: (info: WorkspaceInfo) => Target | Promise<Target>
|
||||
configure?: (info: WorkspaceInfo) => WorkspaceInfo | Promise<WorkspaceInfo>
|
||||
create?: (info: WorkspaceInfo, env: Record<string, string | undefined>, from?: WorkspaceInfo) => Promise<void>
|
||||
remove?: (info: WorkspaceInfo) => Promise<void>
|
||||
}): RecordedAdaptor {
|
||||
const calls: RecordedAdaptor["calls"] = {
|
||||
}): RecordedAdapter {
|
||||
const calls: RecordedAdapter["calls"] = {
|
||||
configure: [],
|
||||
create: [],
|
||||
remove: [],
|
||||
@@ -180,7 +180,7 @@ function recordedAdaptor(input: {
|
||||
|
||||
return {
|
||||
calls,
|
||||
adaptor: {
|
||||
adapter: {
|
||||
name: "recorded",
|
||||
description: "recorded",
|
||||
configure(info) {
|
||||
@@ -207,8 +207,8 @@ function recordedAdaptor(input: {
|
||||
}
|
||||
}
|
||||
|
||||
function localAdaptor(dir: string, input?: { createDir?: boolean; remove?: (info: WorkspaceInfo) => Promise<void> }) {
|
||||
return recordedAdaptor({
|
||||
function localAdapter(dir: string, input?: { createDir?: boolean; remove?: (info: WorkspaceInfo) => Promise<void> }) {
|
||||
return recordedAdapter({
|
||||
configure(info) {
|
||||
return { ...info, directory: dir }
|
||||
},
|
||||
@@ -223,8 +223,8 @@ function localAdaptor(dir: string, input?: { createDir?: boolean; remove?: (info
|
||||
})
|
||||
}
|
||||
|
||||
function remoteAdaptor(url: string, input?: { directory?: string | null; headers?: HeadersInit }) {
|
||||
return recordedAdaptor({
|
||||
function remoteAdapter(url: string, input?: { directory?: string | null; headers?: HeadersInit }) {
|
||||
return recordedAdapter({
|
||||
configure(info) {
|
||||
return { ...info, directory: input?.directory ?? info.directory }
|
||||
},
|
||||
@@ -429,7 +429,7 @@ describe("workspace-old CRUD", () => {
|
||||
const workspaceID = WorkspaceID.ascending("wrk_create_local")
|
||||
const type = unique("create-local")
|
||||
const targetDir = path.join(dir, "created-local")
|
||||
const recorded = recordedAdaptor({
|
||||
const recorded = recordedAdapter({
|
||||
configure(info) {
|
||||
return {
|
||||
...info,
|
||||
@@ -446,7 +446,7 @@ describe("workspace-old CRUD", () => {
|
||||
return { type: "local", directory: targetDir }
|
||||
},
|
||||
})
|
||||
registerAdaptor(Instance.project.id, type, recorded.adaptor)
|
||||
registerAdapter(Instance.project.id, type, recorded.adapter)
|
||||
|
||||
const info = await createWorkspace({
|
||||
id: workspaceID,
|
||||
@@ -489,17 +489,17 @@ describe("workspace-old CRUD", () => {
|
||||
test("create propagates configure failures and does not insert a workspace", async () => {
|
||||
await withInstance(async () => {
|
||||
const type = unique("configure-failure")
|
||||
registerAdaptor(
|
||||
registerAdapter(
|
||||
Instance.project.id,
|
||||
type,
|
||||
recordedAdaptor({
|
||||
recordedAdapter({
|
||||
configure() {
|
||||
throw new Error("configure exploded")
|
||||
},
|
||||
target() {
|
||||
return { type: "local", directory: "/unused" }
|
||||
},
|
||||
}).adaptor,
|
||||
}).adapter,
|
||||
)
|
||||
|
||||
await expect(
|
||||
@@ -509,10 +509,10 @@ describe("workspace-old CRUD", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("create leaves the inserted row when adaptor create fails", async () => {
|
||||
test("create leaves the inserted row when adapter create fails", async () => {
|
||||
await withInstance(async () => {
|
||||
const type = unique("create-failure")
|
||||
const recorded = recordedAdaptor({
|
||||
const recorded = recordedAdapter({
|
||||
async create() {
|
||||
throw new Error("create exploded")
|
||||
},
|
||||
@@ -520,7 +520,7 @@ describe("workspace-old CRUD", () => {
|
||||
return { type: "local", directory: "/unused" }
|
||||
},
|
||||
})
|
||||
registerAdaptor(Instance.project.id, type, recorded.adaptor)
|
||||
registerAdapter(Instance.project.id, type, recorded.adapter)
|
||||
|
||||
await expect(
|
||||
createWorkspace({ type, branch: "branch", projectID: Instance.project.id, extra: { x: 1 } }),
|
||||
@@ -538,8 +538,8 @@ describe("workspace-old CRUD", () => {
|
||||
await withInstance(async (dir) => {
|
||||
const type = unique("local-error")
|
||||
const missing = path.join(dir, "missing-local-target")
|
||||
const recorded = localAdaptor(missing, { createDir: false })
|
||||
registerAdaptor(Instance.project.id, type, recorded.adaptor)
|
||||
const recorded = localAdapter(missing, { createDir: false })
|
||||
registerAdapter(Instance.project.id, type, recorded.adapter)
|
||||
|
||||
const info = await createWorkspace({ type, branch: null, projectID: Instance.project.id, extra: null })
|
||||
|
||||
@@ -576,8 +576,8 @@ describe("workspace-old CRUD", () => {
|
||||
Effect.gen(function* () {
|
||||
const workspace = yield* WorkspaceOld.Service
|
||||
const type = unique("remote-create")
|
||||
const recorded = remoteAdaptor(`${url}/base/?ignored=1#hash`, { directory: dir })
|
||||
registerAdaptor(Instance.project.id, type, recorded.adaptor)
|
||||
const recorded = remoteAdapter(`${url}/base/?ignored=1#hash`, { directory: dir })
|
||||
registerAdapter(Instance.project.id, type, recorded.adapter)
|
||||
|
||||
const info = yield* workspace.create({ type, branch: null, projectID: Instance.project.id, extra: null })
|
||||
|
||||
@@ -603,11 +603,11 @@ describe("workspace-old CRUD", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("remove deletes the workspace, associated sessions, adaptor resources, and status", async () => {
|
||||
test("remove deletes the workspace, associated sessions, adapter resources, and status", async () => {
|
||||
await withInstance(async (dir) => {
|
||||
const type = unique("remove-local")
|
||||
const recorded = localAdaptor(path.join(dir, "remove-local"))
|
||||
registerAdaptor(Instance.project.id, type, recorded.adaptor)
|
||||
const recorded = localAdapter(path.join(dir, "remove-local"))
|
||||
registerAdapter(Instance.project.id, type, recorded.adapter)
|
||||
const info = await createWorkspace({ type, branch: null, projectID: Instance.project.id, extra: null })
|
||||
const one = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
|
||||
const two = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
|
||||
@@ -628,21 +628,21 @@ describe("workspace-old CRUD", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("remove still deletes the row when the adaptor cannot remove resources", async () => {
|
||||
test("remove still deletes the row when the adapter cannot remove resources", async () => {
|
||||
await withInstance(async () => {
|
||||
const type = unique("remove-throws")
|
||||
const info = workspaceInfo(Instance.project.id, type, { id: WorkspaceID.ascending("wrk_remove_throws") })
|
||||
registerAdaptor(
|
||||
registerAdapter(
|
||||
Instance.project.id,
|
||||
type,
|
||||
recordedAdaptor({
|
||||
recordedAdapter({
|
||||
async remove() {
|
||||
throw new Error("remove exploded")
|
||||
},
|
||||
target() {
|
||||
return { type: "local", directory: "/unused" }
|
||||
},
|
||||
}).adaptor,
|
||||
}).adapter,
|
||||
)
|
||||
insertWorkspace(info)
|
||||
|
||||
@@ -661,7 +661,7 @@ describe("workspace-old sync state", () => {
|
||||
const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))
|
||||
attachSessionToWorkspace(session.id, info.id)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, localAdaptor(path.join(dir, "flag-disabled")).adaptor)
|
||||
registerAdapter(Instance.project.id, type, localAdapter(path.join(dir, "flag-disabled")).adapter)
|
||||
|
||||
startWorkspaceSyncing(Instance.project.id)
|
||||
await delay(25)
|
||||
@@ -682,8 +682,8 @@ describe("workspace-old sync state", () => {
|
||||
await fs.mkdir(withoutSessionDir, { recursive: true })
|
||||
insertWorkspace(withSession)
|
||||
insertWorkspace(withoutSession)
|
||||
registerAdaptor(Instance.project.id, withSessionType, localAdaptor(withSessionDir).adaptor)
|
||||
registerAdaptor(Instance.project.id, withoutSessionType, localAdaptor(withoutSessionDir).adaptor)
|
||||
registerAdapter(Instance.project.id, withSessionType, localAdapter(withSessionDir).adapter)
|
||||
registerAdapter(Instance.project.id, withoutSessionType, localAdapter(withoutSessionDir).adapter)
|
||||
attachSessionToWorkspace(
|
||||
(await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))).id,
|
||||
withSession.id,
|
||||
@@ -707,10 +707,10 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("missing-local")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(
|
||||
registerAdapter(
|
||||
Instance.project.id,
|
||||
type,
|
||||
localAdaptor(path.join(dir, "missing-target"), { createDir: false }).adaptor,
|
||||
localAdapter(path.join(dir, "missing-target"), { createDir: false }).adapter,
|
||||
)
|
||||
attachSessionToWorkspace(
|
||||
(await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))).id,
|
||||
@@ -738,7 +738,7 @@ describe("workspace-old sync state", () => {
|
||||
const target = path.join(dir, "dedupe-local")
|
||||
await fs.mkdir(target, { recursive: true })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, localAdaptor(target).adaptor)
|
||||
registerAdapter(Instance.project.id, type, localAdapter(target).adapter)
|
||||
attachSessionToWorkspace(
|
||||
(await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({})))).id,
|
||||
info.id,
|
||||
@@ -795,7 +795,7 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("remote-start")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/sync`).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/sync`).adapter)
|
||||
attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id)
|
||||
|
||||
yield* workspace.startWorkspaceSyncing(Instance.project.id)
|
||||
@@ -850,7 +850,7 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("remote-connect-fail")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/failed`).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/failed`).adapter)
|
||||
attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id)
|
||||
|
||||
yield* workspace.startWorkspaceSyncing(Instance.project.id)
|
||||
@@ -890,7 +890,7 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("remote-history-fail")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/history-failed`).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/history-failed`).adapter)
|
||||
attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id)
|
||||
|
||||
yield* workspace.startWorkspaceSyncing(Instance.project.id)
|
||||
@@ -947,7 +947,7 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("history-replay")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/history`).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/history`).adapter)
|
||||
const session = yield* sessionSvc.create({ title: "before history" })
|
||||
attachSessionToWorkspace(session.id, info.id)
|
||||
historySessionID = session.id
|
||||
@@ -1014,7 +1014,7 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("sse-forward")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/sse-forward`).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/sse-forward`).adapter)
|
||||
attachSessionToWorkspace((yield* sessionSvc.create({})).id, info.id)
|
||||
|
||||
yield* workspace.startWorkspaceSyncing(Instance.project.id)
|
||||
@@ -1095,7 +1095,7 @@ describe("workspace-old sync state", () => {
|
||||
const type = unique("sse-sync")
|
||||
const info = workspaceInfo(Instance.project.id, type)
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/sse-sync`).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/sse-sync`).adapter)
|
||||
const session = yield* sessionSvc.create({ title: "before sse" })
|
||||
attachSessionToWorkspace(session.id, info.id)
|
||||
sseSessionID = session.id
|
||||
@@ -1232,7 +1232,7 @@ describe("workspace-old sessionRestore", () => {
|
||||
const type = unique("restore-missing-session")
|
||||
const info = workspaceInfo(Instance.project.id, type, { directory: dir })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, localAdaptor(dir).adaptor)
|
||||
registerAdapter(Instance.project.id, type, localAdapter(dir).adapter)
|
||||
|
||||
await expect(
|
||||
restoreWorkspaceSession({ workspaceID: info.id, sessionID: SessionID.descending("ses_missing_restore") }),
|
||||
@@ -1273,13 +1273,13 @@ describe("workspace-old sessionRestore", () => {
|
||||
const type = unique("restore-remote")
|
||||
const info = workspaceInfo(Instance.project.id, type, { directory: dir })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(
|
||||
registerAdapter(
|
||||
Instance.project.id,
|
||||
type,
|
||||
remoteAdaptor(`${url}/restore/?ignored=1#hash`, {
|
||||
remoteAdapter(`${url}/restore/?ignored=1#hash`, {
|
||||
directory: dir,
|
||||
headers: { authorization: "Bearer restore" },
|
||||
}).adaptor,
|
||||
}).adapter,
|
||||
)
|
||||
const session = yield* sessionSvc.create({ title: "restore remote" })
|
||||
replaceSessionEvents(session.id, 24)
|
||||
@@ -1353,7 +1353,7 @@ describe("workspace-old sessionRestore", () => {
|
||||
const type = unique("restore-null-dir")
|
||||
const info = workspaceInfo(Instance.project.id, type, { directory: null })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/null-dir`, { directory: null }).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/null-dir`, { directory: null }).adapter)
|
||||
const session = yield* sessionSvc.create({ title: "null dir" })
|
||||
replaceSessionEvents(session.id, 0)
|
||||
|
||||
@@ -1397,7 +1397,7 @@ describe("workspace-old sessionRestore", () => {
|
||||
const type = unique("restore-remote-fail")
|
||||
const info = workspaceInfo(Instance.project.id, type, { directory: dir })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/fail`, { directory: dir }).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/fail`, { directory: dir }).adapter)
|
||||
const session = yield* sessionSvc.create({ title: "restore fail" })
|
||||
replaceSessionEvents(session.id, 11)
|
||||
|
||||
@@ -1437,7 +1437,7 @@ describe("workspace-old sessionRestore", () => {
|
||||
const type = unique("restore-local")
|
||||
const info = workspaceInfo(Instance.project.id, type, { directory: dir })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, localAdaptor(dir).adaptor)
|
||||
registerAdapter(Instance.project.id, type, localAdapter(dir).adapter)
|
||||
const session = yield* sessionSvc.create({ title: "restore local" })
|
||||
replaceSessionEvents(session.id, 20)
|
||||
|
||||
@@ -1488,7 +1488,7 @@ describe("workspace-old sessionRestore", () => {
|
||||
const type = unique("restore-real-events")
|
||||
const info = workspaceInfo(Instance.project.id, type, { directory: dir })
|
||||
insertWorkspace(info)
|
||||
registerAdaptor(Instance.project.id, type, remoteAdaptor(`${url}/real`, { directory: dir }).adaptor)
|
||||
registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/real`, { directory: dir }).adapter)
|
||||
const session = yield* sessionSvc.create({ title: "real events" })
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const msg = yield* sessionSvc.updateMessage({
|
||||
|
||||
@@ -34,7 +34,7 @@ afterAll(() => {
|
||||
})
|
||||
|
||||
describe("plugin.workspace", () => {
|
||||
it.live("plugin can install a workspace adaptor", () =>
|
||||
it.live("plugin can install a workspace adapter", () =>
|
||||
provideTmpdirInstance((dir) =>
|
||||
Effect.gen(function* () {
|
||||
const type = `plug-${Math.random().toString(36).slice(2)}`
|
||||
@@ -48,7 +48,7 @@ describe("plugin.workspace", () => {
|
||||
"export default async ({ experimental_workspace }) => {",
|
||||
` experimental_workspace.register(${JSON.stringify(type)}, {`,
|
||||
' name: "plug",',
|
||||
' description: "plugin workspace adaptor",',
|
||||
' description: "plugin workspace adapter",',
|
||||
" configure(input) {",
|
||||
` return { ...input, name: "plug", branch: "plug/main", directory: ${JSON.stringify(space)} }`,
|
||||
" },",
|
||||
@@ -258,6 +258,18 @@ describe("HttpApi server", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("matches SDK-affecting request schema details", () => {
|
||||
const effect = effectOpenApi()
|
||||
const sessionUpdate = effect.paths["/session/{sessionID}"]?.patch?.requestBody
|
||||
const sessionUpdateSchema =
|
||||
typeof sessionUpdate === "object" && sessionUpdate && "content" in sessionUpdate
|
||||
? sessionUpdate.content?.["application/json"]?.schema
|
||||
: undefined
|
||||
const sessionUpdateProperties = sessionUpdateSchema?.properties as Record<string, OpenApiSchema> | undefined
|
||||
const time = sessionUpdateProperties?.time
|
||||
expect(time?.properties?.archived).toEqual({ type: "number" })
|
||||
})
|
||||
|
||||
test("documents event routes as server-sent events", () => {
|
||||
const effect = effectOpenApi()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user