From e5b33f8a5e249f8b3de20afaaedf573bdabb3b5d Mon Sep 17 00:00:00 2001 From: Goni Zahavy Date: Thu, 29 Jan 2026 02:47:09 +0200 Subject: [PATCH 01/39] fix(opencode): add `AbortSignal` support to `Ripgrep.files()` and `GlobTool` (#10833) --- packages/opencode/src/file/ripgrep.ts | 12 ++++++++++-- packages/opencode/src/tool/glob.ts | 1 + packages/opencode/src/tool/ls.ts | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 0d18173565..0f6889779a 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -209,7 +209,10 @@ export namespace Ripgrep { hidden?: boolean follow?: boolean maxDepth?: number + signal?: AbortSignal }) { + input.signal?.throwIfAborted() + const args = [await filepath(), "--files", "--glob=!.git/*"] if (input.follow !== false) args.push("--follow") if (input.hidden !== false) args.push("--hidden") @@ -235,6 +238,7 @@ export namespace Ripgrep { stdout: "pipe", stderr: "ignore", maxBuffer: 1024 * 1024 * 20, + signal: input.signal, }) const reader = proc.stdout.getReader() @@ -243,6 +247,8 @@ export namespace Ripgrep { try { while (true) { + input.signal?.throwIfAborted() + const { done, value } = await reader.read() if (done) break @@ -261,11 +267,13 @@ export namespace Ripgrep { reader.releaseLock() await proc.exited } + + input.signal?.throwIfAborted() } - export async function tree(input: { cwd: string; limit?: number }) { + export async function tree(input: { cwd: string; limit?: number; signal?: AbortSignal }) { log.info("tree", input) - const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd })) + const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd, signal: input.signal })) interface Node { path: string[] children: Node[] diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index dda57f6ee1..6943795f88 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -38,6 +38,7 @@ export const GlobTool = Tool.define("glob", { for await (const file of Ripgrep.files({ cwd: search, glob: [params.pattern], + signal: ctx.abort, })) { if (files.length >= limit) { truncated = true diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts index cc3d750078..b848e969b7 100644 --- a/packages/opencode/src/tool/ls.ts +++ b/packages/opencode/src/tool/ls.ts @@ -56,7 +56,7 @@ export const ListTool = Tool.define("list", { const ignoreGlobs = IGNORE_PATTERNS.map((p) => `!${p}*`).concat(params.ignore?.map((p) => `!${p}`) || []) const files = [] - for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs })) { + for await (const file of Ripgrep.files({ cwd: searchPath, glob: ignoreGlobs, signal: ctx.abort })) { files.push(file) if (files.length >= LIMIT) break } From 7ad165fbdce352786c52b99503cc6f34422e3084 Mon Sep 17 00:00:00 2001 From: Julian Coy Date: Wed, 28 Jan 2026 20:41:50 -0500 Subject: [PATCH 02/39] fix(typescript errors): remove duplicate keys causing typescript failures (#11071) --- packages/app/src/i18n/zh.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index ef69855b12..2266c109b0 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -529,9 +529,6 @@ export const dict = { "settings.general.row.releaseNotes.title": "发行说明", "settings.general.row.releaseNotes.description": "更新后显示“新功能”弹窗", - "settings.general.row.releaseNotes.title": "发行说明", - "settings.general.row.releaseNotes.description": "更新后显示“新功能”弹窗", - "settings.updates.row.startup.title": "启动时检查更新", "settings.updates.row.startup.description": "在 OpenCode 启动时自动检查更新", "settings.updates.row.check.title": "检查更新", From 0c8de47f7d8ec04400c45d491c45807b6ddacb4a Mon Sep 17 00:00:00 2001 From: Alex Yaroshuk <34632190+alexyaroshuk@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:45:12 +0800 Subject: [PATCH 03/39] fix(app): file tabs - auto scroll on open & scroll via mouse wheel (#11070) --- packages/app/src/pages/session.tsx | 57 +++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 87ebf6db72..a3056102e4 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -2183,7 +2183,62 @@ export default function Page() {
- + { + let scrollTimeout: number | undefined + let prevScrollWidth = el.scrollWidth + let prevContextOpen = contextOpen() + + const handler = () => { + if (scrollTimeout !== undefined) clearTimeout(scrollTimeout) + scrollTimeout = window.setTimeout(() => { + const scrollWidth = el.scrollWidth + const clientWidth = el.clientWidth + const currentContextOpen = contextOpen() + + // Only scroll when a tab is added (width increased), not on removal + if (scrollWidth > prevScrollWidth) { + if (!prevContextOpen && currentContextOpen) { + // Context tab was opened, scroll to first + el.scrollTo({ + left: 0, + behavior: "smooth", + }) + } else if (scrollWidth > clientWidth) { + // File tab was added, scroll to rightmost + el.scrollTo({ + left: scrollWidth - clientWidth, + behavior: "smooth", + }) + } + } + // When width decreases (tab removed), don't scroll - let browser handle it naturally + + prevScrollWidth = scrollWidth + prevContextOpen = currentContextOpen + }, 0) + } + + const wheelHandler = (e: WheelEvent) => { + // Enable horizontal scrolling with mouse wheel + if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) { + el.scrollLeft += e.deltaY > 0 ? 50 : -50 + e.preventDefault() + } + } + + el.addEventListener("wheel", wheelHandler, { passive: false }) + + const observer = new MutationObserver(handler) + observer.observe(el, { childList: true }) + + onCleanup(() => { + el.removeEventListener("wheel", wheelHandler) + observer.disconnect() + if (scrollTimeout !== undefined) clearTimeout(scrollTimeout) + }) + }} + > Date: Wed, 28 Jan 2026 18:46:53 -0600 Subject: [PATCH 04/39] tweak: add ctx.abort to grep tool --- packages/opencode/src/tool/grep.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 097dedf4aa..6cb70d022e 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -54,6 +54,7 @@ export const GrepTool = Tool.define("grep", { const proc = Bun.spawn([rgPath, ...args], { stdout: "pipe", stderr: "pipe", + signal: ctx.abort, }) const output = await new Response(proc.stdout).text() From a9e757b5bb0c8a31fb498622091c0e3d1c9645e6 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 29 Jan 2026 10:14:42 +0800 Subject: [PATCH 05/39] fix(app): types --- packages/app/src/pages/session.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index a3056102e4..b346fa6928 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -2184,7 +2184,7 @@ export default function Page() {
{ + ref={(el: HTMLDivElement) => { let scrollTimeout: number | undefined let prevScrollWidth = el.scrollWidth let prevContextOpen = contextOpen() From 8cdb82038a465dece6e9482d4e10adbf7719b6bb Mon Sep 17 00:00:00 2001 From: Babou <42550761+bbabou@users.noreply.github.com> Date: Thu, 29 Jan 2026 03:20:13 +0100 Subject: [PATCH 06/39] docs: update experimental environment variables in CLI docs (#11030) --- packages/opencode/src/flag/flag.ts | 1 - packages/web/src/content/docs/cli.mdx | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 9dea041e46..9084bf4443 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -38,7 +38,6 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT") export const OPENCODE_ENABLE_EXA = truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA") - export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH") export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS") export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX") export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT") diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx index d1d11ed70d..7fb948f505 100644 --- a/packages/web/src/content/docs/cli.mdx +++ b/packages/web/src/content/docs/cli.mdx @@ -585,9 +585,13 @@ These environment variables enable experimental features that may change or be r | `OPENCODE_EXPERIMENTAL` | boolean | Enable all experimental features | | `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Enable icon discovery | | `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Disable copy on select in TUI | -| `OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH` | number | Max output length for bash commands | | `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Default timeout for bash commands in ms | | `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` | number | Max output tokens for LLM responses | | `OPENCODE_EXPERIMENTAL_FILEWATCHER` | boolean | Enable file watcher for entire dir | | `OPENCODE_EXPERIMENTAL_OXFMT` | boolean | Enable oxfmt formatter | | `OPENCODE_EXPERIMENTAL_LSP_TOOL` | boolean | Enable experimental LSP tool | +| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER` | boolean | Disable file watcher | +| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features | +| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable experimental LSP type checking | +| `OPENCODE_EXPERIMENTAL_MARKDOWN` | boolean | Enable experimental markdown features | +| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode | From a7d7f5bb07eb197ce91cd7267d7b0103daf5613e Mon Sep 17 00:00:00 2001 From: Devin Griffin <31415269+DNGriffin@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:20:34 -0600 Subject: [PATCH 07/39] fix(app): make settings more responsive for mobile and small web/desktop (#10775) --- packages/app/src/components/settings-general.tsx | 6 +++--- packages/app/src/components/settings-keybinds.tsx | 2 +- packages/app/src/components/settings-models.tsx | 4 ++-- packages/app/src/components/settings-permissions.tsx | 10 +++++----- packages/app/src/components/settings-providers.tsx | 6 +++--- packages/ui/src/components/tabs.css | 9 +++++++-- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 180a99c739..08d68b1cbf 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -130,7 +130,7 @@ export const SettingsGeneral: Component = () => { const soundOptions = [...SOUND_OPTIONS] return ( -
+

{language.t("settings.tab.general")}

@@ -406,8 +406,8 @@ interface SettingsRowProps { const SettingsRow: Component = (props) => { return ( -
-
+
+
{props.title} {props.description}
diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index a2cd3280c8..453d700490 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -352,7 +352,7 @@ export const SettingsKeybinds: Component = () => { }) return ( -
+
diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index 0cb6deee1f..062d17330f 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -39,7 +39,7 @@ export const SettingsModels: Component = () => { }) return ( -
+

{language.t("settings.models.title")}

@@ -99,7 +99,7 @@ export const SettingsModels: Component = () => { {(item) => { const key = { providerID: item.provider.id, modelID: item.id } return ( -
+
{item.name}
diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx index bcbf7b68fa..87a1a7e2a8 100644 --- a/packages/app/src/components/settings-permissions.tsx +++ b/packages/app/src/components/settings-permissions.tsx @@ -175,14 +175,14 @@ export const SettingsPermissions: Component = () => { return (
-
-
+
+

{language.t("settings.permissions.title")}

{language.t("settings.permissions.description")}

-
+

{language.t("settings.permissions.section.tools")}

@@ -217,8 +217,8 @@ interface SettingsRowProps { const SettingsRow: Component = (props) => { return ( -
-
+
+
{props.title} {props.description}
diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index abc2bee77d..036e46935f 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -115,7 +115,7 @@ export const SettingsProviders: Component = () => { } return ( -
+

{language.t("settings.providers.title")}

@@ -136,7 +136,7 @@ export const SettingsProviders: Component = () => { > {(item) => ( -
+
{item.name} @@ -166,7 +166,7 @@ export const SettingsProviders: Component = () => {
{(item) => ( -
+
diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index 6b57f0c04b..56c3e083f5 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -375,8 +375,13 @@ &[data-variant="settings"] { [data-slot="tabs-list"] { - width: 200px; - min-width: 200px; + width: 150px; + min-width: 150px; + + @media (min-width: 640px) { + width: 200px; + min-width: 200px; + } padding: 12px; gap: 0; background-color: var(--background-base); From d8fe12aafcbf86534c43c8153a1952cda57f8c14 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 29 Jan 2026 03:21:26 +0000 Subject: [PATCH 08/39] chore: generate --- packages/app/src/components/settings-general.tsx | 2 +- packages/app/src/components/settings-keybinds.tsx | 2 +- packages/app/src/components/settings-models.tsx | 2 +- packages/app/src/components/settings-permissions.tsx | 2 +- packages/app/src/components/settings-providers.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 08d68b1cbf..3b08652bbb 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -130,7 +130,7 @@ export const SettingsGeneral: Component = () => { const soundOptions = [...SOUND_OPTIONS] return ( -
+

{language.t("settings.tab.general")}

diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index 453d700490..393da0c2ab 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -352,7 +352,7 @@ export const SettingsKeybinds: Component = () => { }) return ( -
+
diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index 062d17330f..1807d561ea 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -39,7 +39,7 @@ export const SettingsModels: Component = () => { }) return ( -
+

{language.t("settings.models.title")}

diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx index 87a1a7e2a8..7dd43a7075 100644 --- a/packages/app/src/components/settings-permissions.tsx +++ b/packages/app/src/components/settings-permissions.tsx @@ -175,7 +175,7 @@ export const SettingsPermissions: Component = () => { return (
-
+

{language.t("settings.permissions.title")}

{language.t("settings.permissions.description")}

diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index 036e46935f..aa43cee4f2 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -115,7 +115,7 @@ export const SettingsProviders: Component = () => { } return ( -
+

{language.t("settings.providers.title")}

From 427ef95f7de29b0485fbbdd507b38f2f451dbfe6 Mon Sep 17 00:00:00 2001 From: tan Date: Thu, 29 Jan 2026 11:21:49 +0800 Subject: [PATCH 09/39] fix(opencode): allow media-src data: URL for small audio files (#11082) --- packages/opencode/src/server/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index e6afc563be..1d832d77a2 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -539,7 +539,7 @@ export namespace Server { }) response.headers.set( "Content-Security-Policy", - "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' data:", + "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:", ) return response }) as unknown as Hono, From b937fe94500dc521366e0ab5d441731c8e90b375 Mon Sep 17 00:00:00 2001 From: Saba Tchikhinashvili <38084512+saba-ch@users.noreply.github.com> Date: Thu, 29 Jan 2026 04:26:26 +0100 Subject: [PATCH 10/39] fix(provider): include providerID in SDK cache key (#11020) Co-authored-by: Claude Opus 4.5 --- packages/opencode/src/provider/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index ee7ee75c9f..27ff5475db 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -977,7 +977,7 @@ export namespace Provider { ...model.headers, } - const key = Bun.hash.xxHash32(JSON.stringify({ npm: model.api.npm, options })) + const key = Bun.hash.xxHash32(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options })) const existing = s.sdk.get(key) if (existing) return existing From 870c38a6aa61e821f90d7d17b100db82c07188fa Mon Sep 17 00:00:00 2001 From: ideallove <20013535+ideallove@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:28:15 +0800 Subject: [PATCH 11/39] fix: maxOutputTokens was accidentally hardcoded to undefined (#10995) --- packages/opencode/src/session/llm.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index d651308032..033e4862ce 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -150,7 +150,14 @@ export namespace LLM { }, ) - const maxOutputTokens = isCodex ? undefined : undefined + const maxOutputTokens = isCodex + ? undefined + : ProviderTransform.maxOutputTokens( + input.model.api.npm, + params.options, + input.model.limit.output, + OUTPUT_TOKEN_MAX, + ) log.info("max_output_tokens", { tokens: ProviderTransform.maxOutputTokens( input.model.api.npm, From 29ea9fcf2545d4dbea5c51c7d02b955dd57dc350 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 28 Jan 2026 21:55:50 -0600 Subject: [PATCH 12/39] fix: ensure variants for copilot models work w/ maxTokens being set --- packages/opencode/src/provider/transform.ts | 4 ++-- packages/opencode/src/session/llm.ts | 13 ------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 39eef6c916..57042d817c 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -428,13 +428,13 @@ export namespace ProviderTransform { high: { thinking: { type: "enabled", - budgetTokens: 16000, + budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)), }, }, max: { thinking: { type: "enabled", - budgetTokens: 31999, + budgetTokens: Math.min(31_999, model.limit.output - 1), }, }, } diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 033e4862ce..1e409b03fe 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -158,19 +158,6 @@ export namespace LLM { input.model.limit.output, OUTPUT_TOKEN_MAX, ) - log.info("max_output_tokens", { - tokens: ProviderTransform.maxOutputTokens( - input.model.api.npm, - params.options, - input.model.limit.output, - OUTPUT_TOKEN_MAX, - ), - modelOptions: params.options, - outputLimit: input.model.limit.output, - }) - // tokens = 32000 - // outputLimit = 64000 - // modelOptions={"reasoningEffort":"minimal"} const tools = await resolveTools(input) From 9cfdbbb1afa2f183f3fb0507457297b317261990 Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 29 Jan 2026 04:00:39 +0000 Subject: [PATCH 13/39] release: v1.1.41 --- bun.lock | 30 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 4 ++-- packages/sdk/js/package.json | 4 ++-- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 2b07df6ce9..47898958d3 100644 --- a/bun.lock +++ b/bun.lock @@ -23,7 +23,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -73,7 +73,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -107,7 +107,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -134,7 +134,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -158,7 +158,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -182,7 +182,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -212,7 +212,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -241,7 +241,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -257,7 +257,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.1.40", + "version": "1.1.41", "bin": { "opencode": "./bin/opencode", }, @@ -361,7 +361,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -381,7 +381,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.1.40", + "version": "1.1.41", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -392,7 +392,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -405,7 +405,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -447,7 +447,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "zod": "catalog:", }, @@ -458,7 +458,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 887b6498cc..ea46539a81 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.1.40", + "version": "1.1.41", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 4bc1d0ef85..8e0f777ff9 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.1.40", + "version": "1.1.41", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 8e63786938..a52193368f 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.1.40", + "version": "1.1.41", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index b3836dcf83..cbdd9d0eb4 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.1.40", + "version": "1.1.41", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 4499061c72..384c5c6bcf 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.1.40", + "version": "1.1.41", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index e960d21917..7314c0a524 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.1.40", + "version": "1.1.41", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 6eef4b810c..1d0156168f 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.1.40", + "version": "1.1.41", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 26d8045edb..6aaf428afc 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.1.40" +version = "1.1.41" 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.1.40/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.40/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/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.1.40/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/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.1.40/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index c48548cb76..1e7947e287 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.1.40", + "version": "1.1.41", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d3a50bdd05..04351c1e8d 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.1.40", + "version": "1.1.41", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 574f17a7f4..0cd061061f 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.1.40", + "version": "1.1.41", "type": "module", "license": "MIT", "scripts": { @@ -25,4 +25,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 4b4faebab2..20d1c4b23b 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.1.40", + "version": "1.1.41", "type": "module", "license": "MIT", "scripts": { @@ -30,4 +30,4 @@ "publishConfig": { "directory": "dist" } -} +} \ No newline at end of file diff --git a/packages/slack/package.json b/packages/slack/package.json index 0ac4e9b07b..97076356ba 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.1.40", + "version": "1.1.41", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 7cba4c66f9..b6ed6ee21b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.1.40", + "version": "1.1.41", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index f8360df7ed..28e38dda81 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.1.40", + "version": "1.1.41", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index c3b8d0b750..3c7e064766 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.1.40", + "version": "1.1.41", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 178ec913c6..e183e9f00e 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.1.40", + "version": "1.1.41", "publisher": "sst-dev", "repository": { "type": "git", From b938e1ea991f4221f0295741b46d510bb688242f Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 28 Jan 2026 22:02:07 -0600 Subject: [PATCH 14/39] test: fix test --- packages/opencode/test/provider/transform.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 037083d5e3..1d69a2a295 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -1056,8 +1056,8 @@ describe("ProviderTransform.variants", () => { cache: { read: 0.0001, write: 0.0002 }, }, limit: { - context: 128000, - output: 8192, + context: 200_000, + output: 64_000, }, status: "active", options: {}, From efc9303b27ea6e95e76809d22eaca755964773f5 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 29 Jan 2026 04:03:01 +0000 Subject: [PATCH 15/39] chore: generate --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 0cd061061f..e599b46f6d 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -25,4 +25,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 20d1c4b23b..59963d1581 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -30,4 +30,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From f40bdd1ac31c10d81b40b99f402f833ee26f095e Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Thu, 29 Jan 2026 11:03:44 +0700 Subject: [PATCH 16/39] feat(cli): include cache tokens in stats (#10582) --- packages/opencode/src/cli/cmd/stats.ts | 32 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts index d78c4f0abd..9239bb90a6 100644 --- a/packages/opencode/src/cli/cmd/stats.ts +++ b/packages/opencode/src/cli/cmd/stats.ts @@ -27,6 +27,10 @@ interface SessionStats { tokens: { input: number output: number + cache: { + read: number + write: number + } } cost: number } @@ -191,6 +195,10 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin tokens: { input: number output: number + cache: { + read: number + write: number + } } cost: number } @@ -204,7 +212,7 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin if (!sessionModelUsage[modelKey]) { sessionModelUsage[modelKey] = { messages: 0, - tokens: { input: 0, output: 0 }, + tokens: { input: 0, output: 0, cache: { read: 0, write: 0 } }, cost: 0, } } @@ -221,6 +229,8 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin sessionModelUsage[modelKey].tokens.input += message.info.tokens.input || 0 sessionModelUsage[modelKey].tokens.output += (message.info.tokens.output || 0) + (message.info.tokens.reasoning || 0) + sessionModelUsage[modelKey].tokens.cache.read += message.info.tokens.cache?.read || 0 + sessionModelUsage[modelKey].tokens.cache.write += message.info.tokens.cache?.write || 0 } } @@ -235,7 +245,12 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin messageCount: messages.length, sessionCost, sessionTokens, - sessionTotalTokens: sessionTokens.input + sessionTokens.output + sessionTokens.reasoning, + sessionTotalTokens: + sessionTokens.input + + sessionTokens.output + + sessionTokens.reasoning + + sessionTokens.cache.read + + sessionTokens.cache.write, sessionToolUsage, sessionModelUsage, earliestTime: cutoffTime > 0 ? session.time.updated : session.time.created, @@ -266,13 +281,15 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin if (!stats.modelUsage[model]) { stats.modelUsage[model] = { messages: 0, - tokens: { input: 0, output: 0 }, + tokens: { input: 0, output: 0, cache: { read: 0, write: 0 } }, cost: 0, } } stats.modelUsage[model].messages += usage.messages stats.modelUsage[model].tokens.input += usage.tokens.input stats.modelUsage[model].tokens.output += usage.tokens.output + stats.modelUsage[model].tokens.cache.read += usage.tokens.cache.read + stats.modelUsage[model].tokens.cache.write += usage.tokens.cache.write stats.modelUsage[model].cost += usage.cost } } @@ -286,7 +303,12 @@ export async function aggregateSessionStats(days?: number, projectFilter?: strin } stats.days = effectiveDays stats.costPerDay = stats.totalCost / effectiveDays - const totalTokens = stats.totalTokens.input + stats.totalTokens.output + stats.totalTokens.reasoning + const totalTokens = + stats.totalTokens.input + + stats.totalTokens.output + + stats.totalTokens.reasoning + + stats.totalTokens.cache.read + + stats.totalTokens.cache.write stats.tokensPerSession = filteredSessions.length > 0 ? totalTokens / filteredSessions.length : 0 sessionTotalTokens.sort((a, b) => a - b) const mid = Math.floor(sessionTotalTokens.length / 2) @@ -353,6 +375,8 @@ export function displayStats(stats: SessionStats, toolLimit?: number, modelLimit console.log(renderRow(" Messages", usage.messages.toLocaleString())) console.log(renderRow(" Input Tokens", formatNumber(usage.tokens.input))) console.log(renderRow(" Output Tokens", formatNumber(usage.tokens.output))) + console.log(renderRow(" Cache Read", formatNumber(usage.tokens.cache.read))) + console.log(renderRow(" Cache Write", formatNumber(usage.tokens.cache.write))) console.log(renderRow(" Cost", `$${usage.cost.toFixed(4)}`)) console.log("├────────────────────────────────────────────────────────┤") } From 121016af812710eb53839d782d0d52710e64973b Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 28 Jan 2026 22:28:48 -0600 Subject: [PATCH 17/39] ci: adjust team --- script/changelog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/script/changelog.ts b/script/changelog.ts index 388c097302..ace579ee4b 100755 --- a/script/changelog.ts +++ b/script/changelog.ts @@ -15,6 +15,7 @@ export const team = [ "adamdotdevin", "iamdavidhill", "opencode-agent[bot]", + "R44VC0RP", ] export async function getLatestRelease() { From 58ba486375b20c8738a0f1e6afef38f33ca8cd57 Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Wed, 28 Jan 2026 23:53:37 -0500 Subject: [PATCH 18/39] guard destroyed input field in timeout --- packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index 8ff5a3b23b..bd1de7d4de 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -241,7 +241,11 @@ export function DialogSelect(props: DialogSelectProps) { focusedTextColor={theme.textMuted} ref={(r) => { input = r - setTimeout(() => input.focus(), 1) + setTimeout(() => { + if (!input) return + if (input.isDestroyed) return + input.focus() + }, 1) }} placeholder={props.placeholder ?? "Search"} /> From e84d92da2865ff1c71a16fb50ff7b71d6be2369d Mon Sep 17 00:00:00 2001 From: Ariane Emory <97994360+ariane-emory@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:08:35 -0500 Subject: [PATCH 19/39] feat: Sequential numbering for forked session titles (Issue #10105) (#10321) --- packages/opencode/src/session/index.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index fb0836bfb7..87cf3a0820 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -39,6 +39,16 @@ export namespace Session { ).test(title) } + function getForkedTitle(title: string): string { + const match = title.match(/^(.+) \(fork #(\d+)\)$/) + if (match) { + const base = match[1] + const num = parseInt(match[2], 10) + return `${base} (fork #${num + 1})` + } + return `${title} (fork #1)` + } + export const Info = z .object({ id: Identifier.schema("session"), @@ -151,8 +161,12 @@ export namespace Session { messageID: Identifier.schema("message").optional(), }), async (input) => { + const original = await get(input.sessionID) + if (!original) throw new Error("session not found") + const title = getForkedTitle(original.title) const session = await createNext({ directory: Instance.directory, + title, }) const msgs = await messages({ sessionID: input.sessionID }) const idMap = new Map() From 41ea4694db7636ba184d238fd2a00deb770f9c0b Mon Sep 17 00:00:00 2001 From: Sebastian Herrlinger Date: Thu, 29 Jan 2026 00:17:34 -0500 Subject: [PATCH 20/39] more timeout race guards --- .../opencode/src/cli/cmd/tui/component/prompt/index.tsx | 7 +++++++ packages/opencode/src/cli/cmd/tui/context/keybind.tsx | 5 ++--- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 3 ++- .../opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx | 1 + packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) 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 e19c8b7098..caa1303229 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -93,8 +93,11 @@ export function Prompt(props: PromptProps) { let promptPartTypeId = 0 sdk.event.on(TuiEvent.PromptAppend.type, (evt) => { + if (!input || input.isDestroyed) return input.insertText(evt.properties.text) setTimeout(() => { + // setTimeout is a workaround and needs to be addressed properly + if (!input || input.isDestroyed) return input.getLayoutNode().markDirty() input.gotoBufferEnd() renderer.requestRender() @@ -924,6 +927,8 @@ export function Prompt(props: PromptProps) { // Force layout update and render for the pasted content setTimeout(() => { + // setTimeout is a workaround and needs to be addressed properly + if (!input || input.isDestroyed) return input.getLayoutNode().markDirty() renderer.requestRender() }, 0) @@ -935,6 +940,8 @@ export function Prompt(props: PromptProps) { } props.ref?.(ref) setTimeout(() => { + // setTimeout is a workaround and needs to be addressed properly + if (!input || input.isDestroyed) return input.cursorColor = theme.text }, 0) }} diff --git a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx index 4c82e594c3..0dbbbc6f9e 100644 --- a/packages/opencode/src/cli/cmd/tui/context/keybind.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/keybind.tsx @@ -34,9 +34,8 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex timeout = setTimeout(() => { if (!store.leader) return leader(false) - if (focus) { - focus.focus() - } + if (!focus || focus.isDestroyed) return + focus.focus() }, 2000) return } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 66dac85eca..d36a7d2099 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -275,7 +275,8 @@ export function Session() { function toBottom() { setTimeout(() => { - if (scroll) scroll.scrollTo(scroll.scrollHeight) + if (!scroll || scroll.isDestroyed) return + scroll.scrollTo(scroll.scrollHeight) }, 50) } diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx index 90699e1f0b..867ed68100 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-export-options.tsx @@ -68,6 +68,7 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { onMount(() => { dialog.setSize("medium") setTimeout(() => { + if (!textarea || textarea.isDestroyed) return textarea.focus() }, 1) textarea.gotoLineEnd() diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx index 1b9acb5898..b296524124 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-prompt.tsx @@ -27,6 +27,7 @@ export function DialogPrompt(props: DialogPromptProps) { onMount(() => { dialog.setSize("medium") setTimeout(() => { + if (!textarea || textarea.isDestroyed) return textarea.focus() }, 1) textarea.gotoLineEnd() From 0fabdccf11aeaf62d60f6be3c10869283021ffb9 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 28 Jan 2026 23:40:44 -0600 Subject: [PATCH 21/39] fix: ensure that kimi doesnt have fake variants available --- packages/opencode/src/provider/transform.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 57042d817c..0f40cd89de 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -319,7 +319,14 @@ export namespace ProviderTransform { if (!model.capabilities.reasoning) return {} const id = model.id.toLowerCase() - if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {} + if ( + id.includes("deepseek") || + id.includes("minimax") || + id.includes("glm") || + id.includes("mistral") || + id.includes("kimi") + ) + return {} // see: https://docs.x.ai/docs/guides/reasoning#control-how-hard-the-model-thinks if (id.includes("grok") && id.includes("grok-3-mini")) { From 33c5c100ff271f639f998079e380b3957713b63c Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:40:59 -0600 Subject: [PATCH 22/39] fix: frontmatter was adding newlines in some cases causing invalid model ids (#11095) Co-authored-by: aptdnfapt --- packages/opencode/src/config/markdown.ts | 29 +++++++----- .../test/config/fixtures/markdown-header.md | 11 +++++ .../test/config/fixtures/weird-model-id.md | 13 ++++++ .../opencode/test/config/markdown.test.ts | 46 +++++++++++++++++-- 4 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 packages/opencode/test/config/fixtures/markdown-header.md create mode 100644 packages/opencode/test/config/fixtures/weird-model-id.md diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index d1eeeac382..4cd17746c5 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/packages/opencode/src/config/markdown.ts @@ -14,7 +14,9 @@ export namespace ConfigMarkdown { return Array.from(template.matchAll(SHELL_REGEX)) } - export function preprocessFrontmatter(content: string): string { + // other coding agents like claude code allow invalid yaml in their + // frontmatter, we need to fallback to a more permissive parser for those cases + export function fallbackSanitization(content: string): string { const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/) if (!match) return content @@ -53,7 +55,7 @@ export namespace ConfigMarkdown { // if value contains a colon, convert to block scalar if (value.includes(":")) { - result.push(`${key}: |`) + result.push(`${key}: |-`) result.push(` ${value}`) continue } @@ -66,20 +68,23 @@ export namespace ConfigMarkdown { } export async function parse(filePath: string) { - const raw = await Bun.file(filePath).text() - const template = preprocessFrontmatter(raw) + const template = await Bun.file(filePath).text() try { const md = matter(template) return md - } catch (err) { - throw new FrontmatterError( - { - path: filePath, - message: `${filePath}: Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`, - }, - { cause: err }, - ) + } catch { + try { + return matter(fallbackSanitization(template)) + } catch (err) { + throw new FrontmatterError( + { + path: filePath, + message: `${filePath}: Failed to parse YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`, + }, + { cause: err }, + ) + } } } diff --git a/packages/opencode/test/config/fixtures/markdown-header.md b/packages/opencode/test/config/fixtures/markdown-header.md new file mode 100644 index 0000000000..d5af1f1c23 --- /dev/null +++ b/packages/opencode/test/config/fixtures/markdown-header.md @@ -0,0 +1,11 @@ +# Response Formatting Requirements + +Always structure your responses using clear markdown formatting: + +- By default don't put information into tables for questions (but do put information into tables when creating or updating files) +- Use headings (##, ###) to organise sections, always +- Use bullet points or numbered lists for multiple items +- Use code blocks with language tags for any code +- Use **bold** for key terms and emphasis +- Use tables when comparing options or listing structured data +- Break long responses into logical sections with headings diff --git a/packages/opencode/test/config/fixtures/weird-model-id.md b/packages/opencode/test/config/fixtures/weird-model-id.md new file mode 100644 index 0000000000..bb02b0650f --- /dev/null +++ b/packages/opencode/test/config/fixtures/weird-model-id.md @@ -0,0 +1,13 @@ +--- +description: General coding and planning agent +mode: subagent +model: synthetic/hf:zai-org/GLM-4.7 +tools: + write: true + read: true + edit: true +stuff: > + This is some stuff +--- + +Strictly follow da rules diff --git a/packages/opencode/test/config/markdown.test.ts b/packages/opencode/test/config/markdown.test.ts index b4263ee6b5..c6133317e2 100644 --- a/packages/opencode/test/config/markdown.test.ts +++ b/packages/opencode/test/config/markdown.test.ts @@ -104,7 +104,7 @@ describe("ConfigMarkdown: frontmatter parsing", async () => { }) test("should extract occupation field with colon in value", () => { - expect(parsed.data.occupation).toBe("This man has the following occupation: Software Engineer\n") + expect(parsed.data.occupation).toBe("This man has the following occupation: Software Engineer") }) test("should extract title field with single quotes", () => { @@ -128,15 +128,15 @@ describe("ConfigMarkdown: frontmatter parsing", async () => { }) test("should extract URL with port", () => { - expect(parsed.data.url).toBe("https://example.com:8080/path?query=value\n") + expect(parsed.data.url).toBe("https://example.com:8080/path?query=value") }) test("should extract time with colons", () => { - expect(parsed.data.time).toBe("The time is 12:30:00 PM\n") + expect(parsed.data.time).toBe("The time is 12:30:00 PM") }) test("should extract value with multiple colons", () => { - expect(parsed.data.nested).toBe("First: Second: Third: Fourth\n") + expect(parsed.data.nested).toBe("First: Second: Third: Fourth") }) test("should preserve already double-quoted values with colons", () => { @@ -148,7 +148,7 @@ describe("ConfigMarkdown: frontmatter parsing", async () => { }) test("should extract value with quotes and colons mixed", () => { - expect(parsed.data.mixed).toBe('He said "hello: world" and then left\n') + expect(parsed.data.mixed).toBe('He said "hello: world" and then left') }) test("should handle empty values", () => { @@ -190,3 +190,39 @@ describe("ConfigMarkdown: frontmatter parsing w/ no frontmatter", async () => { expect(result.content.trim()).toBe("Content") }) }) + +describe("ConfigMarkdown: frontmatter parsing w/ Markdown header", async () => { + const result = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/markdown-header.md") + + test("should parse and match", () => { + expect(result).toBeDefined() + expect(result.data).toEqual({}) + expect(result.content.trim()).toBe(`# Response Formatting Requirements + +Always structure your responses using clear markdown formatting: + +- By default don't put information into tables for questions (but do put information into tables when creating or updating files) +- Use headings (##, ###) to organise sections, always +- Use bullet points or numbered lists for multiple items +- Use code blocks with language tags for any code +- Use **bold** for key terms and emphasis +- Use tables when comparing options or listing structured data +- Break long responses into logical sections with headings`) + }) +}) + +describe("ConfigMarkdown: frontmatter has weird model id", async () => { + const result = await ConfigMarkdown.parse(import.meta.dir + "/fixtures/weird-model-id.md") + + test("should parse and match", () => { + expect(result).toBeDefined() + expect(result.data["description"]).toEqual("General coding and planning agent") + expect(result.data["mode"]).toEqual("subagent") + expect(result.data["model"]).toEqual("synthetic/hf:zai-org/GLM-4.7") + expect(result.data["tools"]["write"]).toBeTrue() + expect(result.data["tools"]["read"]).toBeTrue() + expect(result.data["stuff"]).toBe("This is some stuff\n") + + expect(result.content.trim()).toBe("Strictly follow da rules") + }) +}) From 92eb98286373b54bab97c89cb63244a3aa0005c5 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 28 Jan 2026 23:52:16 -0600 Subject: [PATCH 23/39] fix: undo change that used anthropic messages endpoint for anthropic models on copilot due to ratelimiting issues, go back to completions endpoint instead --- packages/opencode/src/plugin/copilot.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/plugin/copilot.ts b/packages/opencode/src/plugin/copilot.ts index 51f29db5ed..ef41ee38d3 100644 --- a/packages/opencode/src/plugin/copilot.ts +++ b/packages/opencode/src/plugin/copilot.ts @@ -40,22 +40,25 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { }, } + // TODO: re-enable once messages api has higher rate limits // TODO: move some of this hacky-ness to models.dev presets once we have better grasp of things here... - const base = baseURL ?? model.api.url - const claude = model.id.includes("claude") - const url = iife(() => { - if (!claude) return base - if (base.endsWith("/v1")) return base - if (base.endsWith("/")) return `${base}v1` - return `${base}/v1` - }) + // const base = baseURL ?? model.api.url + // const claude = model.id.includes("claude") + // const url = iife(() => { + // if (!claude) return base + // if (base.endsWith("/v1")) return base + // if (base.endsWith("/")) return `${base}v1` + // return `${base}/v1` + // }) - model.api.url = url - model.api.npm = claude ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot" + // model.api.url = url + // model.api.npm = claude ? "@ai-sdk/anthropic" : "@ai-sdk/github-copilot" + model.api.npm = "@ai-sdk/github-copilot" } } return { + baseURL, apiKey: "", async fetch(request: RequestInfo | URL, init?: RequestInit) { const info = await getAuth() From 7c0067d59d318bfd6ecd473c36a9e673a4f68ff9 Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 29 Jan 2026 05:59:07 +0000 Subject: [PATCH 24/39] release: v1.1.42 --- bun.lock | 30 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 4 ++-- packages/sdk/js/package.json | 4 ++-- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 47898958d3..a8c14f3860 100644 --- a/bun.lock +++ b/bun.lock @@ -23,7 +23,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -73,7 +73,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -107,7 +107,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -134,7 +134,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -158,7 +158,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -182,7 +182,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -212,7 +212,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -241,7 +241,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -257,7 +257,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.1.41", + "version": "1.1.42", "bin": { "opencode": "./bin/opencode", }, @@ -361,7 +361,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -381,7 +381,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.1.41", + "version": "1.1.42", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -392,7 +392,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -405,7 +405,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -447,7 +447,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "zod": "catalog:", }, @@ -458,7 +458,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index ea46539a81..1299c7ca14 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.1.41", + "version": "1.1.42", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 8e0f777ff9..f1b0aca8f5 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.1.41", + "version": "1.1.42", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index a52193368f..59a9d2ecc4 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.1.41", + "version": "1.1.42", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index cbdd9d0eb4..8cfdc1c0af 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.1.41", + "version": "1.1.42", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 384c5c6bcf..622d34815c 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.1.41", + "version": "1.1.42", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 7314c0a524..5ba2ec347b 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.1.41", + "version": "1.1.42", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 1d0156168f..d0e2b0135d 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.1.41", + "version": "1.1.42", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 6aaf428afc..9e09a12f99 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.1.41" +version = "1.1.42" 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.1.41/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.42/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.42/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.41/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.42/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.1.41/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.42/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.1.41/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.42/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 1e7947e287..34a0f5c7ba 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.1.41", + "version": "1.1.42", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 04351c1e8d..bb322556df 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.1.41", + "version": "1.1.42", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index e599b46f6d..7e9a11c722 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.1.41", + "version": "1.1.42", "type": "module", "license": "MIT", "scripts": { @@ -25,4 +25,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 59963d1581..9083e84386 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.1.41", + "version": "1.1.42", "type": "module", "license": "MIT", "scripts": { @@ -30,4 +30,4 @@ "publishConfig": { "directory": "dist" } -} +} \ No newline at end of file diff --git a/packages/slack/package.json b/packages/slack/package.json index 97076356ba..49504a7256 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.1.41", + "version": "1.1.42", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index b6ed6ee21b..a95f8b52ae 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.1.41", + "version": "1.1.42", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 28e38dda81..06677a1132 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.1.41", + "version": "1.1.42", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 3c7e064766..23da5f8e4a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.1.41", + "version": "1.1.42", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index e183e9f00e..77f4ec7a4d 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.1.41", + "version": "1.1.42", "publisher": "sst-dev", "repository": { "type": "git", From 2af326606c936380c303bf56506e3c8bed04b0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hegyi=20=C3=81ron=20Ferenc?= Date: Thu, 29 Jan 2026 08:09:53 +0100 Subject: [PATCH 25/39] feat(desktop): Add desktop deep link (#10072) Co-authored-by: Brendan Allan --- bun.lock | 3 + packages/app/src/app.tsx | 2 +- packages/app/src/pages/layout.tsx | 40 +++++++ packages/desktop/package.json | 1 + packages/desktop/src-tauri/Cargo.lock | 106 +++++++++++++++++- packages/desktop/src-tauri/Cargo.toml | 3 +- .../src-tauri/capabilities/default.json | 1 + packages/desktop/src-tauri/src/lib.rs | 6 + packages/desktop/src-tauri/tauri.conf.json | 7 ++ packages/desktop/src/index.tsx | 18 +++ 10 files changed, 181 insertions(+), 6 deletions(-) diff --git a/bun.lock b/bun.lock index a8c14f3860..30b0cecb0f 100644 --- a/bun.lock +++ b/bun.lock @@ -189,6 +189,7 @@ "@solid-primitives/i18n": "2.2.1", "@solid-primitives/storage": "catalog:", "@tauri-apps/api": "^2", + "@tauri-apps/plugin-deep-link": "~2", "@tauri-apps/plugin-dialog": "~2", "@tauri-apps/plugin-http": "~2", "@tauri-apps/plugin-notification": "~2", @@ -1748,6 +1749,8 @@ "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg=="], + "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.6", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-UUOSt0U5juK20uhO2MoHZX/IPblkrhUh+VPtIeu3RwtzI0R9Em3Auzfg/PwcZ9Pv8mLne3cQ4p9CFXD6WxqCZA=="], + "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ=="], "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg=="], diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index ba0d1e7aa4..11fdb57432 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -43,7 +43,7 @@ function UiI18nBridge(props: ParentProps) { declare global { interface Window { - __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string } + __OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; deepLinks?: string[] } } } diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index afef14c84a..73480e8f20 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1136,6 +1136,46 @@ export default function Layout(props: ParentProps) { if (navigate) navigateToProject(directory) } + const deepLinkEvent = "opencode:deep-link" + + const parseDeepLink = (input: string) => { + if (!input.startsWith("opencode://")) return + const url = new URL(input) + if (url.hostname !== "open-project") return + const directory = url.searchParams.get("directory") + if (!directory) return + return directory + } + + const handleDeepLinks = (urls: string[]) => { + if (!server.isLocal()) return + for (const input of urls) { + const directory = parseDeepLink(input) + if (!directory) continue + openProject(directory) + } + } + + const drainDeepLinks = () => { + const pending = window.__OPENCODE__?.deepLinks ?? [] + if (pending.length === 0) return + if (window.__OPENCODE__) window.__OPENCODE__.deepLinks = [] + handleDeepLinks(pending) + } + + onMount(() => { + const handler = (event: Event) => { + const detail = (event as CustomEvent<{ urls: string[] }>).detail + const urls = detail?.urls ?? [] + if (urls.length === 0) return + handleDeepLinks(urls) + } + + drainDeepLinks() + window.addEventListener(deepLinkEvent, handler as EventListener) + onCleanup(() => window.removeEventListener(deepLinkEvent, handler as EventListener)) + }) + const displayName = (project: LocalProject) => project.name || getFilename(project.worktree) async function renameProject(project: LocalProject, next: string) { diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 5ba2ec347b..0047cde20a 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -18,6 +18,7 @@ "@solid-primitives/i18n": "2.2.1", "@solid-primitives/storage": "catalog:", "@tauri-apps/api": "^2", + "@tauri-apps/plugin-deep-link": "~2", "@tauri-apps/plugin-dialog": "~2", "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-os": "~2", diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index 294d7ad6ce..8e7e9af0df 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -609,6 +609,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -980,6 +1000,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "document-features" version = "0.2.12" @@ -1777,6 +1806,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1930,7 +1965,7 @@ dependencies = [ "tokio", "tower-service", "tracing", - "windows-registry", + "windows-registry 0.6.1", ] [[package]] @@ -2345,7 +2380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4f8240c33bb08c5d8b8cdea87b683b05e61037aa76ff26bef40672cc6ecbb80" dependencies = [ "freedesktop_entry_parser", - "rust-ini", + "rust-ini 0.17.0", ] [[package]] @@ -3038,6 +3073,7 @@ dependencies = [ "tauri-build", "tauri-plugin-clipboard-manager", "tauri-plugin-decorum", + "tauri-plugin-deep-link", "tauri-plugin-dialog", "tauri-plugin-http", "tauri-plugin-notification", @@ -3067,10 +3103,20 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" dependencies = [ - "dlv-list", + "dlv-list 0.2.3", "hashbrown 0.9.1", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list 0.5.2", + "hashbrown 0.14.5", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3947,7 +3993,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" dependencies = [ "cfg-if", - "ordered-multimap", + "ordered-multimap 0.3.1", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap 0.7.3", ] [[package]] @@ -4817,6 +4873,27 @@ dependencies = [ "tauri-plugin", ] +[[package]] +name = "tauri-plugin-deep-link" +version = "2.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b091f24f2f6bdb4a305b54d3961f629c11861c685aceeea9a1972f89e43d5" +dependencies = [ + "dunce", + "plist", + "rust-ini 0.21.3", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "tracing", + "url", + "windows-registry 0.5.3", + "windows-result 0.3.4", +] + [[package]] name = "tauri-plugin-dialog" version = "2.4.2" @@ -4980,6 +5057,7 @@ dependencies = [ "serde", "serde_json", "tauri", + "tauri-plugin-deep-link", "thiserror 2.0.17", "tracing", "windows-sys 0.60.2", @@ -5271,6 +5349,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -6208,6 +6295,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-registry" version = "0.6.1" diff --git a/packages/desktop/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml index b875f928b0..6a0219ae40 100644 --- a/packages/desktop/src-tauri/Cargo.toml +++ b/packages/desktop/src-tauri/Cargo.toml @@ -20,6 +20,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = ["macos-private-api", "devtools"] } tauri-plugin-opener = "2" +tauri-plugin-deep-link = "2.4.6" tauri-plugin-shell = "2" tauri-plugin-dialog = "2" tauri-plugin-updater = "2" @@ -29,7 +30,7 @@ tauri-plugin-window-state = "2" tauri-plugin-clipboard-manager = "2" tauri-plugin-http = "2" tauri-plugin-notification = "2" -tauri-plugin-single-instance = "2" +tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/packages/desktop/src-tauri/capabilities/default.json b/packages/desktop/src-tauri/capabilities/default.json index 12de32931b..66f068af8b 100644 --- a/packages/desktop/src-tauri/capabilities/default.json +++ b/packages/desktop/src-tauri/capabilities/default.json @@ -6,6 +6,7 @@ "permissions": [ "core:default", "opener:default", + "deep-link:default", "core:window:allow-start-dragging", "core:window:allow-set-theme", "core:webview:allow-set-webview-zoom", diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index dab98f4a00..29ac86f29a 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -16,6 +16,8 @@ use std::{ time::{Duration, Instant}, }; use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewWindowBuilder}; +#[cfg(any(target_os = "linux", all(debug_assertions, windows)))] +use tauri_plugin_deep_link::DeepLinkExt; #[cfg(windows)] use tauri_plugin_decorum::WebviewWindowExt; use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult}; @@ -263,6 +265,7 @@ pub fn run() { let _ = window.unminimize(); } })) + .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_os::init()) .plugin( tauri_plugin_window_state::Builder::new() @@ -291,6 +294,9 @@ pub fn run() { markdown::parse_markdown_command ]) .setup(move |app| { + #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] + app.deep_link().register_all().ok(); + let app = app.handle().clone(); // Initialize log state diff --git a/packages/desktop/src-tauri/tauri.conf.json b/packages/desktop/src-tauri/tauri.conf.json index f8df151bdf..5f76d510bc 100644 --- a/packages/desktop/src-tauri/tauri.conf.json +++ b/packages/desktop/src-tauri/tauri.conf.json @@ -52,5 +52,12 @@ "sidebarImage": "assets/nsis-sidebar.bmp" } } + }, + "plugins": { + "deep-link": { + "desktop": { + "schemes": ["opencode"] + } + } } } diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 344c6be8d9..2e7ca136ac 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -3,6 +3,7 @@ import "./webview-zoom" import { render } from "solid-js/web" import { AppBaseProviders, AppInterface, PlatformProvider, Platform } from "@opencode-ai/app" import { open, save } from "@tauri-apps/plugin-dialog" +import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link" import { open as shellOpen } from "@tauri-apps/plugin-shell" import { type as ostype } from "@tauri-apps/plugin-os" import { check, Update } from "@tauri-apps/plugin-updater" @@ -42,6 +43,22 @@ window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => { let update: Update | null = null +const deepLinkEvent = "opencode:deep-link" + +const emitDeepLinks = (urls: string[]) => { + if (urls.length === 0) return + window.__OPENCODE__ ??= {} + const pending = window.__OPENCODE__.deepLinks ?? [] + window.__OPENCODE__.deepLinks = [...pending, ...urls] + window.dispatchEvent(new CustomEvent(deepLinkEvent, { detail: { urls } })) +} + +const listenForDeepLinks = async () => { + const startUrls = await getCurrent().catch(() => null) + if (startUrls?.length) emitDeepLinks(startUrls) + await onOpenUrl((urls) => emitDeepLinks(urls)).catch(() => undefined) +} + const createPlatform = (password: Accessor): Platform => ({ platform: "desktop", os: (() => { @@ -332,6 +349,7 @@ const createPlatform = (password: Accessor): Platform => ({ }) createMenu() +void listenForDeepLinks() render(() => { const [serverPassword, setServerPassword] = createSignal(null) From c946f5f7eb67a46c0824fbd473ae020d390c29bf Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 29 Jan 2026 07:10:34 +0000 Subject: [PATCH 26/39] chore: generate --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 7e9a11c722..6ffef240f0 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -25,4 +25,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 9083e84386..85022e30ac 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -30,4 +30,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From 82717f6e8b820fb0a542e7b15a3421bce8d126b3 Mon Sep 17 00:00:00 2001 From: Github Action Date: Thu, 29 Jan 2026 07:12:37 +0000 Subject: [PATCH 27/39] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 0b735b35d6..8e95135aa2 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-9oI1gekRbjY6L8VwlkLdPty/9rCxC20EJlESkazEX8Y=", - "aarch64-linux": "sha256-vn+eCVanOSNfjyqHRJn4VdqbpdMoBFm49REuIkByAio=", - "aarch64-darwin": "sha256-0dMP5WbqDq3qdLRrKfmCjXz2kUDjTttGTqD3v6PDbkg=", - "x86_64-darwin": "sha256-9dEWluRXY7RTPdSEhhPsDJeGo+qa3V8dqh6n6WsLeGw=" + "x86_64-linux": "sha256-yAtZlh6YR78RwPt0LK/7Pk0qUm0/97+s6ghhZzuoE/0=", + "aarch64-linux": "sha256-6j81rdjQ7Wps9bvfw+mmdwW5p01qUOwX40UZltCTe3Y=", + "aarch64-darwin": "sha256-pDM8M/QMWR6Go5pz3XXsJqcJDHAlHrx2Faijjkzcngo=", + "x86_64-darwin": "sha256-eOAPtMd1n5xYupBOevCLhY1eFy3wzGqFk/EsZocl9Y8=" } } From 571751c313336e933a3d3b2478255107f80f3686 Mon Sep 17 00:00:00 2001 From: Ryan Vogel Date: Thu, 29 Jan 2026 06:16:45 -0500 Subject: [PATCH 28/39] fix: remove redundant Highlights heading from publish template (#11121) --- script/publish-start.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/script/publish-start.ts b/script/publish-start.ts index 385a2384bc..474e01a64a 100755 --- a/script/publish-start.ts +++ b/script/publish-start.ts @@ -4,8 +4,7 @@ import { $ } from "bun" import { Script } from "@opencode-ai/script" import { buildNotes, getLatestRelease } from "./changelog" -const highlightsTemplate = `## Highlights - +const highlightsTemplate = `