paviko 2 месяцев назад
Родитель
Сommit
e2ab6fb9fa

+ 3 - 34
.opencode/bun.lock

@@ -4,45 +4,14 @@
   "workspaces": {
     "": {
       "dependencies": {
-        "@octokit/rest": "^22.0.1",
-        "@opencode-ai/plugin": "1.0.161",
+        "@opencode-ai/plugin": "1.0.152",
       },
     },
   },
   "packages": {
-    "@octokit/auth-token": ["@octokit/[email protected]", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
+    "@opencode-ai/plugin": ["@opencode-ai/[email protected]", "", { "dependencies": { "@opencode-ai/sdk": "1.0.152", "zod": "4.1.8" } }, "sha512-P8ov9iUOaonO1yWKnArYrIOcyzncFgIP0oDn3KTWbIM0viVA0lUf64GIwLuKcDd6W9m9ZgFcB06RzEKDnZHNjA=="],
 
-    "@octokit/core": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="],
-
-    "@octokit/endpoint": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="],
-
-    "@octokit/graphql": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="],
-
-    "@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
-
-    "@octokit/plugin-paginate-rest": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="],
-
-    "@octokit/plugin-request-log": ["@octokit/[email protected]", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
-
-    "@octokit/plugin-rest-endpoint-methods": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="],
-
-    "@octokit/request": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="],
-
-    "@octokit/request-error": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="],
-
-    "@octokit/rest": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="],
-
-    "@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
-
-    "@opencode-ai/plugin": ["@opencode-ai/[email protected]", "", { "dependencies": { "@opencode-ai/sdk": "1.0.161", "zod": "4.1.8" } }, "sha512-8bD/SvqO0LMrhPx8rNTF02nUWNW0ajYkuO9uTMnCCqiyWMx7QLcGfM1xMb/qm1gUiWR9KsSFsAr3s5cGkA78yQ=="],
-
-    "@opencode-ai/sdk": ["@opencode-ai/[email protected]", "", {}, "sha512-yhduFCmayZ0C7GKecwZ1fvtixReC3wHM4FOgRTc4ai9nwd7jiGCtFi/pimc/0/6DV4lVkNyp0/2jdMekAZnt7A=="],
-
-    "before-after-hook": ["[email protected]", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
-
-    "fast-content-type-parse": ["[email protected]", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
-
-    "universal-user-agent": ["[email protected]", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
+    "@opencode-ai/sdk": ["@opencode-ai/[email protected]", "", {}, "sha512-0m0Z8QFg98DMEt24PWOJZBrdUhKCHBYo5AHsCIrMkrH4jfl9V8zsN2upp1mWw+waREDEnFtQwdLA56oIS8gngQ=="],
 
     "zod": ["[email protected]", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
   }

Разница между файлами не показана из-за своего большого размера
+ 386 - 18
bun.lock


+ 1 - 1
hosts/jetbrains-plugin/build.gradle.kts

@@ -5,7 +5,7 @@ plugins {
 }
 
 group = "paviko.opencode"
-version = "25.12.5"
+version = "25.12.16"
 
 repositories {
     mavenCentral()

+ 9 - 4
hosts/jetbrains-plugin/changelog.html

@@ -1,6 +1,11 @@
 <h2>OpenCode UX+ (unofficial) JetBrains Plugin Changelog</h2>
 
-<h3>2025.12.5</h3>
+<h3>25.12.16</h3>
+<ul>
+  <li>Updated OpenCode to v1.0.163</li>
+</ul>
+
+<h3>25.12.5</h3>
 <ul>
   <li>Auto refresh files in IDE on edit/write</li>
   <li>New panel with all modified files in session</li>
@@ -9,7 +14,7 @@
   <li>Updated OpenCode to v1.0.133</li>
 </ul>
 
-<h3>2025.11.30</h3>
+<h3>25.11.30</h3>
 <ul>
   <li>Updated OpenCode to v1.0.121</li>
   <li>Fixed working directory as server start directory, not git root</li>
@@ -17,13 +22,13 @@
   <li>Fixed web view hang on html url click in Intellij IDEA 2025.x</li>
 </ul>
 
-<h3>2025.11.24</h3>
+<h3>25.11.24</h3>
 <ul>
   <li>Fixed multi-project bug with drag&drop and "Add to context"</li>
   <li>UI improvements</li>
 </ul>
 
-<h3>2025.11.20</h3>
+<h3>25.11.20</h3>
 <ul>
   <li>Providers can be configured from Settings panel - can be added/removed, also OAuth</li>
   <li>Fixed context size bug when session what aborted</li>

+ 1 - 1
hosts/jetbrains-plugin/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME

+ 10 - 10
hosts/jetbrains-plugin/gradlew.bat

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
 
 goto fail
 
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 if exist "%JAVA_EXE%" goto execute
 
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
 
 goto fail
 

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

@@ -5,6 +5,10 @@ All notable changes to the OpenCode VSCode extension will be documented in this
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [25.12.16] - 2025-12-16
+
+- Updated OpenCode to v1.0.163
+
 ## [25.12.5] - 2025-12-05
 
 - Auto refresh files in IDE on edit/write

+ 1 - 1
hosts/vscode-plugin/package.json

@@ -2,7 +2,7 @@
   "name": "opencode-ux-plus",
   "displayName": "OpenCode UX+ (unofficial)",
   "description": "Unofficial OpenCode VSCode extension",
-  "version": "25.12.5",
+  "version": "25.12.16",
   "publisher": "paviko",
   "author": {
     "name": "paviko"

+ 3 - 1
package.json

@@ -17,7 +17,8 @@
       "packages/*",
       "packages/console/*",
       "packages/sdk/js",
-      "packages/slack"
+      "packages/slack",
+      "packages/opencode/webgui"
     ],
     "catalog": {
       "@types/bun": "1.3.4",
@@ -64,6 +65,7 @@
   "dependencies": {
     "@aws-sdk/client-s3": "3.933.0",
     "@octokit/rest": "22.0.1",
+    "@opencode-ai/plugin": "workspace:*",
     "@opencode-ai/script": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
     "typescript": "catalog:"

+ 5 - 1
packages/opencode/package.json

@@ -14,7 +14,8 @@
     "lint": "echo 'Running lint checks...' && bun test --coverage",
     "format": "echo 'Formatting code...' && bun run --prettier --write src/**/*.ts",
     "docs": "echo 'Generating documentation...' && find src -name '*.ts' -exec echo 'Processing: {}' \\;",
-    "deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'"
+    "deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'",
+    "build:webgui": "cd webgui && bun run build"
   },
   "bin": {
     "opencode": "./bin/opencode"
@@ -24,6 +25,7 @@
   },
   "devDependencies": {
     "@babel/core": "7.28.4",
+    "@babel/preset-typescript": "7.27.1",
     "@octokit/webhooks-types": "7.6.1",
     "@parcel/watcher-darwin-arm64": "2.5.1",
     "@parcel/watcher-darwin-x64": "2.5.1",
@@ -38,6 +40,7 @@
     "@types/bun": "catalog:",
     "@types/turndown": "5.0.5",
     "@types/yargs": "17.0.33",
+    "babel-preset-solid": "1.9.10",
     "typescript": "catalog:",
     "@typescript/native-preview": "catalog:",
     "vscode-languageserver-types": "3.17.5",
@@ -62,6 +65,7 @@
     "@clack/prompts": "1.0.0-alpha.1",
     "@hono/standard-validator": "0.1.5",
     "@hono/zod-validator": "catalog:",
+    "@iarna/toml": "2.2.5",
     "@modelcontextprotocol/sdk": "1.15.1",
     "@octokit/graphql": "9.0.2",
     "@octokit/rest": "catalog:",

+ 116 - 0
packages/opencode/src/server/server.ts

@@ -4,6 +4,7 @@ import { GlobalBus } from "@/bus/global"
 import { Log } from "../util/log"
 import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
 import { Hono } from "hono"
+import type { Context } from "hono"
 import { cors } from "hono/cors"
 import { stream, streamSSE } from "hono/streaming"
 import { proxy } from "hono/proxy"
@@ -29,6 +30,7 @@ import { Command } from "../command"
 import { ProviderAuth } from "../provider/auth"
 import { Global } from "../global"
 import { ProjectRoute } from "./project"
+import { WebGuiRoute } from "../webgui/server/webgui.ts"
 import { ToolRegistry } from "../tool/registry"
 import { zodToJsonSchema } from "zod-to-json-schema"
 import { SessionPrompt } from "../session/prompt"
@@ -47,10 +49,111 @@ import { SessionStatus } from "@/session/status"
 import { upgradeWebSocket, websocket } from "hono/bun"
 import { errors } from "./error"
 import { Pty } from "@/pty"
+import * as State from "@/webgui/state/state"
+import path from "path"
+import * as fs from "fs"
+import { fileURLToPath } from "url"
+import { embeddedWebGui } from "../webgui/embed.generated"
+import { Buffer } from "node:buffer"
 
 // @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
 
+const embeddedWebGuiMap = new Map<string, string>(
+  embeddedWebGui.map((item): [string, string] => [item.path, item.data]),
+)
+
+const moduleDirectory = path.dirname(fileURLToPath(import.meta.url))
+const webGuiCandidates = [
+  path.join(moduleDirectory, "../../webgui-dist"),
+  path.resolve(path.dirname(process.execPath), "../packages/opencode/webgui-dist"),
+  path.resolve(process.cwd(), "packages/opencode/webgui-dist"),
+  path.resolve(process.cwd(), "../packages/opencode/webgui-dist"),
+]
+
+function existingWebGuiRoot() {
+  const existing = webGuiCandidates.find((candidate) => fs.existsSync(path.join(candidate, "index.html")))
+  return existing ?? webGuiCandidates[0]
+}
+
+const webGuiRoot = existingWebGuiRoot()
+
+function webGuiRelative(pathname: string) {
+  const withoutPrefix = pathname.replace(/^\/app/, "")
+  const trimmed = withoutPrefix.replace(/^\/+/, "")
+  if (trimmed.length === 0) return "index.html"
+  if (trimmed.endsWith("/")) return trimmed + "index.html"
+  return trimmed
+}
+
+function webGuiContentType(relativePath: string) {
+  if (relativePath.endsWith(".html")) return "text/html; charset=utf-8"
+  if (relativePath.endsWith(".js")) return "application/javascript; charset=utf-8"
+  if (relativePath.endsWith(".css")) return "text/css; charset=utf-8"
+  if (relativePath.endsWith(".svg")) return "image/svg+xml"
+  if (relativePath.endsWith(".png")) return "image/png"
+  if (relativePath.endsWith(".jpg") || relativePath.endsWith(".jpeg")) return "image/jpeg"
+  if (relativePath.endsWith(".gif")) return "image/gif"
+  if (relativePath.endsWith(".webp")) return "image/webp"
+  if (relativePath.endsWith(".ico")) return "image/x-icon"
+  if (relativePath.endsWith(".json")) return "application/json; charset=utf-8"
+  if (relativePath.endsWith(".txt")) return "text/plain; charset=utf-8"
+  if (relativePath.endsWith(".map")) return "application/json; charset=utf-8"
+  return "application/octet-stream"
+}
+
+function serveWebGuiFromFs(relativePath: string) {
+  const fullPath = path.join(webGuiRoot, relativePath)
+  if (!fs.existsSync(fullPath)) return
+  const file = Bun.file(fullPath)
+  return new Response(file, {
+    headers: {
+      "Content-Type": webGuiContentType(relativePath),
+      "Cache-Control": "public, max-age=3600",
+    },
+  })
+}
+
+function serveWebGuiFromEmbed(relativePath: string) {
+  const encoded = embeddedWebGuiMap.get(relativePath)
+  if (!encoded) return
+  const buffer = Buffer.from(encoded, "base64")
+  const body = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
+  return new Response(body, {
+    headers: {
+      "Content-Type": webGuiContentType(relativePath),
+      "Cache-Control": "public, max-age=3600",
+    },
+  })
+}
+
+function serveWebGui(pathname: string) {
+  const relativePath = webGuiRelative(pathname)
+  const fsResponse = serveWebGuiFromFs(relativePath)
+  if (fsResponse) return fsResponse
+  const embedResponse = serveWebGuiFromEmbed(relativePath)
+  if (embedResponse) return embedResponse
+  if (!relativePath.includes(".")) {
+    const fallbackFs = serveWebGuiFromFs("index.html")
+    if (fallbackFs) return fallbackFs
+    const fallbackEmbed = serveWebGuiFromEmbed("index.html")
+    if (fallbackEmbed) return fallbackEmbed
+  }
+}
+
+function handleWebGui(c: Context) {
+  const response = serveWebGui(c.req.path)
+  if (response) return response
+  return c.text("Not Found", 404)
+}
+
+function handleWebGuiRoot(c: Context) {
+  const response = serveWebGui("index.html")
+  if (response) return response
+  return c.text("Not Found", 404)
+
+}
+
 export namespace Server {
   const log = Log.create({ service: "server" })
 
@@ -200,6 +303,14 @@ export namespace Server {
           async fn() {
             return next()
           },
+        }).catch((err) => {
+          console.log("Caught Error in Instance.provide:", err, err.code, err.message);
+          if (err instanceof Storage.NotFoundError) return c.text(err.message, 404)
+          // If the directory doesn't exist or is invalid, return 400
+          if (err.code === "ENOENT" || err.message.includes("No such file or directory")) {
+            return c.text(`Invalid directory: ${directory}`, 400)
+          }
+          throw err
         })
       })
       .get(
@@ -2506,6 +2617,11 @@ export namespace Server {
           })
         },
       )
+      // Mount Web GUI API routes
+      .route("/app/api", WebGuiRoute)
+      // Serve Web GUI static files, prioritizing filesystem assets and falling back to embedded bundle
+      .get("/app", handleWebGui)
+      .get("/app/*", handleWebGui)
       .all("/*", async (c) => {
         return proxy(`https://desktop.opencode.ai${c.req.path}`, {
           ...c.req,

+ 10 - 3
packages/opencode/src/tool/registry.ts

@@ -38,9 +38,16 @@ export namespace ToolRegistry {
         dot: true,
       })) {
         const namespace = path.basename(match, path.extname(match))
-        const mod = await import(match)
-        for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
-          custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
+        try {
+          const mod = await import(match)
+          for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
+            custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
+          }
+        } catch (error) {
+          log.error("failed to load custom tool", {
+            path: match,
+            error,
+          })
         }
       }
     }

+ 4 - 1
packages/opencode/webgui/package.json

@@ -49,6 +49,9 @@
     "typescript": "~5.9.3",
     "typescript-eslint": "^8.45.0",
     "vite": "catalog:",
-    "vitest": "4.0.13"
+    "vitest": "4.0.13",
+    "quansync": "^0.2.11",
+    "react-markdown": "10.1.0",
+    "remark-gfm": "4.0.1"
   }
 }

+ 0 - 1
packages/opencode/webgui/src/components/MarkdownRenderer.tsx

@@ -205,7 +205,6 @@ const markdownComponents: Partial<Components> = {
 export function MarkdownRenderer({ children }: MarkdownRendererProps) {
   return (
     <div className="markdown-content">
-      {/* @ts-expect-error - React 19 type incompatibility with react-markdown v10 */}
       <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
         {children}
       </ReactMarkdown>

Некоторые файлы не были показаны из-за большого количества измененных файлов