From cf50a289db056657171b73fb5e1f907b0baedd59 Mon Sep 17 00:00:00 2001
From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com>
Date: Sun, 15 Feb 2026 07:48:40 -0800
Subject: [PATCH 01/32] fix(desktop): issue viewing new files opened from the
file tree (#13689)
---
packages/app/src/pages/session.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 5ce6202eef..41646d2f6c 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -272,6 +272,7 @@ export default function Page() {
if (!path) return
file.load(path)
openReviewPanel()
+ tabs().setActive(next)
}
createEffect(() => {
From 3a3aa300bb846ae60391ba96c5f1f4aa9a9a5d74 Mon Sep 17 00:00:00 2001
From: Alex Yaroshuk <34632190+alexyaroshuk@users.noreply.github.com>
Date: Mon, 16 Feb 2026 00:40:09 +0800
Subject: [PATCH 02/32] feat(app): localize "free usage exceeded" error & "Add
credits" clickable link (#13652)
---
packages/ui/src/components/session-turn.css | 6 ++++++
packages/ui/src/components/session-turn.tsx | 19 ++++++++++++++++++-
packages/ui/src/i18n/ar.ts | 2 ++
packages/ui/src/i18n/br.ts | 2 ++
packages/ui/src/i18n/bs.ts | 2 ++
packages/ui/src/i18n/da.ts | 2 ++
packages/ui/src/i18n/de.ts | 2 ++
packages/ui/src/i18n/en.ts | 2 ++
packages/ui/src/i18n/es.ts | 2 ++
packages/ui/src/i18n/fr.ts | 2 ++
packages/ui/src/i18n/ja.ts | 2 ++
packages/ui/src/i18n/ko.ts | 2 ++
packages/ui/src/i18n/no.ts | 2 ++
packages/ui/src/i18n/pl.ts | 2 ++
packages/ui/src/i18n/ru.ts | 2 ++
packages/ui/src/i18n/th.ts | 2 ++
packages/ui/src/i18n/zh.ts | 2 ++
packages/ui/src/i18n/zht.ts | 2 ++
18 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index 9887ce2fc6..414e8a3590 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -560,6 +560,12 @@
overflow-y: auto;
}
+ .retry-error-link,
+ .error-card-link {
+ color: var(--text-strong);
+ text-decoration: underline;
+ }
+
[data-slot="session-turn-collapsible-content-inner"] {
width: 100%;
min-width: 0;
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index 9ffa671e69..c036221059 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -436,6 +436,11 @@ export function SessionTurn(
if (s.type !== "retry") return
return s
})
+ const isRetryFreeUsageLimitError = createMemo(() => {
+ const r = retry()
+ if (!r) return false
+ return r.message.includes("Free usage exceeded")
+ })
const response = createMemo(() => lastTextPart()?.text)
const responsePartId = createMemo(() => lastTextPart()?.id)
@@ -691,10 +696,22 @@ export function SessionTurn(
{(() => {
const r = retry()
if (!r) return ""
- const msg = unwrap(r.message)
+ const msg = isRetryFreeUsageLimitError()
+ ? i18n.t("ui.sessionTurn.error.freeUsageExceeded")
+ : unwrap(r.message)
return msg.length > 60 ? msg.slice(0, 60) + "..." : msg
})()}
+
+
+ {i18n.t("ui.sessionTurn.error.addCredits")}
+
+
· {i18n.t("ui.sessionTurn.retry.retrying")}
{store.retrySeconds > 0
diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts
index 9a6c8dcbd0..4a1525d468 100644
--- a/packages/ui/src/i18n/ar.ts
+++ b/packages/ui/src/i18n/ar.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "إعادة المحاولة",
"ui.sessionTurn.retry.inSeconds": "خلال {{seconds}} ثواني",
+ "ui.sessionTurn.error.freeUsageExceeded": "تم تجاوز حد الاستخدام المجاني",
+ "ui.sessionTurn.error.addCredits": "إضافة رصيد",
"ui.sessionTurn.status.delegating": "تفويض العمل",
"ui.sessionTurn.status.planning": "تخطيط الخطوات التالية",
diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts
index 148b0ae174..160d07aee2 100644
--- a/packages/ui/src/i18n/br.ts
+++ b/packages/ui/src/i18n/br.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "tentando novamente",
"ui.sessionTurn.retry.inSeconds": "em {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Limite de uso gratuito excedido",
+ "ui.sessionTurn.error.addCredits": "Adicionar créditos",
"ui.sessionTurn.status.delegating": "Delegando trabalho",
"ui.sessionTurn.status.planning": "Planejando próximos passos",
diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts
index 7614af087f..9a049c14bc 100644
--- a/packages/ui/src/i18n/bs.ts
+++ b/packages/ui/src/i18n/bs.ts
@@ -32,6 +32,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "ponovni pokušaj",
"ui.sessionTurn.retry.inSeconds": "za {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Besplatna upotreba premašena",
+ "ui.sessionTurn.error.addCredits": "Dodaj kredite",
"ui.sessionTurn.status.delegating": "Delegiranje posla",
"ui.sessionTurn.status.planning": "Planiranje sljedećih koraka",
diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts
index 2f49a94344..de0e854be9 100644
--- a/packages/ui/src/i18n/da.ts
+++ b/packages/ui/src/i18n/da.ts
@@ -27,6 +27,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "prøver igen",
"ui.sessionTurn.retry.inSeconds": "om {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Gratis forbrug overskredet",
+ "ui.sessionTurn.error.addCredits": "Tilføj kreditter",
"ui.sessionTurn.status.delegating": "Delegerer arbejde",
"ui.sessionTurn.status.planning": "Planlægger næste trin",
diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts
index 44090b7bdb..977065db4c 100644
--- a/packages/ui/src/i18n/de.ts
+++ b/packages/ui/src/i18n/de.ts
@@ -31,6 +31,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "erneuter Versuch",
"ui.sessionTurn.retry.inSeconds": "in {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Kostenloses Nutzungslimit überschritten",
+ "ui.sessionTurn.error.addCredits": "Guthaben aufladen",
"ui.sessionTurn.status.delegating": "Arbeit delegieren",
"ui.sessionTurn.status.planning": "Nächste Schritte planen",
diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts
index 9b6ab0bd6d..59f08e48d3 100644
--- a/packages/ui/src/i18n/en.ts
+++ b/packages/ui/src/i18n/en.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "retrying",
"ui.sessionTurn.retry.inSeconds": "in {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Free usage exceeded",
+ "ui.sessionTurn.error.addCredits": "Add credits",
"ui.sessionTurn.status.delegating": "Delegating work",
"ui.sessionTurn.status.planning": "Planning next steps",
diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts
index c2f8ac3b9d..6706515ecb 100644
--- a/packages/ui/src/i18n/es.ts
+++ b/packages/ui/src/i18n/es.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "reintentando",
"ui.sessionTurn.retry.inSeconds": "en {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Límite de uso gratuito excedido",
+ "ui.sessionTurn.error.addCredits": "Añadir créditos",
"ui.sessionTurn.status.delegating": "Delegando trabajo",
"ui.sessionTurn.status.planning": "Planificando siguientes pasos",
diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts
index 679d56fa76..68a687e840 100644
--- a/packages/ui/src/i18n/fr.ts
+++ b/packages/ui/src/i18n/fr.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "nouvelle tentative",
"ui.sessionTurn.retry.inSeconds": "dans {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Limite d'utilisation gratuite dépassée",
+ "ui.sessionTurn.error.addCredits": "Ajouter des crédits",
"ui.sessionTurn.status.delegating": "Délégation du travail",
"ui.sessionTurn.status.planning": "Planification des prochaines étapes",
diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts
index bf85807d00..6fff28cff4 100644
--- a/packages/ui/src/i18n/ja.ts
+++ b/packages/ui/src/i18n/ja.ts
@@ -27,6 +27,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "再試行中",
"ui.sessionTurn.retry.inSeconds": "{{seconds}}秒後",
+ "ui.sessionTurn.error.freeUsageExceeded": "無料使用制限に達しました",
+ "ui.sessionTurn.error.addCredits": "クレジットを追加",
"ui.sessionTurn.status.delegating": "作業を委任中",
"ui.sessionTurn.status.planning": "次のステップを計画中",
diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts
index aba793a11b..6fac1590d7 100644
--- a/packages/ui/src/i18n/ko.ts
+++ b/packages/ui/src/i18n/ko.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "재시도 중",
"ui.sessionTurn.retry.inSeconds": "{{seconds}}초 후",
+ "ui.sessionTurn.error.freeUsageExceeded": "무료 사용량 초과",
+ "ui.sessionTurn.error.addCredits": "크레딧 추가",
"ui.sessionTurn.status.delegating": "작업 위임 중",
"ui.sessionTurn.status.planning": "다음 단계 계획 중",
diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts
index 7982b3ac75..160f26a546 100644
--- a/packages/ui/src/i18n/no.ts
+++ b/packages/ui/src/i18n/no.ts
@@ -31,6 +31,8 @@ export const dict: Record = {
"ui.sessionTurn.retry.retrying": "Prøver igjen",
"ui.sessionTurn.retry.inSeconds": "om {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Gratis bruk overskredet",
+ "ui.sessionTurn.error.addCredits": "Legg til kreditt",
"ui.sessionTurn.status.delegating": "Delegerer arbeid",
"ui.sessionTurn.status.planning": "Planlegger neste trinn",
diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts
index 2489ac7f2e..4882ba0348 100644
--- a/packages/ui/src/i18n/pl.ts
+++ b/packages/ui/src/i18n/pl.ts
@@ -27,6 +27,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "ponawianie",
"ui.sessionTurn.retry.inSeconds": "za {{seconds}}s",
+ "ui.sessionTurn.error.freeUsageExceeded": "Przekroczono limit darmowego użytkowania",
+ "ui.sessionTurn.error.addCredits": "Dodaj kredyty",
"ui.sessionTurn.status.delegating": "Delegowanie pracy",
"ui.sessionTurn.status.planning": "Planowanie kolejnych kroków",
diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts
index 8e6bb678f2..93a9883d26 100644
--- a/packages/ui/src/i18n/ru.ts
+++ b/packages/ui/src/i18n/ru.ts
@@ -27,6 +27,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "повтор",
"ui.sessionTurn.retry.inSeconds": "через {{seconds}}с",
+ "ui.sessionTurn.error.freeUsageExceeded": "Лимит бесплатного использования превышен",
+ "ui.sessionTurn.error.addCredits": "Добавить кредиты",
"ui.sessionTurn.status.delegating": "Делегирование работы",
"ui.sessionTurn.status.planning": "Планирование следующих шагов",
diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts
index b036eca2e8..1a5438a2ae 100644
--- a/packages/ui/src/i18n/th.ts
+++ b/packages/ui/src/i18n/th.ts
@@ -28,6 +28,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "กำลังลองใหม่",
"ui.sessionTurn.retry.inSeconds": "ใน {{seconds}}วิ",
+ "ui.sessionTurn.error.freeUsageExceeded": "เกินขีดจำกัดการใช้งานฟรี",
+ "ui.sessionTurn.error.addCredits": "เพิ่มเครดิต",
"ui.sessionTurn.status.delegating": "มอบหมายงาน",
"ui.sessionTurn.status.planning": "วางแผนขั้นตอนถัดไป",
diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts
index dcb8062a33..dbebfb3f9f 100644
--- a/packages/ui/src/i18n/zh.ts
+++ b/packages/ui/src/i18n/zh.ts
@@ -32,6 +32,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "重试中",
"ui.sessionTurn.retry.inSeconds": "{{seconds}} 秒后",
+ "ui.sessionTurn.error.freeUsageExceeded": "免费使用额度已用完",
+ "ui.sessionTurn.error.addCredits": "添加积分",
"ui.sessionTurn.status.delegating": "正在委派工作",
"ui.sessionTurn.status.planning": "正在规划下一步",
diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts
index 271a6ded32..5cec9c399e 100644
--- a/packages/ui/src/i18n/zht.ts
+++ b/packages/ui/src/i18n/zht.ts
@@ -32,6 +32,8 @@ export const dict = {
"ui.sessionTurn.retry.retrying": "重試中",
"ui.sessionTurn.retry.inSeconds": "{{seconds}} 秒後",
+ "ui.sessionTurn.error.freeUsageExceeded": "免費使用額度已用完",
+ "ui.sessionTurn.error.addCredits": "新增點數",
"ui.sessionTurn.status.delegating": "正在委派工作",
"ui.sessionTurn.status.planning": "正在規劃下一步",
From 62a24c2ddaf56c4234898269b1951ab11483f57a Mon Sep 17 00:00:00 2001
From: opencode
Date: Sun, 15 Feb 2026 18:49:52 +0000
Subject: [PATCH 03/32] release: v1.2.5
---
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 | 2 +-
packages/sdk/js/package.json | 2 +-
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, 37 insertions(+), 37 deletions(-)
diff --git a/bun.lock b/bun.lock
index 59106e14af..a29f006878 100644
--- a/bun.lock
+++ b/bun.lock
@@ -23,7 +23,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -73,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.2.4",
+ "version": "1.2.5",
"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.2.4",
+ "version": "1.2.5",
"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.2.4",
+ "version": "1.2.5",
"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.2.4",
+ "version": "1.2.5",
"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.2.4",
+ "version": "1.2.5",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -215,7 +215,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -244,7 +244,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -260,7 +260,7 @@
},
"packages/opencode": {
"name": "opencode",
- "version": "1.2.4",
+ "version": "1.2.5",
"bin": {
"opencode": "./bin/opencode",
},
@@ -369,7 +369,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -389,7 +389,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.2.4",
+ "version": "1.2.5",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -400,7 +400,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -413,7 +413,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -455,7 +455,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.2.4",
+ "version": "1.2.5",
"dependencies": {
"zod": "catalog:",
},
@@ -466,7 +466,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.2.4",
+ "version": "1.2.5",
"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 31afda6566..0272e78bbf 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "1.2.4",
+ "version": "1.2.5",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/console/app/package.json b/packages/console/app/package.json
index a6b2f5685d..0075a949d2 100644
--- a/packages/console/app/package.json
+++ b/packages/console/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
- "version": "1.2.4",
+ "version": "1.2.5",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/console/core/package.json b/packages/console/core/package.json
index f81304c920..34626c1e93 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.2.4",
+ "version": "1.2.5",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/console/function/package.json b/packages/console/function/package.json
index e3864bfad6..5e8c5841c9 100644
--- a/packages/console/function/package.json
+++ b/packages/console/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
- "version": "1.2.4",
+ "version": "1.2.5",
"$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 261d36bae5..99a5ab7d93 100644
--- a/packages/console/mail/package.json
+++ b/packages/console/mail/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
- "version": "1.2.4",
+ "version": "1.2.5",
"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 2901e299c0..e89078b893 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "1.2.4",
+ "version": "1.2.5",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json
index ac7060dd10..874f2ed218 100644
--- a/packages/enterprise/package.json
+++ b/packages/enterprise/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
- "version": "1.2.4",
+ "version": "1.2.5",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 9c10eb9826..00af2842cd 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.2.4"
+version = "1.2.5"
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.2.4/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.5/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.5/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.4/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.5/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.2.4/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.5/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.2.4/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.5/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/function/package.json b/packages/function/package.json
index 3a431e9bdf..d2c4e51e90 100644
--- a/packages/function/package.json
+++ b/packages/function/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
- "version": "1.2.4",
+ "version": "1.2.5",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index a5b3415550..99be25372f 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
- "version": "1.2.4",
+ "version": "1.2.5",
"name": "opencode",
"type": "module",
"license": "MIT",
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 437fc09170..82618195c9 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.2.4",
+ "version": "1.2.5",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 74c3fdb1ad..6b3494e0c6 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.2.4",
+ "version": "1.2.5",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/slack/package.json b/packages/slack/package.json
index 5a5a0e8359..45eb1d841d 100644
--- a/packages/slack/package.json
+++ b/packages/slack/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
- "version": "1.2.4",
+ "version": "1.2.5",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 684836335c..5634398377 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
- "version": "1.2.4",
+ "version": "1.2.5",
"type": "module",
"license": "MIT",
"exports": {
diff --git a/packages/util/package.json b/packages/util/package.json
index 74393cecee..7a76bfdec2 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
- "version": "1.2.4",
+ "version": "1.2.5",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/web/package.json b/packages/web/package.json
index e2acd6cf39..ff2a7d2686 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.2.4",
+ "version": "1.2.5",
"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 e8e41a4f29..bea2f5712d 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.2.4",
+ "version": "1.2.5",
"publisher": "sst-dev",
"repository": {
"type": "git",
From 9b23130ac47442a216d84eace4032369620e548a Mon Sep 17 00:00:00 2001
From: Salam Elbilig
Date: Sun, 15 Feb 2026 13:21:57 -0800
Subject: [PATCH 04/32] feat(opencode): add `cljfmt` formatter support for
Clojure files (#13426)
---
packages/opencode/src/format/formatter.ts | 9 +++++
packages/web/src/content/docs/formatters.mdx | 37 ++++++++++----------
2 files changed, 28 insertions(+), 18 deletions(-)
diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts
index 9e97fae9df..4184469580 100644
--- a/packages/opencode/src/format/formatter.ts
+++ b/packages/opencode/src/format/formatter.ts
@@ -364,3 +364,12 @@ export const ormolu: Info = {
return Bun.which("ormolu") !== null
},
}
+
+export const cljfmt: Info = {
+ name: "cljfmt",
+ command: ["cljfmt", "fix", "--quiet", "$FILE"],
+ extensions: [".clj", ".cljs", ".cljc", ".edn"],
+ async enabled() {
+ return Bun.which("cljfmt") !== null
+ },
+}
diff --git a/packages/web/src/content/docs/formatters.mdx b/packages/web/src/content/docs/formatters.mdx
index 54f36e0cd0..0cb947b08f 100644
--- a/packages/web/src/content/docs/formatters.mdx
+++ b/packages/web/src/content/docs/formatters.mdx
@@ -13,30 +13,31 @@ OpenCode comes with several built-in formatters for popular languages and framew
| Formatter | Extensions | Requirements |
| -------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
-| gofmt | .go | `gofmt` command available |
-| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available |
-| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` |
+| air | .R | `air` command available |
| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file |
-| zig | .zig, .zon | `zig` command available |
+| cargofmt | .rs | `cargo fmt` command available |
| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file |
+| cljfmt | .clj, .cljs, .cljc, .edn | `cljfmt` command available |
+| dart | .dart | `dart` command available |
+| gleam | .gleam | `gleam` command available |
+| gofmt | .go | `gofmt` command available |
+| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available |
| ktlint | .kt, .kts | `ktlint` command available |
+| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available |
+| nixfmt | .nix | `nixfmt` command available |
+| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file |
+| ormolu | .hs | `ormolu` command available |
+| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) |
+| pint | .php | `laravel/pint` dependency in `composer.json` |
+| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` |
+| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
| ruff | .py, .pyi | `ruff` command available with config |
| rustfmt | .rs | `rustfmt` command available |
-| cargofmt | .rs | `cargo fmt` command available |
-| uv | .py, .pyi | `uv` command available |
-| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
-| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
-| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available |
-| air | .R | `air` command available |
-| dart | .dart | `dart` command available |
-| ocamlformat | .ml, .mli | `ocamlformat` command available and `.ocamlformat` config file |
-| terraform | .tf, .tfvars | `terraform` command available |
-| gleam | .gleam | `gleam` command available |
-| nixfmt | .nix | `nixfmt` command available |
| shfmt | .sh, .bash | `shfmt` command available |
-| pint | .php | `laravel/pint` dependency in `composer.json` |
-| oxfmt (Experimental) | .js, .jsx, .ts, .tsx | `oxfmt` dependency in `package.json` and an [experimental env variable flag](/docs/cli/#experimental) |
-| ormolu | .hs | `ormolu` command available |
+| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
+| terraform | .tf, .tfvars | `terraform` command available |
+| uv | .py, .pyi | `uv` command available |
+| zig | .zig, .zon | `zig` command available |
So if your project has `prettier` in your `package.json`, OpenCode will automatically use it.
From d9363da9eebc0481e9829f5b96cb07adcb4caaa8 Mon Sep 17 00:00:00 2001
From: Pan Kaixin
Date: Mon, 16 Feb 2026 06:30:47 +0800
Subject: [PATCH 05/32] fix(website): correct zh-CN translation of proprietary
terms in zen.mdx (#13734)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
---
packages/web/src/content/docs/zh-cn/zen.mdx | 56 ++++++++++-----------
1 file changed, 28 insertions(+), 28 deletions(-)
diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx
index 7c00b5cdf4..e808652a3a 100644
--- a/packages/web/src/content/docs/zh-cn/zen.mdx
+++ b/packages/web/src/content/docs/zh-cn/zen.mdx
@@ -116,39 +116,39 @@ https://opencode.ai/zen/v1/models
| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 |
| ---------------------------------- | ---------- | ---------- | ---------- | ---------- |
-| 大泡菜 | 免费 | 免费 | 免费 | - |
-| MiniMax M2.1 免费 | 免费 | 免费 | 免费 | - |
-| 迷你最大M2.1 | 0.30 美元 | 1.20 美元 | 0.10 美元 | - |
-| GLM 4.7 免费 | 免费 | 免费 | 免费 | - |
+| Big Pickle | 免费 | 免费 | 免费 | - |
+| MiniMax M2.1 Free | 免费 | 免费 | 免费 | - |
+| MiniMax M2.1 | 0.30 美元 | 1.20 美元 | 0.10 美元 | - |
+| GLM 4.7 Free | 免费 | 免费 | 免费 | - |
| GLM 4.7 | 0.60 美元 | 2.20 美元 | 0.10 美元 | - |
| GLM 4.6 | 0.60 美元 | 2.20 美元 | 0.10 美元 | - |
-| Kimi K2.5 免费 | 免费 | 免费 | 免费 | - |
-| 作为K2.5 | 0.60 美元 | $3.00 | 0.08 美元 | - |
-| Kimi K2 思考 | 0.40 美元 | 2.50 美元 | - | - |
-| 作为K2 | 0.40 美元 | 2.50 美元 | - | - |
-| Qwen3 编码器 480B | 0.45 美元 | 1.50 美元 | - | - |
-| Claude Sonnet 4.5(≤ 200K Tokens) | $3.00 | 15.00 美元 | 0.30 美元 | 3.75 美元 |
-| 克劳德十四行诗 4.5(> 200K 代币) | 6.00 美元 | 22.50 美元 | 0.60 美元 | 7.50 美元 |
-| Claude Sonnet 4(≤ 200K Tokens) | $3.00 | 15.00 美元 | 0.30 美元 | 3.75 美元 |
+| Kimi K2.5 Free | 免费 | 免费 | 免费 | - |
+| Kimi K2.5 | 0.60 美元 | 3.00 美元 | 0.08 美元 | - |
+| Kimi K2 Thinking | 0.40 美元 | 2.50 美元 | - | - |
+| Kimi K2 | 0.40 美元 | 2.50 美元 | - | - |
+| Qwen3 Coder 480B | 0.45 美元 | 1.50 美元 | - | - |
+| Claude Sonnet 4.5(≤ 200K Tokens) | 3.00 美元 | 15.00 美元 | 0.30 美元 | 3.75 美元 |
+| Claude Sonnet 4.5(> 200K Tokens) | 6.00 美元 | 22.50 美元 | 0.60 美元 | 7.50 美元 |
+| Claude Sonnet 4(≤ 200K Tokens) | 3.00 美元 | 15.00 美元 | 0.30 美元 | 3.75 美元 |
| Claude Sonnet 4(> 200K Tokens) | 6.00 美元 | 22.50 美元 | 0.60 美元 | 7.50 美元 |
-| Claude 俳句 4.5 | 1.00 美元 | 5.00 美元 | 0.10 美元 | 1.25 美元 |
-| Claude 俳句 3.5 | 0.80 美元 | 4.00 美元 | 0.08 美元 | 1.00 美元 |
-| 克劳德作品4.6(≤ 200K 代币) | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
+| Claude Haiku 4.5 | 1.00 美元 | 5.00 美元 | 0.10 美元 | 1.25 美元 |
+| Claude Haiku 3.5 | 0.80 美元 | 4.00 美元 | 0.08 美元 | 1.00 美元 |
+| Claude Opus 4.6(≤ 200K Tokens) | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
| Claude Opus 4.6(> 200K Tokens) | 10.00 美元 | 37.50 美元 | 1.00 美元 | 12.50 美元 |
-| Claude 工作 4.5 | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
-| Claude 工作 4.1 | 15.00 美元 | 75.00 美元 | 1.50 美元 | 18.75 美元 |
-| Gemini 3 Pro(≤20万代币) | 2.00 美元 | 12.00 美元 | 0.20 美元 | - |
-| Gemini 3 Pro(>20万代币) | 4.00 美元 | 18.00 美元 | 0.40 美元 | - |
-| 双子座 3 闪光 | 0.50 美元 | $3.00 | 0.05 美元 | - |
+| Claude Opus 4.5 | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
+| Claude Opus 4.1 | 15.00 美元 | 75.00 美元 | 1.50 美元 | 18.75 美元 |
+| Gemini 3 Pro(≤20万 Tokens) | 2.00 美元 | 12.00 美元 | 0.20 美元 | - |
+| Gemini 3 Pro(>20万 Tokens) | 4.00 美元 | 18.00 美元 | 0.40 美元 | - |
+| Gemini 3 Flash | 0.50 美元 | 3.00 美元 | 0.05 美元 | - |
| GPT 5.2 | 1.75 美元 | 14.00 美元 | 0.175 美元 | - |
-| GPT 5.2 法典 | 1.75 美元 | 14.00 美元 | 0.175 美元 | - |
+| GPT 5.2 Codex | 1.75 美元 | 14.00 美元 | 0.175 美元 | - |
| GPT 5.1 | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5.1 法典 | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5.1 法典最大 | 1.25 美元 | 10.00 美元 | 0.125 美元 | - |
-| GPT 5.1 迷你版 | 0.25 美元 | 2.00 美元 | 0.025 美元 | - |
+| GPT 5.1 Codex | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
+| GPT 5.1 Codex Max | 1.25 美元 | 10.00 美元 | 0.125 美元 | - |
+| GPT 5.1 Codex Mini | 0.25 美元 | 2.00 美元 | 0.025 美元 | - |
| GPT 5 | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5 法典 | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5 奈米 | 免费 | 免费 | 免费 | - |
+| GPT 5 Codex | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
+| GPT 5 Nano | 免费 | 免费 | 免费 | - |
您可能会在您的使用历史记录中注意到*Claude Haiku 3.5*。这是一个[低成本模型](/docs/config/#models),用于生成会话标题。
@@ -216,8 +216,8 @@ Zen 也非常适合团队使用。您可以邀请您可以邀请队友,分配
您可以邀请团队成员到您的工作区并分配角色:
-- **管理员**:管理模型、成员、API 密钥和设备
-- **成员**:仅管理自己的API 金?
+- **管理员**:管理模型、成员、API 密钥和计费/账单
+- **成员**:仅管理自己的 API 密钥
管理员还可以为每个成员设置每月支出限额,以控制成本。
From 21e07780023dc34b57b1b79cf9715b537971d673 Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]"
Date: Sun, 15 Feb 2026 22:31:40 +0000
Subject: [PATCH 06/32] chore: generate
---
packages/web/src/content/docs/zh-cn/zen.mdx | 34 ++++++++++-----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx
index e808652a3a..d03836dc55 100644
--- a/packages/web/src/content/docs/zh-cn/zen.mdx
+++ b/packages/web/src/content/docs/zh-cn/zen.mdx
@@ -116,38 +116,38 @@ https://opencode.ai/zen/v1/models
| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 |
| ---------------------------------- | ---------- | ---------- | ---------- | ---------- |
-| Big Pickle | 免费 | 免费 | 免费 | - |
+| Big Pickle | 免费 | 免费 | 免费 | - |
| MiniMax M2.1 Free | 免费 | 免费 | 免费 | - |
| MiniMax M2.1 | 0.30 美元 | 1.20 美元 | 0.10 美元 | - |
| GLM 4.7 Free | 免费 | 免费 | 免费 | - |
| GLM 4.7 | 0.60 美元 | 2.20 美元 | 0.10 美元 | - |
| GLM 4.6 | 0.60 美元 | 2.20 美元 | 0.10 美元 | - |
| Kimi K2.5 Free | 免费 | 免费 | 免费 | - |
-| Kimi K2.5 | 0.60 美元 | 3.00 美元 | 0.08 美元 | - |
-| Kimi K2 Thinking | 0.40 美元 | 2.50 美元 | - | - |
-| Kimi K2 | 0.40 美元 | 2.50 美元 | - | - |
-| Qwen3 Coder 480B | 0.45 美元 | 1.50 美元 | - | - |
+| Kimi K2.5 | 0.60 美元 | 3.00 美元 | 0.08 美元 | - |
+| Kimi K2 Thinking | 0.40 美元 | 2.50 美元 | - | - |
+| Kimi K2 | 0.40 美元 | 2.50 美元 | - | - |
+| Qwen3 Coder 480B | 0.45 美元 | 1.50 美元 | - | - |
| Claude Sonnet 4.5(≤ 200K Tokens) | 3.00 美元 | 15.00 美元 | 0.30 美元 | 3.75 美元 |
-| Claude Sonnet 4.5(> 200K Tokens) | 6.00 美元 | 22.50 美元 | 0.60 美元 | 7.50 美元 |
+| Claude Sonnet 4.5(> 200K Tokens) | 6.00 美元 | 22.50 美元 | 0.60 美元 | 7.50 美元 |
| Claude Sonnet 4(≤ 200K Tokens) | 3.00 美元 | 15.00 美元 | 0.30 美元 | 3.75 美元 |
| Claude Sonnet 4(> 200K Tokens) | 6.00 美元 | 22.50 美元 | 0.60 美元 | 7.50 美元 |
-| Claude Haiku 4.5 | 1.00 美元 | 5.00 美元 | 0.10 美元 | 1.25 美元 |
-| Claude Haiku 3.5 | 0.80 美元 | 4.00 美元 | 0.08 美元 | 1.00 美元 |
-| Claude Opus 4.6(≤ 200K Tokens) | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
+| Claude Haiku 4.5 | 1.00 美元 | 5.00 美元 | 0.10 美元 | 1.25 美元 |
+| Claude Haiku 3.5 | 0.80 美元 | 4.00 美元 | 0.08 美元 | 1.00 美元 |
+| Claude Opus 4.6(≤ 200K Tokens) | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
| Claude Opus 4.6(> 200K Tokens) | 10.00 美元 | 37.50 美元 | 1.00 美元 | 12.50 美元 |
| Claude Opus 4.5 | 5.00 美元 | 25.00 美元 | 0.50 美元 | 6.25 美元 |
| Claude Opus 4.1 | 15.00 美元 | 75.00 美元 | 1.50 美元 | 18.75 美元 |
-| Gemini 3 Pro(≤20万 Tokens) | 2.00 美元 | 12.00 美元 | 0.20 美元 | - |
-| Gemini 3 Pro(>20万 Tokens) | 4.00 美元 | 18.00 美元 | 0.40 美元 | - |
-| Gemini 3 Flash | 0.50 美元 | 3.00 美元 | 0.05 美元 | - |
+| Gemini 3 Pro(≤20万 Tokens) | 2.00 美元 | 12.00 美元 | 0.20 美元 | - |
+| Gemini 3 Pro(>20万 Tokens) | 4.00 美元 | 18.00 美元 | 0.40 美元 | - |
+| Gemini 3 Flash | 0.50 美元 | 3.00 美元 | 0.05 美元 | - |
| GPT 5.2 | 1.75 美元 | 14.00 美元 | 0.175 美元 | - |
-| GPT 5.2 Codex | 1.75 美元 | 14.00 美元 | 0.175 美元 | - |
+| GPT 5.2 Codex | 1.75 美元 | 14.00 美元 | 0.175 美元 | - |
| GPT 5.1 | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5.1 Codex | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5.1 Codex Max | 1.25 美元 | 10.00 美元 | 0.125 美元 | - |
-| GPT 5.1 Codex Mini | 0.25 美元 | 2.00 美元 | 0.025 美元 | - |
+| GPT 5.1 Codex | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
+| GPT 5.1 Codex Max | 1.25 美元 | 10.00 美元 | 0.125 美元 | - |
+| GPT 5.1 Codex Mini | 0.25 美元 | 2.00 美元 | 0.025 美元 | - |
| GPT 5 | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
-| GPT 5 Codex | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
+| GPT 5 Codex | 1.07 美元 | 8.50 美元 | 0.107 美元 | - |
| GPT 5 Nano | 免费 | 免费 | 免费 | - |
您可能会在您的使用历史记录中注意到*Claude Haiku 3.5*。这是一个[低成本模型](/docs/config/#models),用于生成会话标题。
From 920255e8c69270942206b60f94e26b545af18050 Mon Sep 17 00:00:00 2001
From: Brendan Allan
Date: Mon, 16 Feb 2026 12:14:24 +0800
Subject: [PATCH 07/32] desktop: use process-wrap instead of manual job object
(#13431)
---
packages/desktop/src-tauri/Cargo.lock | 91 ++++++++++-
packages/desktop/src-tauri/Cargo.toml | 11 +-
packages/desktop/src-tauri/src/cli.rs | 162 ++++++++++++++++---
packages/desktop/src-tauri/src/job_object.rs | 145 -----------------
packages/desktop/src-tauri/src/lib.rs | 15 +-
packages/desktop/src-tauri/src/logging.rs | 11 +-
packages/desktop/src-tauri/src/server.rs | 2 +-
7 files changed, 229 insertions(+), 208 deletions(-)
delete mode 100644 packages/desktop/src-tauri/src/job_object.rs
diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock
index a2bb2532af..c8575a7593 100644
--- a/packages/desktop/src-tauri/Cargo.lock
+++ b/packages/desktop/src-tauri/Cargo.lock
@@ -2343,9 +2343,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.177"
+version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
@@ -2663,6 +2663,18 @@ dependencies = [
"memoffset",
]
+[[package]]
+name = "nix"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66"
+dependencies = [
+ "bitflags 2.10.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -3093,6 +3105,7 @@ dependencies = [
"listeners",
"objc2 0.6.3",
"objc2-web-kit",
+ "process-wrap",
"reqwest 0.12.24",
"semver",
"serde",
@@ -3123,7 +3136,6 @@ dependencies = [
"tracing-subscriber",
"uuid",
"webkit2gtk",
- "windows 0.61.3",
]
[[package]]
@@ -3638,6 +3650,20 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "process-wrap"
+version = "9.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccd9713fe2c91c3c85ac388b31b89de339365d2c995146e630b5e0da9d06526a"
+dependencies = [
+ "futures",
+ "indexmap 2.12.1",
+ "nix 0.31.1",
+ "tokio",
+ "tracing",
+ "windows 0.62.2",
+]
+
[[package]]
name = "psl-types"
version = "2.0.11"
@@ -6460,11 +6486,23 @@ version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
- "windows-collections",
+ "windows-collections 0.2.0",
"windows-core 0.61.2",
- "windows-future",
+ "windows-future 0.2.1",
"windows-link 0.1.3",
- "windows-numerics",
+ "windows-numerics 0.2.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections 0.3.2",
+ "windows-core 0.62.2",
+ "windows-future 0.3.2",
+ "windows-numerics 0.3.1",
]
[[package]]
@@ -6476,6 +6514,15 @@ dependencies = [
"windows-core 0.61.2",
]
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core 0.62.2",
+]
+
[[package]]
name = "windows-core"
version = "0.51.1"
@@ -6519,7 +6566,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
- "windows-threading",
+ "windows-threading 0.1.0",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core 0.62.2",
+ "windows-link 0.2.1",
+ "windows-threading 0.2.1",
]
[[package]]
@@ -6566,6 +6624,16 @@ dependencies = [
"windows-link 0.1.3",
]
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core 0.62.2",
+ "windows-link 0.2.1",
+]
+
[[package]]
name = "windows-registry"
version = "0.5.3"
@@ -6741,6 +6809,15 @@ dependencies = [
"windows-link 0.1.3",
]
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
[[package]]
name = "windows-version"
version = "0.1.7"
diff --git a/packages/desktop/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml
index 67efd8d8c9..a5539645d6 100644
--- a/packages/desktop/src-tauri/Cargo.toml
+++ b/packages/desktop/src-tauri/Cargo.toml
@@ -34,7 +34,7 @@ tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
-tokio = "1.48.0"
+tokio = { version = "1.48.0", features = ["process"] }
listeners = "0.3"
tauri-plugin-os = "2"
futures = "0.3.31"
@@ -52,6 +52,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
chrono = "0.4"
tokio-stream = { version = "0.1.18", features = ["sync"] }
+process-wrap = { version = "9.0.3", features = ["tokio1"] }
[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18.2"
@@ -62,14 +63,6 @@ objc2 = "0.6"
objc2-web-kit = "0.3"
-[target.'cfg(windows)'.dependencies]
-windows = { version = "0.61", features = [
- "Win32_Foundation",
- "Win32_System_JobObjects",
- "Win32_System_Threading",
- "Win32_Security"
-] }
-
[patch.crates-io]
specta = { git = "https://github.com/specta-rs/specta", rev = "591a5f3ddc78348abf4cbb541d599d65306d92b9" }
specta-typescript = { git = "https://github.com/specta-rs/specta", rev = "591a5f3ddc78348abf4cbb541d599d65306d92b9" }
diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs
index dade1a2818..0f5cd2ff1c 100644
--- a/packages/desktop/src-tauri/src/cli.rs
+++ b/packages/desktop/src-tauri/src/cli.rs
@@ -1,12 +1,19 @@
use futures::{FutureExt, Stream, StreamExt, future};
+use process_wrap::tokio::CommandWrap;
+#[cfg(unix)]
+use process_wrap::tokio::ProcessGroup;
+#[cfg(windows)]
+use process_wrap::tokio::{JobObject, KillOnDrop};
+#[cfg(unix)]
+use std::os::unix::process::ExitStatusExt;
+use std::{process::Stdio, time::Duration};
use tauri::{AppHandle, Manager, path::BaseDirectory};
-use tauri_plugin_shell::{
- ShellExt,
- process::{CommandChild, CommandEvent, TerminatedPayload},
-};
use tauri_plugin_store::StoreExt;
use tauri_specta::Event;
-use tokio::sync::oneshot;
+use tokio::io::{AsyncBufReadExt, BufReader};
+use tokio::process::Command;
+use tokio::sync::{mpsc, oneshot};
+use tokio_stream::wrappers::ReceiverStream;
use tracing::Instrument;
use crate::constants::{SETTINGS_STORE, WSL_ENABLED_KEY};
@@ -25,6 +32,33 @@ pub struct Config {
pub server: Option,
}
+#[derive(Clone, Debug)]
+pub enum CommandEvent {
+ Stdout(Vec),
+ Stderr(Vec),
+ Error(String),
+ Terminated(TerminatedPayload),
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct TerminatedPayload {
+ pub code: Option,
+ pub signal: Option,
+}
+
+#[derive(Clone, Debug)]
+pub struct CommandChild {
+ kill: mpsc::Sender<()>,
+}
+
+impl CommandChild {
+ pub fn kill(&self) -> std::io::Result<()> {
+ self.kill
+ .try_send(())
+ .map_err(|e| std::io::Error::other(e.to_string()))
+ }
+}
+
pub async fn get_config(app: &AppHandle) -> Option {
let (events, _) = spawn_command(app, "debug config", &[]).ok()?;
@@ -190,7 +224,7 @@ pub fn spawn_command(
app: &tauri::AppHandle,
args: &str,
extra_env: &[(&str, String)],
-) -> Result<(impl Stream- + 'static, CommandChild), tauri_plugin_shell::Error> {
+) -> Result<(impl Stream
- + 'static, CommandChild), std::io::Error> {
let state_dir = app
.path()
.resolve("", BaseDirectory::AppLocalData)
@@ -217,7 +251,7 @@ pub fn spawn_command(
.map(|(key, value)| (key.to_string(), value.clone())),
);
- let cmd = if cfg!(windows) {
+ let mut cmd = if cfg!(windows) {
if is_wsl_enabled(app) {
tracing::info!("WSL is enabled, spawning CLI server in WSL");
let version = app.package_info().version.to_string();
@@ -249,18 +283,16 @@ pub fn spawn_command(
script.push(format!("{} exec \"$BIN\" {}", env_prefix.join(" "), args));
- app.shell()
- .command("wsl")
- .args(["-e", "bash", "-lc", &script.join("\n")])
+ let mut cmd = Command::new("wsl");
+ cmd.args(["-e", "bash", "-lc", &script.join("\n")]);
+ cmd
} else {
- let mut cmd = app
- .shell()
- .sidecar("opencode-cli")
- .unwrap()
- .args(args.split_whitespace());
+ let sidecar = get_sidecar_path(app);
+ let mut cmd = Command::new(sidecar);
+ cmd.args(args.split_whitespace());
for (key, value) in envs {
- cmd = cmd.env(key, value);
+ cmd.env(key, value);
}
cmd
@@ -269,26 +301,111 @@ pub fn spawn_command(
let sidecar = get_sidecar_path(app);
let shell = get_user_shell();
- let cmd = if shell.ends_with("/nu") {
+ let line = if shell.ends_with("/nu") {
format!("^\"{}\" {}", sidecar.display(), args)
} else {
format!("\"{}\" {}", sidecar.display(), args)
};
- let mut cmd = app.shell().command(&shell).args(["-il", "-c", &cmd]);
+ let mut cmd = Command::new(shell);
+ cmd.args(["-il", "-c", &line]);
for (key, value) in envs {
- cmd = cmd.env(key, value);
+ cmd.env(key, value);
}
cmd
};
- let (rx, child) = cmd.spawn()?;
- let event_stream = tokio_stream::wrappers::ReceiverStream::new(rx);
+ cmd.stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
+
+ let mut wrap = CommandWrap::from(cmd);
+
+ #[cfg(unix)]
+ {
+ wrap.wrap(ProcessGroup::leader());
+ }
+
+ #[cfg(windows)]
+ {
+ wrap.wrap(JobObject).wrap(KillOnDrop);
+ }
+
+ let mut child = wrap.spawn()?;
+ let stdout = child.stdout().take();
+ let stderr = child.stderr().take();
+ let (tx, rx) = mpsc::channel(256);
+ let (kill_tx, mut kill_rx) = mpsc::channel(1);
+
+ if let Some(stdout) = stdout {
+ let tx = tx.clone();
+ tokio::spawn(async move {
+ let mut lines = BufReader::new(stdout).lines();
+ while let Ok(Some(line)) = lines.next_line().await {
+ let _ = tx.send(CommandEvent::Stdout(line.into_bytes())).await;
+ }
+ });
+ }
+
+ if let Some(stderr) = stderr {
+ let tx = tx.clone();
+ tokio::spawn(async move {
+ let mut lines = BufReader::new(stderr).lines();
+ while let Ok(Some(line)) = lines.next_line().await {
+ let _ = tx.send(CommandEvent::Stderr(line.into_bytes())).await;
+ }
+ });
+ }
+
+ tokio::spawn(async move {
+ let status = loop {
+ match child.try_wait() {
+ Ok(Some(status)) => break Ok(status),
+ Ok(None) => {}
+ Err(err) => break Err(err),
+ }
+
+ tokio::select! {
+ _ = kill_rx.recv() => {
+ let _ = child.start_kill();
+ }
+ _ = tokio::time::sleep(Duration::from_millis(100)) => {}
+ }
+ };
+
+ match status {
+ Ok(status) => {
+ let payload = TerminatedPayload {
+ code: status.code(),
+ signal: signal_from_status(status),
+ };
+ let _ = tx.send(CommandEvent::Terminated(payload)).await;
+ }
+ Err(err) => {
+ let _ = tx.send(CommandEvent::Error(err.to_string())).await;
+ }
+ }
+ });
+
+ let event_stream = ReceiverStream::new(rx);
let event_stream = sqlite_migration::logs_middleware(app.clone(), event_stream);
- Ok((event_stream, child))
+ Ok((event_stream, CommandChild { kill: kill_tx }))
+}
+
+fn signal_from_status(status: std::process::ExitStatus) -> Option {
+ #[cfg(unix)]
+ {
+ return status.signal();
+ }
+
+ #[cfg(not(unix))]
+ {
+ let _ = status;
+ None
+ }
}
pub fn serve(
@@ -340,7 +457,6 @@ pub fn serve(
let _ = tx.send(payload);
}
}
- _ => {}
}
future::ready(())
diff --git a/packages/desktop/src-tauri/src/job_object.rs b/packages/desktop/src-tauri/src/job_object.rs
deleted file mode 100644
index 8d774b14cd..0000000000
--- a/packages/desktop/src-tauri/src/job_object.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-//! Windows Job Object for reliable child process cleanup.
-//!
-//! This module provides a wrapper around Windows Job Objects with the
-//! `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` flag set. When the job object handle
-//! is closed (including when the parent process exits or crashes), Windows
-//! automatically terminates all processes assigned to the job.
-//!
-//! This is more reliable than manual cleanup because it works even if:
-//! - The parent process crashes
-//! - The parent is killed via Task Manager
-//! - The RunEvent::Exit handler fails to run
-
-use std::io::{Error, Result};
-#[cfg(windows)]
-use std::sync::Mutex;
-use windows::Win32::Foundation::{CloseHandle, HANDLE};
-use windows::Win32::System::JobObjects::{
- AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation,
- SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
- JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
-};
-use windows::Win32::System::Threading::{OpenProcess, PROCESS_SET_QUOTA, PROCESS_TERMINATE};
-
-/// A Windows Job Object configured to kill all assigned processes when closed.
-///
-/// When this struct is dropped or when the owning process exits (even abnormally),
-/// Windows will automatically terminate all processes that have been assigned to it.
-pub struct JobObject(HANDLE);
-
-// SAFETY: HANDLE is just a pointer-sized value, and Windows job objects
-// can be safely accessed from multiple threads.
-unsafe impl Send for JobObject {}
-unsafe impl Sync for JobObject {}
-
-impl JobObject {
- /// Creates a new anonymous job object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` set.
- ///
- /// When the last handle to this job is closed (including on process exit),
- /// Windows will terminate all processes assigned to the job.
- pub fn new() -> Result {
- unsafe {
- // Create an anonymous job object
- let job = CreateJobObjectW(None, None).map_err(|e| Error::other(e.message()))?;
-
- // Configure the job to kill all processes when the handle is closed
- let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
- info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
-
- SetInformationJobObject(
- job,
- JobObjectExtendedLimitInformation,
- &info as *const _ as *const std::ffi::c_void,
- std::mem::size_of::() as u32,
- )
- .map_err(|e| Error::other(e.message()))?;
-
- Ok(Self(job))
- }
- }
-
- /// Assigns a process to this job object by its process ID.
- ///
- /// Once assigned, the process will be terminated when this job object is dropped
- /// or when the owning process exits.
- ///
- /// # Arguments
- /// * `pid` - The process ID of the process to assign
- pub fn assign_pid(&self, pid: u32) -> Result<()> {
- unsafe {
- // Open a handle to the process with the minimum required permissions
- // PROCESS_SET_QUOTA and PROCESS_TERMINATE are required by AssignProcessToJobObject
- let process = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, pid)
- .map_err(|e| Error::other(e.message()))?;
-
- // Assign the process to the job
- let result = AssignProcessToJobObject(self.0, process);
-
- // Close our handle to the process - the job object maintains its own reference
- let _ = CloseHandle(process);
-
- result.map_err(|e| Error::other(e.message()))
- }
- }
-}
-
-impl Drop for JobObject {
- fn drop(&mut self) {
- unsafe {
- // When this handle is closed and it's the last handle to the job,
- // Windows will terminate all processes in the job due to KILL_ON_JOB_CLOSE
- let _ = CloseHandle(self.0);
- }
- }
-}
-
-/// Holds the Windows Job Object that ensures child processes are killed when the app exits.
-/// On Windows, when the job object handle is closed (including on crash), all assigned
-/// processes are automatically terminated by the OS.
-#[cfg(windows)]
-pub struct JobObjectState {
- job: Mutex