diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts index 8ad0212ad0..ed80f49d54 100644 --- a/.opencode/tool/github-triage.ts +++ b/.opencode/tool/github-triage.ts @@ -5,16 +5,8 @@ import DESCRIPTION from "./github-triage.txt" const TEAM = { desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"], zen: ["fwang", "MrMushrooooom"], - tui: [ - "thdxr", - "kommander", - // "rekram1-node" (on vacation) - ], - core: [ - "thdxr", - // "rekram1-node", (on vacation) - "jlongster", - ], + tui: ["thdxr", "kommander", "rekram1-node"], + core: ["thdxr", "rekram1-node", "jlongster"], docs: ["R44VC0RP"], windows: ["Hona"], } as const @@ -50,7 +42,10 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) { export default tool({ description: DESCRIPTION, args: { - assignee: tool.schema.enum(ASSIGNEES as [string, ...string[]]).describe("The username of the assignee"), + assignee: tool.schema + .enum(ASSIGNEES as [string, ...string[]]) + .describe("The username of the assignee") + .default("rekram1-node"), labels: tool.schema .array(tool.schema.enum(["nix", "opentui", "perf", "web", "desktop", "zen", "docs", "windows", "core"])) .describe("The labels(s) to add to the issue") @@ -73,8 +68,7 @@ export default tool({ results.push("Dropped label: nix (issue does not mention nix)") } - // const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee - const assignee = web ? pick(TEAM.desktop) : args.assignee + const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee if (labels.includes("zen") && !zen) { throw new Error("Only add the zen label when issue title/body contains 'zen'") diff --git a/.opencode/tool/github-triage.txt b/.opencode/tool/github-triage.txt index 1a2d69bdb5..4369ed2351 100644 --- a/.opencode/tool/github-triage.txt +++ b/.opencode/tool/github-triage.txt @@ -4,5 +4,3 @@ Choose labels and assignee using the current triage policy and ownership rules. Pick the most fitting labels for the issue and assign one owner. If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random. - -(Note: rekram1-node is on vacation, do not assign issues to him.) diff --git a/bun.lock b/bun.lock index 4b7097232a..bb85a46e01 100644 --- a/bun.lock +++ b/bun.lock @@ -335,8 +335,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.86", - "@opentui/solid": "0.1.86", + "@opentui/core": "0.1.87", + "@opentui/solid": "0.1.87", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -1422,21 +1422,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.86", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.86", "@opentui/core-darwin-x64": "0.1.86", "@opentui/core-linux-arm64": "0.1.86", "@opentui/core-linux-x64": "0.1.86", "@opentui/core-win32-arm64": "0.1.86", "@opentui/core-win32-x64": "0.1.86", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-3tRLbI9ADrQE1jEEn4x2aJexEOQZkv9Emk2BixMZqxfVhz2zr2SxtpimDAX0vmZK3+GnWAwBWxuaCAsxZpY4+w=="], + "@opentui/core": ["@opentui/core@0.1.87", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.87", "@opentui/core-darwin-x64": "0.1.87", "@opentui/core-linux-arm64": "0.1.87", "@opentui/core-linux-x64": "0.1.87", "@opentui/core-win32-arm64": "0.1.87", "@opentui/core-win32-x64": "0.1.87", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dhsmMv0IqKftwG7J/pBrLBj2armsYIg5R3LBvciRQI/6X89GufP4l1u0+QTACAx6iR4SYJJNVNQ2tdX8LM9rMw=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.86", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Zp7q64+d+Dcx6YrH3mRcnHq8EOBnrfc1RvjgSWLhpXr49hY6LzuhqpfZM57aGErPYlR+ff8QM6e5FUkFnDfyjw=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.87", "", { "os": "darwin", "cpu": "arm64" }, "sha512-G8oq85diOfkU6n0T1CxCle7oDmpKxwhcdhZ9khBMU5IrfLx9ZDuCM3F6MsiRQWdvPPCq2oomNbd64bYkPamYgw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.86", "", { "os": "darwin", "cpu": "x64" }, "sha512-NcxfjCJm1kLnTMVOpAPdRYNi8W8XdAXNa6N7i9khiVFrl2v5KRQfUjbrSOUYVxFJNc3jKFG6rsn3jEApvn92qA=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.87", "", { "os": "darwin", "cpu": "x64" }, "sha512-MYTFQfOHm6qO7YaY4GHK9u/oJlXY6djaaxl5I+k4p2mk3vvuFIl/AP1ypITwBFjyV5gyp7PRWFp4nGfY9oN8bw=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.86", "", { "os": "linux", "cpu": "arm64" }, "sha512-EDHAvqSOr8CXzbDvo1aE5blJ6wu1aSbR2LqoXtoeXHemr2T2W42D2TdIWewG6K+/BuRbzZnqt9wnYFBksLW6lw=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.87", "", { "os": "linux", "cpu": "arm64" }, "sha512-he8o1h5M6oskRJ7wE+xKJgmWnv5ZwN6gB3M/Z+SeHtOMPa5cZmi3TefTjG54llEgFfx0F9RcqHof7TJ/GNxRkw=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.86", "", { "os": "linux", "cpu": "x64" }, "sha512-VBaBkVdQDxYV4WcKjb+jgyMS5PiVHepvfaoKWpz1Bq+J01xXW4XPcXyPGkgR1+2R93KzaugEnLscTW4mWtLHlQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.87", "", { "os": "linux", "cpu": "x64" }, "sha512-aiUwjPlH4yDcB8/6YDKSmMkaoGAAltL0Xo0AzXyAtJXWK5tkCSaYjEVwzJ/rYRkr4Magnad+Mjth4AQUWdR2AA=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.86", "", { "os": "win32", "cpu": "arm64" }, "sha512-xKbT7sEKYKGwUPkoqmLfHjbJU+vwHPDwf/r/mIunL41JXQBB35CSZ3/QgIwpp2kkteu7oE1tdBdg15ogUU4OMg=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.87", "", { "os": "win32", "cpu": "arm64" }, "sha512-cmP0pOyREjWGniHqbDmaMY7U+1AyagrD8VseJbU0cGpNgVpG2/gbrJUGdfdLB0SNb+mzLdx6SOjdxtrElwRCQA=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.86", "", { "os": "win32", "cpu": "x64" }, "sha512-HRfgAUlcu71/MrtgfX4Gj7PsDtfXZiuC506Pkn1OnRN1Xomcu10BVRDweUa0/g8ldU9i9kLjMGGnpw6/NjaBFg=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.87", "", { "os": "win32", "cpu": "x64" }, "sha512-N2GErAAP8iODf2RPp86pilPaVKiD6G4pkpZL5nLGbKsl0bndrVTpSqZcn8+/nQwFZDPD/AsiRTYNOfWOblhzOw=="], - "@opentui/solid": ["@opentui/solid@0.1.86", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.86", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-pOZC9dlZIH+bpstVVZ2AvYukBnslZTKSl/y5H8FWcMTHGv/BzpGxXBxstL65E/IQASqPFbvFcs7yMRzdLhynmA=="], + "@opentui/solid": ["@opentui/solid@0.1.87", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.87", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-lRT9t30l8+FtgOjjWJcdb2MT6hP8/RKqwGgYwTI7fXrOqdhxxwdP2SM+rH2l3suHeASheiTdlvPAo230iUcsvg=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/nix/hashes.json b/nix/hashes.json index 2190feb256..d4146be6b7 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-+SMpaj0jeIHjlddAu6QIwojmWFVIiA8/G32hiQMjcOk=", - "aarch64-linux": "sha256-uo63IF6OCMab+O3ngn1sVxqIGJMm04HXuDgIRmXNTNk=", - "aarch64-darwin": "sha256-yB2tWm6AsX6UifnDqe7VldhN5zTQkDoqZ87AGQYjxT4=", - "x86_64-darwin": "sha256-nNhtqMSG4/y+uxjj14Jc5QQ7X6hQli9ni4v56XAvaAU=" + "x86_64-linux": "sha256-duBedS4ZTc1as03OM0KB9mKKU21Cywv4o9GHwQZv6Ts=", + "aarch64-linux": "sha256-juvQfuNBqqzeB/TIY9PuUDqgpsdyI54ImowjQLrNhns=", + "aarch64-darwin": "sha256-kKgcuEN1oJqHJc+sGjcZ4INWvbZczSTDJ8VHIWAquD4=", + "x86_64-darwin": "sha256-hXkFWOL4wi9s8HSrChpqtH4PKSNzbzVgU+0GbAxEUT4=" } } diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 93a4654f86..acfd7f90f4 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -49,14 +49,19 @@ const bad = (n: number | undefined, limit: number, low = false) => { const session = (path: string) => path.includes("/session") -function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string }) { +function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string; wide?: boolean }) { return ( - -
-
{props.label}
+ +
+
{props.label}
-
+
diff --git a/packages/opencode/package.json b/packages/opencode/package.json index beeb1de82c..e22b5cd959 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -93,8 +93,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.86", - "@opentui/solid": "0.1.86", + "@opentui/core": "0.1.87", + "@opentui/solid": "0.1.87", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index d5aef34f6e..4f7c94b1d3 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -402,9 +402,12 @@ function App() { const current = promptRef.current // Don't require focus - if there's any text, preserve it const currentPrompt = current?.current?.input ? current.current : undefined + const workspaceID = + route.data.type === "session" ? sync.session.get(route.data.sessionID)?.workspaceID : undefined route.navigate({ type: "home", initialPrompt: currentPrompt, + workspaceID, }) dialog.clear() }, diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx index a25b20505c..b11ad6a734 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx @@ -47,7 +47,7 @@ async function openWorkspace(input: { } let created: Session | undefined while (!created) { - const result = await client.session.create({}).catch(() => undefined) + const result = await client.session.create({ workspaceID: input.workspaceID }).catch(() => undefined) if (!result) { input.toast.show({ message: "Failed to open workspace", diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 77577b2a0e..2d99051fb9 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -37,6 +37,7 @@ import { DialogSkill } from "../dialog-skill" export type PromptProps = { sessionID?: string + workspaceID?: string visible?: boolean disabled?: boolean onSubmit?: () => void @@ -542,7 +543,9 @@ export function Prompt(props: PromptProps) { let sessionID = props.sessionID if (sessionID == null) { - const res = await sdk.client.session.create({}) + const res = await sdk.client.session.create({ + workspaceID: props.workspaceID, + }) if (res.error) { console.log("Creating a session failed:", res.error) diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx index 358461921b..e96cd2c3a4 100644 --- a/packages/opencode/src/cli/cmd/tui/context/route.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx @@ -5,6 +5,7 @@ import type { PromptInfo } from "../component/prompt/history" export type HomeRoute = { type: "home" initialPrompt?: PromptInfo + workspaceID?: string } export type SessionRoute = { diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index 24ea8f3b31..e76e165b26 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -121,6 +121,7 @@ export function Home() { promptRef.set(r) }} hint={Hint} + workspaceID={route.workspaceID} /> diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 6b4242a225..ef2821727b 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -972,6 +972,14 @@ export namespace Config { .describe( "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.", ), + chunkTimeout: z + .number() + .int() + .positive() + .optional() + .describe( + "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.", + ), }) .catchall(z.any()) .optional(), diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 0dca27d651..e48a42a8b3 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -396,8 +396,14 @@ export namespace MCP { } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)) - // Handle OAuth-specific errors - if (error instanceof UnauthorizedError) { + // Handle OAuth-specific errors. + // The SDK throws UnauthorizedError when auth() returns 'REDIRECT', + // but may also throw plain Errors when auth() fails internally + // (e.g. during discovery, registration, or state generation). + // When an authProvider is attached, treat both cases as auth-related. + const isAuthError = + error instanceof UnauthorizedError || (authProvider && lastError.message.includes("OAuth")) + if (isAuthError) { log.info("mcp server requires authentication", { key, transport: name }) // Check if this is a "needs registration" error diff --git a/packages/opencode/src/mcp/oauth-provider.ts b/packages/opencode/src/mcp/oauth-provider.ts index 164b1d1f14..b4da73169e 100644 --- a/packages/opencode/src/mcp/oauth-provider.ts +++ b/packages/opencode/src/mcp/oauth-provider.ts @@ -144,10 +144,19 @@ export class McpOAuthProvider implements OAuthClientProvider { async state(): Promise { const entry = await McpAuth.get(this.mcpName) - if (!entry?.oauthState) { - throw new Error(`No OAuth state saved for MCP server: ${this.mcpName}`) + if (entry?.oauthState) { + return entry.oauthState } - return entry.oauthState + + // Generate a new state if none exists — the SDK calls state() as a + // generator, not just a reader, so we need to produce a value even when + // startAuth() hasn't pre-saved one (e.g. during automatic auth on first + // connect). + const newState = Array.from(crypto.getRandomValues(new Uint8Array(32))) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + await McpAuth.updateOAuthState(this.mcpName, newState) + return newState } async invalidateCredentials(type: "all" | "client" | "tokens"): Promise { diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index e1fff1a147..68cece0a52 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -88,6 +88,12 @@ export namespace Project { } } + function readCachedId(dir: string) { + return Filesystem.readText(path.join(dir, "opencode")) + .then((x) => x.trim()) + .catch(() => undefined) + } + export async function fromDirectory(directory: string) { log.info("fromDirectory", { directory }) @@ -101,19 +107,43 @@ export namespace Project { const gitBinary = which("git") // cached id calculation - let id = await Filesystem.readText(path.join(dotgit, "opencode")) - .then((x) => x.trim()) - .catch(() => undefined) + let id = await readCachedId(dotgit) if (!gitBinary) { return { id: id ?? "global", worktree: sandbox, - sandbox: sandbox, + sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } + const worktree = await git(["rev-parse", "--git-common-dir"], { + cwd: sandbox, + }) + .then(async (result) => { + const common = gitpath(sandbox, await result.text()) + // Avoid going to parent of sandbox when git-common-dir is empty. + return common === sandbox ? sandbox : path.dirname(common) + }) + .catch(() => undefined) + + if (!worktree) { + return { + id: id ?? "global", + worktree: sandbox, + sandbox, + vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), + } + } + + // In the case of a git worktree, it can't cache the id + // because `.git` is not a folder, but it always needs the + // same project id as the common dir, so we resolve it now + if (id == null) { + id = await readCachedId(path.join(worktree, ".git")) + } + // generate id from root commit if (!id) { const roots = await git(["rev-list", "--max-parents=0", "--all"], { @@ -132,7 +162,7 @@ export namespace Project { return { id: "global", worktree: sandbox, - sandbox: sandbox, + sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } @@ -147,7 +177,7 @@ export namespace Project { return { id: "global", worktree: sandbox, - sandbox: sandbox, + sandbox, vcs: "git", } } @@ -161,33 +191,14 @@ export namespace Project { if (!top) { return { id, - sandbox, worktree: sandbox, + sandbox, vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), } } sandbox = top - const worktree = await git(["rev-parse", "--git-common-dir"], { - cwd: sandbox, - }) - .then(async (result) => { - const common = gitpath(sandbox, await result.text()) - // Avoid going to parent of sandbox when git-common-dir is empty. - return common === sandbox ? sandbox : path.dirname(common) - }) - .catch(() => undefined) - - if (!worktree) { - return { - id, - sandbox, - worktree: sandbox, - vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS), - } - } - return { id, sandbox, diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 09035d272c..c174ebd9ff 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -46,6 +46,8 @@ import { GoogleAuth } from "google-auth-library" import { ProviderTransform } from "./transform" import { Installation } from "../installation" +const DEFAULT_CHUNK_TIMEOUT = 120_000 + export namespace Provider { const log = Log.create({ service: "provider" }) @@ -85,6 +87,54 @@ export namespace Provider { }) } + function wrapSSE(res: Response, ms: number, ctl: AbortController) { + if (typeof ms !== "number" || ms <= 0) return res + if (!res.body) return res + if (!res.headers.get("content-type")?.includes("text/event-stream")) return res + + const reader = res.body.getReader() + const body = new ReadableStream({ + async pull(ctrl) { + const part = await new Promise>>((resolve, reject) => { + const id = setTimeout(() => { + const err = new Error("SSE read timed out") + ctl.abort(err) + void reader.cancel(err) + reject(err) + }, ms) + + reader.read().then( + (part) => { + clearTimeout(id) + resolve(part) + }, + (err) => { + clearTimeout(id) + reject(err) + }, + ) + }) + + if (part.done) { + ctrl.close() + return + } + + ctrl.enqueue(part.value) + }, + async cancel(reason) { + ctl.abort(reason) + await reader.cancel(reason) + }, + }) + + return new Response(body, { + headers: new Headers(res.headers), + status: res.status, + statusText: res.statusText, + }) + } + const BUNDLED_PROVIDERS: Record SDK> = { "@ai-sdk/amazon-bedrock": createAmazonBedrock, "@ai-sdk/anthropic": createAnthropic, @@ -1092,21 +1142,23 @@ export namespace Provider { if (existing) return existing const customFetch = options["fetch"] + const chunkTimeout = options["chunkTimeout"] || DEFAULT_CHUNK_TIMEOUT + delete options["chunkTimeout"] options["fetch"] = async (input: any, init?: BunFetchRequestInit) => { // Preserve custom fetch if it exists, wrap it with timeout logic const fetchFn = customFetch ?? fetch const opts = init ?? {} + const chunkAbortCtl = typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined + const signals: AbortSignal[] = [] - if (options["timeout"] !== undefined && options["timeout"] !== null) { - const signals: AbortSignal[] = [] - if (opts.signal) signals.push(opts.signal) - if (options["timeout"] !== false) signals.push(AbortSignal.timeout(options["timeout"])) + if (opts.signal) signals.push(opts.signal) + if (chunkAbortCtl) signals.push(chunkAbortCtl.signal) + if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false) + signals.push(AbortSignal.timeout(options["timeout"])) - const combined = signals.length > 1 ? AbortSignal.any(signals) : signals[0] - - opts.signal = combined - } + const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals) + if (combined) opts.signal = combined // Strip openai itemId metadata following what codex does // Codex uses #[serde(skip_serializing)] on id fields for all item types: @@ -1126,11 +1178,14 @@ export namespace Provider { } } - return fetchFn(input, { + const res = await fetchFn(input, { ...opts, // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682 timeout: false, }) + + if (!chunkAbortCtl) return res + return wrapSSE(res, chunkTimeout, chunkAbortCtl) } const bundledFn = BUNDLED_PROVIDERS[model.api.npm] diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 471da03cbc..407c268782 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -657,9 +657,21 @@ export namespace ProviderTransform { // https://v5.ai-sdk.dev/providers/ai-sdk-providers/perplexity return {} - case "@mymediset/sap-ai-provider": case "@jerome-benoit/sap-ai-provider-v2": if (model.api.id.includes("anthropic")) { + if (isAnthropicAdaptive) { + return Object.fromEntries( + adaptiveEfforts.map((effort) => [ + effort, + { + thinking: { + type: "adaptive", + }, + effort, + }, + ]), + ) + } return { high: { thinking: { @@ -675,7 +687,26 @@ export namespace ProviderTransform { }, } } - return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }])) + if (model.api.id.includes("gemini") && id.includes("2.5")) { + return { + high: { + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 16000, + }, + }, + max: { + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 24576, + }, + }, + } + } + if (model.api.id.includes("gpt") || /\bo[1-9]/.test(model.api.id)) { + return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }])) + } + return {} } return {} } diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index b117632051..5cc4d7da8d 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -219,6 +219,7 @@ export namespace Session { parentID: Identifier.schema("session").optional(), title: z.string().optional(), permission: Info.shape.permission, + workspaceID: Identifier.schema("workspace").optional(), }) .optional(), async (input) => { @@ -227,6 +228,7 @@ export namespace Session { directory: Instance.directory, title: input?.title, permission: input?.permission, + workspaceID: input?.workspaceID, }) }, ) @@ -242,6 +244,7 @@ export namespace Session { const title = getForkedTitle(original.title) const session = await createNext({ directory: Instance.directory, + workspaceID: original.workspaceID, title, }) const msgs = await messages({ sessionID: input.sessionID }) @@ -292,6 +295,7 @@ export namespace Session { id?: string title?: string parentID?: string + workspaceID?: string directory: string permission?: PermissionNext.Ruleset }) { @@ -301,7 +305,7 @@ export namespace Session { version: Installation.VERSION, projectID: Instance.project.id, directory: input.directory, - workspaceID: WorkspaceContext.workspaceID, + workspaceID: input.workspaceID, parentID: input.parentID, title: input.title ?? createDefaultTitle(!!input.parentID), permission: input.permission, diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 8b5accc333..aa5613010c 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -413,7 +413,7 @@ export namespace Worktree { await runStartScripts(info.directory, { projectID, extra }) } - void start().catch((error) => { + return start().catch((error) => { log.error("worktree start task failed", { directory: info.directory, error }) }) } diff --git a/packages/opencode/test/mcp/oauth-auto-connect.test.ts b/packages/opencode/test/mcp/oauth-auto-connect.test.ts new file mode 100644 index 0000000000..76f825247c --- /dev/null +++ b/packages/opencode/test/mcp/oauth-auto-connect.test.ts @@ -0,0 +1,199 @@ +import { test, expect, mock, beforeEach } from "bun:test" + +// Mock UnauthorizedError to match the SDK's class +class MockUnauthorizedError extends Error { + constructor(message?: string) { + super(message ?? "Unauthorized") + this.name = "UnauthorizedError" + } +} + +// Track what options were passed to each transport constructor +const transportCalls: Array<{ + type: "streamable" | "sse" + url: string + options: { authProvider?: unknown } +}> = [] + +// Controls whether the mock transport simulates a 401 that triggers the SDK +// auth flow (which calls provider.state()) or a simple UnauthorizedError. +let simulateAuthFlow = true + +// Mock the transport constructors to simulate OAuth auto-auth on 401 +mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({ + StreamableHTTPClientTransport: class MockStreamableHTTP { + authProvider: + | { + state?: () => Promise + redirectToAuthorization?: (url: URL) => Promise + saveCodeVerifier?: (v: string) => Promise + } + | undefined + constructor(url: URL, options?: { authProvider?: unknown }) { + this.authProvider = options?.authProvider as typeof this.authProvider + transportCalls.push({ + type: "streamable", + url: url.toString(), + options: options ?? {}, + }) + } + async start() { + // Simulate what the real SDK transport does on 401: + // It calls auth() which eventually calls provider.state(), then + // provider.redirectToAuthorization(), then throws UnauthorizedError. + if (simulateAuthFlow && this.authProvider) { + // The SDK calls provider.state() to get the OAuth state parameter + if (this.authProvider.state) { + await this.authProvider.state() + } + // The SDK calls saveCodeVerifier before redirecting + if (this.authProvider.saveCodeVerifier) { + await this.authProvider.saveCodeVerifier("test-verifier") + } + // The SDK calls redirectToAuthorization to redirect the user + if (this.authProvider.redirectToAuthorization) { + await this.authProvider.redirectToAuthorization(new URL("https://auth.example.com/authorize?state=test")) + } + throw new MockUnauthorizedError() + } + throw new MockUnauthorizedError() + } + async finishAuth(_code: string) {} + }, +})) + +mock.module("@modelcontextprotocol/sdk/client/sse.js", () => ({ + SSEClientTransport: class MockSSE { + constructor(url: URL, options?: { authProvider?: unknown }) { + transportCalls.push({ + type: "sse", + url: url.toString(), + options: options ?? {}, + }) + } + async start() { + throw new Error("Mock SSE transport cannot connect") + } + }, +})) + +// Mock the MCP SDK Client +mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({ + Client: class MockClient { + async connect(transport: { start: () => Promise }) { + await transport.start() + } + }, +})) + +// Mock UnauthorizedError in the auth module so instanceof checks work +mock.module("@modelcontextprotocol/sdk/client/auth.js", () => ({ + UnauthorizedError: MockUnauthorizedError, +})) + +beforeEach(() => { + transportCalls.length = 0 + simulateAuthFlow = true +}) + +// Import modules after mocking +const { MCP } = await import("../../src/mcp/index") +const { Instance } = await import("../../src/project/instance") +const { tmpdir } = await import("../fixture/fixture") + +test("first connect to OAuth server shows needs_auth instead of failed", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + `${dir}/opencode.json`, + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + mcp: { + "test-oauth": { + type: "remote", + url: "https://example.com/mcp", + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await MCP.add("test-oauth", { + type: "remote", + url: "https://example.com/mcp", + }) + + const serverStatus = result.status as Record + + // The server should be detected as needing auth, NOT as failed. + // Before the fix, provider.state() would throw a plain Error + // ("No OAuth state saved for MCP server: test-oauth") which was + // not caught as UnauthorizedError, causing status to be "failed". + expect(serverStatus["test-oauth"]).toBeDefined() + expect(serverStatus["test-oauth"].status).toBe("needs_auth") + }, + }) +}) + +test("state() generates a new state when none is saved", async () => { + const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider") + const { McpAuth } = await import("../../src/mcp/auth") + + await using tmp = await tmpdir() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const provider = new McpOAuthProvider( + "test-state-gen", + "https://example.com/mcp", + {}, + { onRedirect: async () => {} }, + ) + + // Ensure no state exists + const entryBefore = await McpAuth.get("test-state-gen") + expect(entryBefore?.oauthState).toBeUndefined() + + // state() should generate and return a new state, not throw + const state = await provider.state() + expect(typeof state).toBe("string") + expect(state.length).toBe(64) // 32 bytes as hex + + // The generated state should be persisted + const entryAfter = await McpAuth.get("test-state-gen") + expect(entryAfter?.oauthState).toBe(state) + }, + }) +}) + +test("state() returns existing state when one is saved", async () => { + const { McpOAuthProvider } = await import("../../src/mcp/oauth-provider") + const { McpAuth } = await import("../../src/mcp/auth") + + await using tmp = await tmpdir() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const provider = new McpOAuthProvider( + "test-state-existing", + "https://example.com/mcp", + {}, + { onRedirect: async () => {} }, + ) + + // Pre-save a state + const existingState = "pre-saved-state-value" + await McpAuth.updateOAuthState("test-state-existing", existingState) + + // state() should return the existing state + const state = await provider.state() + expect(state).toBe(existingState) + }, + }) +}) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 11c943db6f..4c6eaf8b22 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -260,6 +260,7 @@ test("env variable takes precedence, config merges options", async () => { anthropic: { options: { timeout: 60000, + chunkTimeout: 15000, }, }, }, @@ -277,6 +278,7 @@ test("env variable takes precedence, config merges options", async () => { expect(providers["anthropic"]).toBeDefined() // Config options should be merged expect(providers["anthropic"].options.timeout).toBe(60000) + expect(providers["anthropic"].options.chunkTimeout).toBe(15000) }, }) }) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 512819a657..9dde1a7131 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -2479,4 +2479,144 @@ describe("ProviderTransform.variants", () => { expect(result).toEqual({}) }) }) + + describe("@jerome-benoit/sap-ai-provider-v2", () => { + test("anthropic models return thinking variants", () => { + const model = createMockModel({ + id: "sap-ai-core/anthropic--claude-sonnet-4", + providerID: "sap-ai-core", + api: { + id: "anthropic--claude-sonnet-4", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["high", "max"]) + expect(result.high).toEqual({ + thinking: { + type: "enabled", + budgetTokens: 16000, + }, + }) + expect(result.max).toEqual({ + thinking: { + type: "enabled", + budgetTokens: 31999, + }, + }) + }) + + test("anthropic 4.6 models return adaptive thinking variants", () => { + const model = createMockModel({ + id: "sap-ai-core/anthropic--claude-sonnet-4-6", + providerID: "sap-ai-core", + api: { + id: "anthropic--claude-sonnet-4-6", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"]) + expect(result.low).toEqual({ + thinking: { + type: "adaptive", + }, + effort: "low", + }) + expect(result.max).toEqual({ + thinking: { + type: "adaptive", + }, + effort: "max", + }) + }) + + test("gemini 2.5 models return thinkingConfig variants", () => { + const model = createMockModel({ + id: "sap-ai-core/gcp--gemini-2.5-pro", + providerID: "sap-ai-core", + api: { + id: "gcp--gemini-2.5-pro", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["high", "max"]) + expect(result.high).toEqual({ + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 16000, + }, + }) + expect(result.max).toEqual({ + thinkingConfig: { + includeThoughts: true, + thinkingBudget: 24576, + }, + }) + }) + + test("gpt models return reasoningEffort variants", () => { + const model = createMockModel({ + id: "sap-ai-core/azure-openai--gpt-4o", + providerID: "sap-ai-core", + api: { + id: "azure-openai--gpt-4o", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result.low).toEqual({ reasoningEffort: "low" }) + expect(result.high).toEqual({ reasoningEffort: "high" }) + }) + + test("o-series models return reasoningEffort variants", () => { + const model = createMockModel({ + id: "sap-ai-core/azure-openai--o3-mini", + providerID: "sap-ai-core", + api: { + id: "azure-openai--o3-mini", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result.low).toEqual({ reasoningEffort: "low" }) + expect(result.high).toEqual({ reasoningEffort: "high" }) + }) + + test("sonar models return empty object", () => { + const model = createMockModel({ + id: "sap-ai-core/perplexity--sonar-pro", + providerID: "sap-ai-core", + api: { + id: "perplexity--sonar-pro", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({}) + }) + + test("mistral models return empty object", () => { + const model = createMockModel({ + id: "sap-ai-core/mistral--mistral-large", + providerID: "sap-ai-core", + api: { + id: "mistral--mistral-large", + url: "https://api.ai.sap", + npm: "@jerome-benoit/sap-ai-provider-v2", + }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({}) + }) + }) }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 22dcfec355..2bb2edcd17 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1295,6 +1295,7 @@ export class Session2 extends HeyApiClient { parentID?: string title?: string permission?: PermissionRuleset + workspaceID?: string }, options?: Options, ) { @@ -1308,6 +1309,7 @@ export class Session2 extends HeyApiClient { { in: "body", key: "parentID" }, { in: "body", key: "title" }, { in: "body", key: "permission" }, + { in: "body", key: "workspaceID" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 71e075b391..a47b18db21 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1225,7 +1225,11 @@ export type ProviderConfig = { * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. */ timeout?: number | false - [key: string]: unknown | string | boolean | number | false | undefined + /** + * Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted. + */ + chunkTimeout?: number + [key: string]: unknown | string | boolean | number | false | number | undefined } } @@ -2764,6 +2768,7 @@ export type SessionCreateData = { parentID?: string title?: string permission?: PermissionRuleset + workspaceID?: string } path?: never query?: { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index d1198c11dd..c3e54a7a11 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1832,6 +1832,10 @@ }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" + }, + "workspaceID": { + "type": "string", + "pattern": "^wrk.*" } } } @@ -10108,6 +10112,12 @@ "const": false } ] + }, + "chunkTimeout": { + "description": "Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 } }, "additionalProperties": {} diff --git a/packages/web/src/content/docs/ar/ecosystem.mdx b/packages/web/src/content/docs/ar/ecosystem.mdx index 1725daffc6..e2a664a1cd 100644 --- a/packages/web/src/content/docs/ar/ecosystem.mdx +++ b/packages/web/src/content/docs/ar/ecosystem.mdx @@ -32,7 +32,7 @@ description: مشاريع وتكاملات مبنية باستخدام OpenCode. | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | تعليمات لأوامر shell غير التفاعلية - تمنع التعليق الناتج عن العمليات المعتمدة على TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | تتبع استخدام OpenCode باستخدام Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | تنظيف جداول markdown التي تنتجها LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | تحرير الكود أسرع بـ 10 مرات باستخدام Morph Fast Apply API وعلامات التحرير الكسولة | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | تحرير Fast Apply وبحث WarpGrep في قاعدة الكود وضغط السياق عبر Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | وكلاء الخلفية، وأدوات LSP/AST/MCP المعدة مسبقًا، ووكلاء مختارون، متوافق مع Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | إشعارات سطح المكتب وتنبيهات صوتية لجلسات OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | إشعارات سطح المكتب وتنبيهات صوتية لأحداث الإذن والاكتمال والخطأ | diff --git a/packages/web/src/content/docs/bs/ecosystem.mdx b/packages/web/src/content/docs/bs/ecosystem.mdx index ff0fbc1526..65a665ca9c 100644 --- a/packages/web/src/content/docs/bs/ecosystem.mdx +++ b/packages/web/src/content/docs/bs/ecosystem.mdx @@ -32,7 +32,7 @@ Također možete pogledati [awesome-opencode](https://github.com/awesome-opencod | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Upute za neinteraktivne naredbe ljuske - sprječava visi od TTY ovisnih operacija | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Pratite upotrebu OpenCode sa Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Očistite tabele umanjenja vrijednosti koje su izradili LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x brže uređivanje koda s Morph Fast Apply API-jem i markerima za lijeno uređivanje | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply uređivanje, WarpGrep pretraga koda i kompresija konteksta putem Morph-a | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Pozadinski agenti, unapred izgrađeni LSP/AST/MCP alati, kurirani agenti, kompatibilni sa Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Obavještenja na radnoj površini i zvučna upozorenja za OpenCode sesije | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Obavještenja na radnoj površini i zvučna upozorenja za dozvole, završetak i događaje greške | diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 038f253274..d2770ee209 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -244,7 +244,7 @@ You can configure the providers and models you want to use in your OpenCode conf The `small_model` option configures a separate model for lightweight tasks like title generation. By default, OpenCode tries to use a cheaper model if one is available from your provider, otherwise it falls back to your main model. -Provider options can include `timeout` and `setCacheKey`: +Provider options can include `timeout`, `chunkTimeout`, and `setCacheKey`: ```json title="opencode.json" { @@ -253,6 +253,7 @@ Provider options can include `timeout` and `setCacheKey`: "anthropic": { "options": { "timeout": 600000, + "chunkTimeout": 30000, "setCacheKey": true } } @@ -261,6 +262,7 @@ Provider options can include `timeout` and `setCacheKey`: ``` - `timeout` - Request timeout in milliseconds (default: 300000). Set to `false` to disable. +- `chunkTimeout` - Timeout in milliseconds between streamed response chunks. If no chunk arrives in time, the request is aborted. - `setCacheKey` - Ensure a cache key is always set for designated provider. You can also configure [local models](/docs/models#local). [Learn more](/docs/models). diff --git a/packages/web/src/content/docs/da/ecosystem.mdx b/packages/web/src/content/docs/da/ecosystem.mdx index 0bba6ecddd..e8382216b2 100644 --- a/packages/web/src/content/docs/da/ecosystem.mdx +++ b/packages/web/src/content/docs/da/ecosystem.mdx @@ -32,7 +32,7 @@ Du kan også tjekke [awesome-opencode](https://github.com/awesome-opencode/aweso | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruktioner til ikke-interaktive shell-kommandoer - forhindrer hænger fra TTY-afhængige operationer | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode brug med Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ryd op afmærkningstabeller produceret af LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x hurtigere koderedigering med Morph Fast Apply API og dovne redigeringsmarkører | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøgning og kontekstkomprimering via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Baggrundsagenter, præbyggede LSP/AST/MCP værktøjer, kuraterede agenter, Claude Kodekompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsmeddelelser og lydadvarsler for OpenCode-sessioner | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsmeddelelser og lydadvarsler for tilladelser, fuldførelse og fejlhændelser | diff --git a/packages/web/src/content/docs/de/ecosystem.mdx b/packages/web/src/content/docs/de/ecosystem.mdx index c9ffcf9c68..802f83af55 100644 --- a/packages/web/src/content/docs/de/ecosystem.mdx +++ b/packages/web/src/content/docs/de/ecosystem.mdx @@ -32,7 +32,7 @@ Sie können sich auch [awesome-opencode](https://github.com/awesome-opencode/awe | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Anweisungen für nicht interaktive Shell-Befehle – verhindert Abstürze bei TTY-abhängigen Vorgängen | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Verfolgen Sie die Nutzung von OpenCode mit Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Von LLMs erstellte Abschriftentabellen bereinigen | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x schnellere Codebearbeitung mit Morph Fast Apply API und Lazy-Edit-Markern | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-Bearbeitung, WarpGrep-Codesuche und Kontextkomprimierung über Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Hintergrundagenten, vorgefertigte LSP/AST/MCP-Tools, kuratierte Agenten, Claude Code-kompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop-Benachrichtigungen und akustische Warnungen für OpenCode-Sitzungen | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop-Benachrichtigungen und akustische Warnungen für Berechtigungs-, Abschluss- und Fehlerereignisse | diff --git a/packages/web/src/content/docs/es/ecosystem.mdx b/packages/web/src/content/docs/es/ecosystem.mdx index 8bb32e7065..238e8c6ffa 100644 --- a/packages/web/src/content/docs/es/ecosystem.mdx +++ b/packages/web/src/content/docs/es/ecosystem.mdx @@ -32,7 +32,7 @@ También puedes consultar [awesome-opencode](https://github.com/awesome-opencode | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrucciones para comandos de shell no interactivos: evita bloqueos de operaciones dependientes de TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Seguimiento del uso de OpenCode con Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpiar tablas de Markdown producidas por LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edición de código 10 veces más rápida con Morph Fast Apply API y marcadores de edición diferidos | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Edición Fast Apply, búsqueda de código con WarpGrep y compactación de contexto a través de Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes en segundo plano, herramientas LSP/AST/MCP prediseñadas, agentes seleccionados, compatible con Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificaciones de escritorio y alertas sonoras para sesiones OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificaciones de escritorio y alertas sonoras para eventos de permiso, finalización y error | diff --git a/packages/web/src/content/docs/fr/ecosystem.mdx b/packages/web/src/content/docs/fr/ecosystem.mdx index 2ce2c52438..d578017bf9 100644 --- a/packages/web/src/content/docs/fr/ecosystem.mdx +++ b/packages/web/src/content/docs/fr/ecosystem.mdx @@ -32,7 +32,7 @@ Vous pouvez également consulter [awesome-opencode](https://github.com/awesome-o | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions pour les commandes shell non interactives - empêche les blocages dus aux opérations dépendantes du TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Suit l'utilisation d'OpenCode avec Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Nettoie les tableaux Markdown produits par les LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Édition de code 10x plus rapide avec l'API Morph Fast Apply et des marqueurs d'édition différée | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Édition Fast Apply, recherche de code WarpGrep et compaction de contexte via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agents d'arrière-plan, outils LSP/AST/MCP pré-construits, agents sélectionnés, compatible Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifications de bureau et alertes sonores pour les sessions OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifications de bureau et alertes sonores pour les événements de permission, d'achèvement et d'erreur | diff --git a/packages/web/src/content/docs/it/ecosystem.mdx b/packages/web/src/content/docs/it/ecosystem.mdx index d2cb9c4383..093c74b13e 100644 --- a/packages/web/src/content/docs/it/ecosystem.mdx +++ b/packages/web/src/content/docs/it/ecosystem.mdx @@ -32,7 +32,7 @@ Puoi anche dare un'occhiata a [awesome-opencode](https://github.com/awesome-open | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Istruzioni per comandi shell non interattivi: evita blocchi dovuti a operazioni dipendenti da TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Traccia l'uso di OpenCode con Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Ripulisce le tabelle markdown prodotte dai LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Editing del codice 10x più veloce con Morph Fast Apply API e marker lazy edit | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Editing Fast Apply, ricerca codebase WarpGrep e compattazione del contesto tramite Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenti in background, tool LSP/AST/MCP predefiniti, agenti curati, compatibile con Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notifiche desktop e avvisi sonori per le sessioni OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notifiche desktop e avvisi sonori per eventi di permesso, completamento ed errore | diff --git a/packages/web/src/content/docs/ja/ecosystem.mdx b/packages/web/src/content/docs/ja/ecosystem.mdx index b1a24a7dbf..02f3b76411 100644 --- a/packages/web/src/content/docs/ja/ecosystem.mdx +++ b/packages/web/src/content/docs/ja/ecosystem.mdx @@ -32,7 +32,7 @@ OpenCode 関連プロジェクトをこのリストに追加したいですか? | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非対話型シェルコマンドの手順 - TTY に依存する操作によるハングの防止 | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | wakatime で OpenCode の使用状況を追跡する | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM によって生成された Markdown テーブルをクリーンアップする | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast apply API と遅延編集マーカーにより 10 倍高速なコード編集 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Morph による Fast Apply 編集、WarpGrep コードベース検索、コンテキスト圧縮 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | バックグラウンドエージェント、事前構築された LSP/AST/MCP ツール、厳選されたエージェント、Claude Code 互換 | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode セッションのデスクトップ通知とサウンドアラート | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 許可、完了、エラーイベントのデスクトップ通知とサウンドアラート | diff --git a/packages/web/src/content/docs/ko/ecosystem.mdx b/packages/web/src/content/docs/ko/ecosystem.mdx index 455459b7ca..546d7af31a 100644 --- a/packages/web/src/content/docs/ko/ecosystem.mdx +++ b/packages/web/src/content/docs/ko/ecosystem.mdx @@ -32,7 +32,7 @@ OpenCode를 기반으로 만들어진 커뮤니티 프로젝트 모음입니다. | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 비대화형 shell 명령 실행 지침을 제공해 TTY 의존 작업으로 인한 멈춤을 방지합니다. | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime으로 OpenCode 사용량을 추적합니다. | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM이 생성한 markdown 표를 정리합니다. | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API와 lazy edit marker를 활용해 코드 편집 속도를 크게 높입니다. | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Morph를 통한 Fast Apply 편집, WarpGrep 코드베이스 검색 및 컨텍스트 압축 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | background agent, 사전 구성된 LSP/AST/MCP tool, curated agent, Claude Code 호환성을 제공합니다. | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 세션에 데스크톱 알림과 사운드 알림을 제공합니다. | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | permission, 완료, 오류 이벤트에 대한 데스크톱 알림과 사운드 알림을 제공합니다. | diff --git a/packages/web/src/content/docs/nb/ecosystem.mdx b/packages/web/src/content/docs/nb/ecosystem.mdx index 2c67b9fd8e..7cd0c21ab9 100644 --- a/packages/web/src/content/docs/nb/ecosystem.mdx +++ b/packages/web/src/content/docs/nb/ecosystem.mdx @@ -32,7 +32,7 @@ Du kan også sjekke ut [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruksjoner for ikke-interaktive skallkommandoer - forhindrer heng ved TTY-avhengige operasjoner | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Spor OpenCode-bruk med Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Rydd opp i markdown-tabeller produsert av LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10 ganger raskere koderedigering med Morph Fast Apply API og lazy-redigeringsmarkører | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply-redigering, WarpGrep-kodesøk og kontekstkomprimering via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Bakgrunnsagenter, forhåndsbygde LSP/AST/MCP verktøy, kurerte agenter, Claude Code-kompatibel | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Skrivebordsvarsler og lydvarsler for OpenCode-økter | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Skrivebordsvarsler og lydvarsler for tillatelse, fullføring og feilhendelser | diff --git a/packages/web/src/content/docs/pl/ecosystem.mdx b/packages/web/src/content/docs/pl/ecosystem.mdx index 9dc4179b75..f7a9048e64 100644 --- a/packages/web/src/content/docs/pl/ecosystem.mdx +++ b/packages/web/src/content/docs/pl/ecosystem.mdx @@ -32,7 +32,7 @@ Możesz również sprawdzić [awesome-opencode](https://github.com/awesome-openc | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instrukcje dla nieinteraktywnych poleceń powłoki - zapobiega zawieszeniom operacji zależnych od TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Śledź użycie OpenCode za pomocą Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Oczyść tabele markdown generowane przez LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x szybsza edycja kodu dzięki API Morph Fast Apply i leniwym znacznikom edycji | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Edycja Fast Apply, wyszukiwanie kodu WarpGrep i kompaktowanie kontekstu przez Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agenci w tle, wbudowane narzędzia LSP/AST/MCP, wyselekcjonowani agenci, kompatybilność z Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Powiadomienia na pulpicie i alerty dźwiękowe dla sesji OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Powiadomienia na pulpicie i alerty dźwiękowe dla uprawnień, zakończeń zadań i błędów | diff --git a/packages/web/src/content/docs/pt-br/ecosystem.mdx b/packages/web/src/content/docs/pt-br/ecosystem.mdx index 1d4d0bac11..3d94334e6b 100644 --- a/packages/web/src/content/docs/pt-br/ecosystem.mdx +++ b/packages/web/src/content/docs/pt-br/ecosystem.mdx @@ -32,7 +32,7 @@ Você também pode conferir [awesome-opencode](https://github.com/awesome-openco | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instruções para comandos de shell não interativos - evita travamentos de operações dependentes de TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Acompanhe o uso do OpenCode com Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Limpe tabelas markdown produzidas por LLMs | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Edição de código 10x mais rápida com a API Morph Fast Apply e marcadores de edição preguiçosos | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Edição Fast Apply, busca de código WarpGrep e compactação de contexto via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Agentes em segundo plano, ferramentas LSP/AST/MCP pré-construídas, agentes curados, compatível com Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Notificações de desktop e alertas sonoros para sessões do OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Notificações de desktop e alertas sonoros para eventos de permissão, conclusão e erro | diff --git a/packages/web/src/content/docs/ru/ecosystem.mdx b/packages/web/src/content/docs/ru/ecosystem.mdx index e0068ead2c..81b0312bbb 100644 --- a/packages/web/src/content/docs/ru/ecosystem.mdx +++ b/packages/web/src/content/docs/ru/ecosystem.mdx @@ -32,7 +32,7 @@ description: Проекты и интеграции, созданные с по | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Инструкции для неинтерактивных shell-команд — предотвращают зависания из-за операций, зависящих от TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Отслеживайте использование OpenCode с помощью Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Очистка таблиц Markdown, созданных LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Редактирование кода в 10 раз быстрее с помощью API Morph Fast Apply и маркеров отложенного редактирования | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Редактирование Fast Apply, поиск по кодовой базе WarpGrep и сжатие контекста через Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Фоновые агенты, встроенные инструменты LSP/AST/MCP, курируемые агенты, совместимость с Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Уведомления на рабочем столе и звуковые оповещения для сеансов OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Уведомления на рабочем столе и звуковые оповещения о разрешениях, завершении и событиях ошибок | diff --git a/packages/web/src/content/docs/th/ecosystem.mdx b/packages/web/src/content/docs/th/ecosystem.mdx index a1b7e90397..ccbf2f1907 100644 --- a/packages/web/src/content/docs/th/ecosystem.mdx +++ b/packages/web/src/content/docs/th/ecosystem.mdx @@ -32,7 +32,7 @@ description: โปรเจ็กต์และการผสานรวม | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | คำแนะนำสำหรับคำสั่ง shell แบบไม่โต้ตอบ - ป้องกันการแฮงค์จากการดำเนินการที่ขึ้นอยู่กับ TTY | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | ติดตามการใช้งาน OpenCode ด้วย Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | ทำความสะอาดตาราง Markdown ที่ผลิตโดย LLM | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | การแก้ไขโค้ดเร็วขึ้น 10 เท่าด้วย Morph Fast Apply API และเครื่องหมายแก้ไขแบบ Lazy | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | การแก้ไข Fast Apply, การค้นหาโค้ดเบส WarpGrep และการบีบอัดบริบทผ่าน Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | ตัวแทนเบื้องหลัง, เครื่องมือ LSP/AST/MCP ที่สร้างไว้ล่วงหน้า, ตัวแทนที่ได้รับการดูแลจัดการ, เข้ากันได้กับ Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับเซสชัน OpenCode | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | การแจ้งเตือนบนเดสก์ท็อปและเสียงเตือนสำหรับการอนุญาต การดำเนินการเสร็จสิ้น และเหตุการณ์ข้อผิดพลาด | diff --git a/packages/web/src/content/docs/tr/ecosystem.mdx b/packages/web/src/content/docs/tr/ecosystem.mdx index ba534c70b4..ca8bc409ca 100644 --- a/packages/web/src/content/docs/tr/ecosystem.mdx +++ b/packages/web/src/content/docs/tr/ecosystem.mdx @@ -32,7 +32,7 @@ Ayrıca ekosistemi ve topluluğu bir araya getiren [awesome-opencode](https://gi | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Etkileşimli olmayan kabuk komutları için talimatlar - TTY bağımlı işlemlerden kaynaklanan takılmaları önler | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Wakatime ile OpenCode kullanımını takip edin | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | LLM'ler tarafından üretilen markdown tablolarını temizleyin | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | Morph Fast Apply API ve tembel düzenleme işaretçileriyle 10 kat daha hızlı kod düzenleme | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply düzenleme, WarpGrep kod tabanı araması ve Morph ile bağlam sıkıştırma | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Arka plan aracıları, hazır LSP/AST/MCP araçları, seçilmiş aracılar, Claude Code uyumlu | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode oturumları için masaüstü bildirimleri ve sesli uyarılar | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | İzin, tamamlanma ve hata olayları için masaüstü bildirimleri ve sesli uyarılar | diff --git a/packages/web/src/content/docs/zh-cn/ecosystem.mdx b/packages/web/src/content/docs/zh-cn/ecosystem.mdx index d222903d34..aaa6a3ede4 100644 --- a/packages/web/src/content/docs/zh-cn/ecosystem.mdx +++ b/packages/web/src/content/docs/zh-cn/ecosystem.mdx @@ -32,7 +32,7 @@ description: 基于 OpenCode 构建的项目与集成。 | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非交互式 shell 命令指令——防止依赖 TTY 的操作导致挂起 | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追踪 OpenCode 的使用情况 | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 通过 Morph Fast Apply API 和惰性编辑标记实现 10 倍更快的代码编辑 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | 通过 Morph 提供 Fast Apply 编辑、WarpGrep 代码搜索和上下文压缩 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 后台代理、预构建的 LSP/AST/MCP 工具、精选代理,兼容 Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 会话的桌面通知和声音提醒 | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 针对权限请求、任务完成和错误事件的桌面通知与声音提醒 | diff --git a/packages/web/src/content/docs/zh-tw/ecosystem.mdx b/packages/web/src/content/docs/zh-tw/ecosystem.mdx index b44dfdacfd..99067acf46 100644 --- a/packages/web/src/content/docs/zh-tw/ecosystem.mdx +++ b/packages/web/src/content/docs/zh-tw/ecosystem.mdx @@ -32,7 +32,7 @@ description: 基於 OpenCode 建置的專案與整合。 | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | 非互動式 shell 指令說明——防止依賴 TTY 的操作導致卡住 | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | 使用 Wakatime 追蹤 OpenCode 的使用情況 | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | 清理 LLM 生成的 Markdown 表格 | -| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 透過 Morph Fast Apply API 和惰性編輯標記實現 10 倍更快的程式碼編輯 | +| [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | 透過 Morph 提供 Fast Apply 編輯、WarpGrep 程式碼搜尋和上下文壓縮 | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | 背景代理、預建置的 LSP/AST/MCP 工具、精選代理,相容 Claude Code | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | OpenCode 工作階段的桌面通知和聲音提醒 | | [opencode-notifier](https://github.com/mohak34/opencode-notifier) | 針對權限請求、任務完成和錯誤事件的桌面通知與聲音提醒 |