From 6a5e329427458619749f9c83e5374b249f87322c Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 5 May 2026 10:34:06 +1000 Subject: [PATCH 01/18] fix(vcs): preserve batched patch boundaries (#25787) --- packages/opencode/src/project/vcs.ts | 4 ++- packages/opencode/test/project/vcs.test.ts | 27 ++++++++++++++ .../ui/src/components/session-diff.test.ts | 16 +++++++++ packages/ui/src/components/session-diff.ts | 35 ++++++++++--------- 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index 28ac143ee..8b3bedbf5 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -85,7 +85,9 @@ const fileFromPatchChunk = (chunk: string) => { } const splitGitPatch = (patch: Git.Patch) => { - const starts = [...patch.text.matchAll(/^diff --git /gm)].map((match) => match.index) + const starts = [...patch.text.matchAll(/(?:^|\n)diff --git /g)].map((match) => + match[0].startsWith("\n") ? match.index + 1 : match.index, + ) const chunks = starts.map((start, index) => patch.text.slice(start, starts[index + 1] ?? patch.text.length)) if (!patch.truncated) return chunks return chunks.slice(0, -1) diff --git a/packages/opencode/test/project/vcs.test.ts b/packages/opencode/test/project/vcs.test.ts index 53ff547ac..06da6ccba 100644 --- a/packages/opencode/test/project/vcs.test.ts +++ b/packages/opencode/test/project/vcs.test.ts @@ -1,5 +1,6 @@ import { $ } from "bun" import { afterEach, describe, expect, test } from "bun:test" +import { parsePatch } from "diff" import { Effect } from "effect" import fs from "fs/promises" import path from "path" @@ -288,6 +289,32 @@ describe("Vcs diff", () => { }) }) + test( + "diff('git') keeps carriage returns inside patch hunks", + async () => { + await using tmp = await tmpdir({ git: true }) + await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nsame\rdiff --git inside\ndelete\n", "utf-8") + await $`git add .`.cwd(tmp.path).quiet() + await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() + await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nadd\nsame\rdiff --git inside\n", "utf-8") + + await withVcsOnly(tmp.path, async () => { + const diff = await AppRuntime.runPromise( + Effect.gen(function* () { + const vcs = yield* Vcs.Service + return yield* vcs.diff("git") + }), + ) + const file = diff.find((item) => item.file === "file.txt") + + expect(file?.patch).toContain(" same\rdiff --git inside") + expect(file?.patch).toContain("-delete") + expect(() => parsePatch(file?.patch ?? "")).not.toThrow() + }) + }, + 20_000, + ) + test("diff('branch') returns changes against default branch", async () => { await using tmp = await tmpdir({ git: true }) await $`git branch -M main`.cwd(tmp.path).quiet() diff --git a/packages/ui/src/components/session-diff.test.ts b/packages/ui/src/components/session-diff.test.ts index 463a72977..edaa15b84 100644 --- a/packages/ui/src/components/session-diff.test.ts +++ b/packages/ui/src/components/session-diff.test.ts @@ -34,4 +34,20 @@ describe("session diff", () => { expect(text(view, "deletions")).toBe("one\n") expect(text(view, "additions")).toBe("two\n") }) + + test("ignores malformed persisted patches", () => { + const diff = { + file: "a.ts", + patch: + "diff --git a/a.ts b/a.ts\nindex ff4ceb2..65a1de0 100644\n--- a/a.ts\n+++ b/a.ts\n@@ -1,3 +1,3 @@\n keep\n+add\n same\r", + additions: 1, + deletions: 1, + status: "modified" as const, + } + const view = normalize(diff) + + expect(view.patch).toBe(diff.patch) + expect(text(view, "deletions")).toBe("") + expect(text(view, "additions")).toBe("") + }) }) diff --git a/packages/ui/src/components/session-diff.ts b/packages/ui/src/components/session-diff.ts index a5fbdbc5c..2da8c61a7 100644 --- a/packages/ui/src/components/session-diff.ts +++ b/packages/ui/src/components/session-diff.ts @@ -27,26 +27,29 @@ const cache = new Map() function patch(diff: ReviewDiff) { if (typeof diff.patch === "string") { - const [patch] = parsePatch(diff.patch) + try { + const [patch] = parsePatch(diff.patch) + const beforeLines = [] + const afterLines = [] - const beforeLines = [] - const afterLines = [] - - for (const hunk of patch.hunks) { - for (const line of hunk.lines) { - if (line.startsWith("-")) { - beforeLines.push(line.slice(1)) - } else if (line.startsWith("+")) { - afterLines.push(line.slice(1)) - } else { - // context line (starts with ' ') - beforeLines.push(line.slice(1)) - afterLines.push(line.slice(1)) + for (const hunk of patch.hunks) { + for (const line of hunk.lines) { + if (line.startsWith("-")) { + beforeLines.push(line.slice(1)) + } else if (line.startsWith("+")) { + afterLines.push(line.slice(1)) + } else { + // context line (starts with ' ') + beforeLines.push(line.slice(1)) + afterLines.push(line.slice(1)) + } } } - } - return { before: beforeLines.join("\n"), after: afterLines.join("\n"), patch: diff.patch } + return { before: beforeLines.join("\n"), after: afterLines.join("\n"), patch: diff.patch } + } catch { + return { before: "", after: "", patch: diff.patch } + } } return { before: "before" in diff && typeof diff.before === "string" ? diff.before : "", From f14784d5319c5fc4f6e298819d8112ee6aa5342c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 5 May 2026 00:35:18 +0000 Subject: [PATCH 02/18] chore: generate --- packages/opencode/test/project/vcs.test.ts | 42 ++++++++++------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/opencode/test/project/vcs.test.ts b/packages/opencode/test/project/vcs.test.ts index 06da6ccba..82eacfb6d 100644 --- a/packages/opencode/test/project/vcs.test.ts +++ b/packages/opencode/test/project/vcs.test.ts @@ -289,31 +289,27 @@ describe("Vcs diff", () => { }) }) - test( - "diff('git') keeps carriage returns inside patch hunks", - async () => { - await using tmp = await tmpdir({ git: true }) - await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nsame\rdiff --git inside\ndelete\n", "utf-8") - await $`git add .`.cwd(tmp.path).quiet() - await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() - await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nadd\nsame\rdiff --git inside\n", "utf-8") + test("diff('git') keeps carriage returns inside patch hunks", async () => { + await using tmp = await tmpdir({ git: true }) + await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nsame\rdiff --git inside\ndelete\n", "utf-8") + await $`git add .`.cwd(tmp.path).quiet() + await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet() + await fs.writeFile(path.join(tmp.path, "file.txt"), "keep\nadd\nsame\rdiff --git inside\n", "utf-8") - await withVcsOnly(tmp.path, async () => { - const diff = await AppRuntime.runPromise( - Effect.gen(function* () { - const vcs = yield* Vcs.Service - return yield* vcs.diff("git") - }), - ) - const file = diff.find((item) => item.file === "file.txt") + await withVcsOnly(tmp.path, async () => { + const diff = await AppRuntime.runPromise( + Effect.gen(function* () { + const vcs = yield* Vcs.Service + return yield* vcs.diff("git") + }), + ) + const file = diff.find((item) => item.file === "file.txt") - expect(file?.patch).toContain(" same\rdiff --git inside") - expect(file?.patch).toContain("-delete") - expect(() => parsePatch(file?.patch ?? "")).not.toThrow() - }) - }, - 20_000, - ) + expect(file?.patch).toContain(" same\rdiff --git inside") + expect(file?.patch).toContain("-delete") + expect(() => parsePatch(file?.patch ?? "")).not.toThrow() + }) + }, 20_000) test("diff('branch') returns changes against default branch", async () => { await using tmp = await tmpdir({ git: true }) From 6b852774e18c2bdabfd8754d3e1c506c7db76bff Mon Sep 17 00:00:00 2001 From: opencode Date: Tue, 5 May 2026 01:01:47 +0000 Subject: [PATCH 03/18] sync release versions for v1.14.35 --- bun.lock | 32 +++++++++++++------------- 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/core/package.json | 2 +- packages/desktop-electron/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/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 3cf2d9ce9..07415dd79 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -85,7 +85,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -119,7 +119,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -146,7 +146,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -170,7 +170,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -194,7 +194,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.14.34", + "version": "1.14.35", "bin": { "opencode": "./bin/opencode", }, @@ -228,7 +228,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -263,7 +263,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -309,7 +309,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -338,7 +338,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -354,7 +354,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.34", + "version": "1.14.35", "bin": { "opencode": "./bin/opencode", }, @@ -496,7 +496,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -531,7 +531,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "cross-spawn": "catalog:", }, @@ -546,7 +546,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -581,7 +581,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -630,7 +630,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.34", + "version": "1.14.35", "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 ac9bfd590..cde4986d1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.34", + "version": "1.14.35", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 85e855c55..fb2e71d22 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index d5157a372..7301b23e5 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.14.34", + "version": "1.14.35", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 0bb126541..06fb0affd 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.34", + "version": "1.14.35", "$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 b685bb1aa..674fc55fd 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.34", + "version": "1.14.35", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/core/package.json b/packages/core/package.json index 5f3371b98..e90ab7628 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.34", + "version": "1.14.35", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 8a6fcf578..ba981e637 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 2ec5cd059..e60320300 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index fe9de8584..dce25e204 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.34", + "version": "1.14.35", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 17b51a625..775f826d4 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.14.34" +version = "1.14.35" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.34/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.35/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.34/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.35/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.34/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.35/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.34/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.35/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.34/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.35/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index f9044078b..1039677b5 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.34", + "version": "1.14.35", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 08d317151..bafa532de 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.34", + "version": "1.14.35", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index a8c17f19f..661201d2d 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.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index b3e12fc25..bef0fee14 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.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 8a2ba85b0..448df6640 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 0c8621623..dcf52499d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.34", + "version": "1.14.35", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 8187602b0..a243a4707 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.14.34", + "version": "1.14.35", "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 43f07930e..22d8adc54 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.14.34", + "version": "1.14.35", "publisher": "sst-dev", "repository": { "type": "git", From ca2411d332f4f7a98f44aa974a1b9d992d27dc8f Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 5 May 2026 11:05:53 +1000 Subject: [PATCH 04/18] Run UI unit tests in CI (#25792) --- packages/ui/package.json | 2 ++ .../ui/src/components/session-diff.test.ts | 15 ++++++++ packages/ui/src/components/session-diff.ts | 34 +++++++++++++++---- turbo.json | 9 +++++ 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index dcf52499d..1bc70c15a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -25,6 +25,8 @@ }, "scripts": { "typecheck": "tsgo --noEmit", + "test": "bun test src", + "test:ci": "mkdir -p .artifacts/unit && bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "dev": "vite", "generate:tailwind": "bun run script/tailwind.ts" }, diff --git a/packages/ui/src/components/session-diff.test.ts b/packages/ui/src/components/session-diff.test.ts index edaa15b84..172fe8d6c 100644 --- a/packages/ui/src/components/session-diff.test.ts +++ b/packages/ui/src/components/session-diff.test.ts @@ -19,6 +19,21 @@ describe("session diff", () => { expect(text(view, "additions")).toBe("one\nthree\n") }) + test("keeps missing final newlines from unified patches", () => { + const diff = { + file: "a.ts", + patch: + "Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n\\ No newline at end of file\n+three\n\\ No newline at end of file\n", + additions: 1, + deletions: 1, + status: "modified" as const, + } + const view = normalize(diff) + + expect(text(view, "deletions")).toBe("one\ntwo") + expect(text(view, "additions")).toBe("one\nthree") + }) + test("converts legacy content into a patch", () => { const diff = { file: "a.ts", diff --git a/packages/ui/src/components/session-diff.ts b/packages/ui/src/components/session-diff.ts index 2da8c61a7..bd6bed88d 100644 --- a/packages/ui/src/components/session-diff.ts +++ b/packages/ui/src/components/session-diff.ts @@ -29,24 +29,44 @@ function patch(diff: ReviewDiff) { if (typeof diff.patch === "string") { try { const [patch] = parsePatch(diff.patch) - const beforeLines = [] - const afterLines = [] + const beforeLines: Array<{ text: string; newline: boolean }> = [] + const afterLines: Array<{ text: string; newline: boolean }> = [] + let previous: "-" | "+" | " " | undefined for (const hunk of patch.hunks) { for (const line of hunk.lines) { + if (line.startsWith("\\")) { + if (previous === "-" || previous === " ") { + const before = beforeLines.at(-1) + if (before) before.newline = false + } + if (previous === "+" || previous === " ") { + const after = afterLines.at(-1) + if (after) after.newline = false + } + continue + } + if (line.startsWith("-")) { - beforeLines.push(line.slice(1)) + beforeLines.push({ text: line.slice(1), newline: true }) + previous = "-" } else if (line.startsWith("+")) { - afterLines.push(line.slice(1)) + afterLines.push({ text: line.slice(1), newline: true }) + previous = "+" } else { // context line (starts with ' ') - beforeLines.push(line.slice(1)) - afterLines.push(line.slice(1)) + beforeLines.push({ text: line.slice(1), newline: true }) + afterLines.push({ text: line.slice(1), newline: true }) + previous = " " } } } - return { before: beforeLines.join("\n"), after: afterLines.join("\n"), patch: diff.patch } + return { + before: beforeLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""), + after: afterLines.map((line) => line.text + (line.newline ? "\n" : "")).join(""), + patch: diff.patch, + } } catch { return { before: "", after: "", patch: diff.patch } } diff --git a/turbo.json b/turbo.json index 28c2fa2de..0183fabca 100644 --- a/turbo.json +++ b/turbo.json @@ -26,6 +26,15 @@ "dependsOn": ["^build"], "outputs": [".artifacts/unit/junit.xml"], "passThroughEnv": ["*"] + }, + "@opencode-ai/ui#test": { + "dependsOn": ["^build"], + "outputs": [] + }, + "@opencode-ai/ui#test:ci": { + "dependsOn": ["^build"], + "outputs": [".artifacts/unit/junit.xml"], + "passThroughEnv": ["*"] } } } From 84afd2bef8d114b41a6cb9b38074ea5cb4c6d4f9 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 5 May 2026 09:19:13 +0800 Subject: [PATCH 05/18] update: normalize download asset names to match new naming convention (#25796) --- .../app/src/routes/download/[channel]/[platform].ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/console/app/src/routes/download/[channel]/[platform].ts b/packages/console/app/src/routes/download/[channel]/[platform].ts index b486acb99..4ae8e2465 100644 --- a/packages/console/app/src/routes/download/[channel]/[platform].ts +++ b/packages/console/app/src/routes/download/[channel]/[platform].ts @@ -2,11 +2,11 @@ import type { APIEvent } from "@solidjs/start" import type { DownloadPlatform } from "../types" const prodAssetNames: Record = { - "darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg", - "darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg", - "windows-x64-nsis": "opencode-desktop-windows-x64.exe", + "darwin-aarch64-dmg": "opencode-desktop-mac-arm64.dmg", + "darwin-x64-dmg": "opencode-desktop-mac-x64.dmg", + "windows-x64-nsis": "opencode-desktop-win-x64.exe", "linux-x64-deb": "opencode-desktop-linux-amd64.deb", - "linux-x64-appimage": "opencode-desktop-linux-amd64.AppImage", + "linux-x64-appimage": "opencode-desktop-linux-x86_64.AppImage", "linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm", } satisfies Record From 22a4a9df8b98f998f526df983393df885388d569 Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 4 May 2026 21:28:38 -0400 Subject: [PATCH 06/18] feat(core): session warping (#25768) --- .../migration.sql | 1 + .../snapshot.json | 1429 +++++++++++++++++ packages/opencode/script/httpapi-exercise.ts | 4 +- .../cmd/tui/component/dialog-session-list.tsx | 120 +- .../tui/component/dialog-workspace-create.tsx | 288 ++-- .../cli/cmd/tui/component/prompt/index.tsx | 357 ++-- .../cli/cmd/tui/component/workspace-label.tsx | 19 + .../cli/cmd/tui/routes/session/sidebar.tsx | 29 +- .../src/cli/cmd/tui/ui/dialog-select.tsx | 53 +- .../opencode/src/control-plane/workspace.ts | 281 ++-- .../src/server/routes/control/workspace.ts | 74 +- .../routes/instance/httpapi/groups/sync.ts | 16 + .../instance/httpapi/groups/workspace.ts | 29 +- .../routes/instance/httpapi/handlers/sync.ts | 24 +- .../instance/httpapi/handlers/workspace.ts | 15 +- .../src/server/routes/instance/index.ts | 2 +- .../src/server/routes/instance/sync.ts | 47 + packages/opencode/src/sync/event.sql.ts | 1 + packages/opencode/src/sync/index.ts | 34 +- .../test/control-plane/workspace.test.ts | 506 ++---- .../test/server/httpapi-workspace.test.ts | 19 +- packages/opencode/test/sync/index.test.ts | 73 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 266 ++- packages/sdk/js/src/v2/gen/types.gen.ts | 631 +++++++- packages/sdk/openapi.json | 809 +++++++++- 25 files changed, 4032 insertions(+), 1095 deletions(-) create mode 100644 packages/opencode/migration/20260504145000_add_sync_owner/migration.sql create mode 100644 packages/opencode/migration/20260504145000_add_sync_owner/snapshot.json create mode 100644 packages/opencode/src/cli/cmd/tui/component/workspace-label.tsx diff --git a/packages/opencode/migration/20260504145000_add_sync_owner/migration.sql b/packages/opencode/migration/20260504145000_add_sync_owner/migration.sql new file mode 100644 index 000000000..3bdf2b85e --- /dev/null +++ b/packages/opencode/migration/20260504145000_add_sync_owner/migration.sql @@ -0,0 +1 @@ +ALTER TABLE `event_sequence` ADD `owner_id` text; \ No newline at end of file diff --git a/packages/opencode/migration/20260504145000_add_sync_owner/snapshot.json b/packages/opencode/migration/20260504145000_add_sync_owner/snapshot.json new file mode 100644 index 000000000..4f6ebe00c --- /dev/null +++ b/packages/opencode/migration/20260504145000_add_sync_owner/snapshot.json @@ -0,0 +1,1429 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "27114226-085b-421a-9a40-29b88747e29a", + "prevIds": ["aaa2ebeb-caa4-478d-8365-4fc595d16856"], + "ddl": [ + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "account", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session_entry", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "name": "event_sequence", + "entityType": "tables" + }, + { + "name": "event", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_org_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "''", + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url_override", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "path", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "owner_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "event" + }, + { + "columns": ["active_account_id"], + "tableTo": "account", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": ["message_id"], + "tableTo": "message", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_entry_session_id_session_id_fk", + "entityType": "fks", + "table": "session_entry" + }, + { + "columns": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": ["aggregate_id"], + "tableTo": "event_sequence", + "columnsTo": ["aggregate_id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_event_aggregate_id_event_sequence_aggregate_id_fk", + "entityType": "fks", + "table": "event" + }, + { + "columns": ["email", "url"], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": ["session_id", "position"], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": ["project_id"], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "session_entry_pk", + "table": "session_entry", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": ["session_id"], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": ["aggregate_id"], + "nameExplicit": false, + "name": "event_sequence_pk", + "table": "event_sequence", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "event_pk", + "table": "event", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_time_created_id_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_id_id_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_entry_session_idx", + "entityType": "indexes", + "table": "session_entry" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "type", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_entry_session_type_idx", + "entityType": "indexes", + "table": "session_entry" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_entry_time_created_idx", + "entityType": "indexes", + "table": "session_entry" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} diff --git a/packages/opencode/script/httpapi-exercise.ts b/packages/opencode/script/httpapi-exercise.ts index 9755cf401..771e1e417 100644 --- a/packages/opencode/script/httpapi-exercise.ts +++ b/packages/opencode/script/httpapi-exercise.ts @@ -776,9 +776,9 @@ const scenarios: Scenario[] = [ })) .status(200), http - .post("/experimental/workspace/{id}/session-restore", "experimental.workspace.sessionRestore") + .post("/experimental/workspace/warp", "experimental.workspace.warp") .at((ctx) => ({ - path: route("/experimental/workspace/{id}/session-restore", { id: "wrk_httpapi_missing" }), + path: "/experimental/workspace/warp", headers: ctx.headers(), body: {}, })) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 04c6b9945..09d952ef8 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -2,7 +2,7 @@ import { useDialog } from "@tui/ui/dialog" import { DialogSelect } from "@tui/ui/dialog-select" import { useRoute } from "@tui/context/route" import { useSync } from "@tui/context/sync" -import { createMemo, createResource, createSignal, onMount } from "solid-js" +import { createMemo, createResource, createSignal, onMount, type JSX } from "solid-js" import { Locale } from "@/util/locale" import { useProject } from "@tui/context/project" import { useKeybind } from "../context/keybind" @@ -10,15 +10,13 @@ import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" import { Flag } from "@opencode-ai/core/flag/flag" import { DialogSessionRename } from "./dialog-session-rename" -import { Keybind } from "@/util/keybind" import { createDebouncedSignal } from "../util/signal" import { useToast } from "../ui/toast" -import { DialogWorkspaceCreate, openWorkspaceSession, restoreWorkspaceSession } from "./dialog-workspace-create" +import { openWorkspaceSelect, type WorkspaceSelection, warpWorkspaceSession } from "./dialog-workspace-create" import { Spinner } from "./spinner" import { errorMessage } from "@/util/error" import { DialogSessionDeleteFailed } from "./dialog-session-delete-failed" - -type WorkspaceStatus = "connected" | "connecting" | "disconnected" | "error" +import { WorkspaceLabel } from "./workspace-label" export function DialogSessionList() { const dialog = useDialog() @@ -44,26 +42,39 @@ export function DialogSessionList() { const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) const sessions = createMemo(() => searchResults() ?? sync.data.session) - function createWorkspace() { - dialog.replace(() => ( - - openWorkspaceSession({ - dialog, - route, - sdk, - sync, - toast, - workspaceID, - }) - } - /> - )) - } - function recover(session: NonNullable[number]>) { const workspace = project.workspace.get(session.workspaceID!) const list = () => dialog.replace(() => ) + const warp = async (selection: WorkspaceSelection) => { + const workspaceID = await (async () => { + if (selection.type === "none") return null + if (selection.type === "existing") return selection.workspaceID + const result = await sdk.client.experimental.workspace + .create({ type: selection.workspaceType, branch: null }) + .catch(() => undefined) + const workspace = result?.data + if (!workspace) { + toast.show({ + message: `Failed to create workspace: ${errorMessage(result?.error ?? "no response")}`, + variant: "error", + }) + return + } + await project.workspace.sync() + return workspace.id + })() + if (workspaceID === undefined) return + await warpWorkspaceSession({ + dialog, + sdk, + sync, + project, + toast, + workspaceID, + sessionID: session.id, + done: list, + }) + } dialog.replace(() => ( { - dialog.replace(() => ( - - restoreWorkspaceSession({ - dialog, - sdk, - sync, - project, - toast, - workspaceID, - sessionID: session.id, - done: list, - }) - } - /> - )) + void openWorkspaceSelect({ + dialog, + sdk, + sync, + toast, + onSelect: (selection) => { + void warp(selection) + }, + }) return false }} /> @@ -124,30 +128,17 @@ export function DialogSessionList() { .map((x) => { const workspace = x.workspaceID ? project.workspace.get(x.workspaceID) : undefined - let workspaceStatus: WorkspaceStatus | null = null - if (x.workspaceID) { - workspaceStatus = project.workspace.status(x.workspaceID) || "error" - } - - let footer = "" + let footer: JSX.Element | string = "" if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) { if (x.workspaceID) { - let desc = "unknown" - if (workspace) { - desc = `${workspace.type}: ${workspace.name}` - } - - footer = ( - <> - {desc}{" "} - - ● - - + footer = workspace ? ( + + ) : ( + ) } } else { @@ -250,15 +241,6 @@ export function DialogSessionList() { dialog.replace(() => ) }, }, - { - keybind: Keybind.parse("ctrl+w")[0], - title: "new workspace", - side: "right", - disabled: !Flag.OPENCODE_EXPERIMENTAL_WORKSPACES, - onTrigger: () => { - createWorkspace() - }, - }, ]} /> ) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index 0aa61c313..e2af0d63e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -1,11 +1,9 @@ -import { createOpencodeClient } from "@opencode-ai/sdk/v2" +import type { Workspace } from "@opencode-ai/sdk/v2" import { useDialog } from "@tui/ui/dialog" -import { DialogSelect } from "@tui/ui/dialog-select" -import { useRoute } from "@tui/context/route" +import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select" import { useSync } from "@tui/context/sync" import { useProject } from "@tui/context/project" import { createMemo, createSignal, onMount } from "solid-js" -import { setTimeout as sleep } from "node:timers/promises" import { errorMessage } from "@/util/error" import { useSDK } from "../context/sdk" import { useToast } from "../ui/toast" @@ -16,184 +14,212 @@ type Adapter = { description: string } -function scoped(sdk: ReturnType, sync: ReturnType, workspaceID: string) { - return createOpencodeClient({ - baseUrl: sdk.url, - fetch: sdk.fetch, - directory: sync.path.directory || sdk.directory, - experimental_workspaceID: workspaceID, - }) -} +export type WorkspaceSelection = + | { + type: "none" + } + | { + type: "new" + workspaceType: string + workspaceName: string + } + | { + type: "existing" + workspaceID: string + workspaceType: string + workspaceName: string + } -export async function openWorkspaceSession(input: { - dialog: ReturnType - route: ReturnType +type WorkspaceSelectValue = WorkspaceSelection | { type: "existing-list" } +type ExistingWorkspaceSelectValue = { workspace: Workspace } + +async function loadWorkspaceAdapters(input: { sdk: ReturnType sync: ReturnType toast: ReturnType - workspaceID: string }) { - const client = scoped(input.sdk, input.sync, input.workspaceID) - - while (true) { - const result = await client.session.create({ workspace: input.workspaceID }).catch(() => undefined) - if (!result) { - input.toast.show({ - message: "Failed to create workspace session", - variant: "error", - }) - return - } - if (result.response?.status && result.response.status >= 500 && result.response.status < 600) { - await sleep(1000) - continue - } - if (!result.data) { - input.toast.show({ - message: "Failed to create workspace session", - variant: "error", - }) - return - } - - input.route.navigate({ - type: "session", - sessionID: result.data.id, - }) - input.dialog.clear() - return - } + const dir = input.sync.path.directory || input.sdk.directory + const url = new URL("/experimental/workspace/adapter", input.sdk.url) + if (dir) url.searchParams.set("directory", dir) + const res = await input.sdk + .fetch(url) + .then((x) => x.json() as Promise) + .catch(() => undefined) + if (res) return res + input.toast.show({ + message: "Failed to load workspace adapters", + variant: "error", + }) } -export async function restoreWorkspaceSession(input: { +export async function openWorkspaceSelect(input: { + dialog: ReturnType + sdk: ReturnType + sync: ReturnType + toast: ReturnType + onSelect: (selection: WorkspaceSelection) => Promise | void +}) { + input.dialog.clear() + const adapters = await loadWorkspaceAdapters(input) + if (!adapters) return + input.dialog.replace(() => ) +} + +export async function warpWorkspaceSession(input: { dialog: ReturnType sdk: ReturnType sync: ReturnType project: ReturnType toast: ReturnType - workspaceID: string + workspaceID: string | null sessionID: string done?: () => void -}) { +}): Promise { const result = await input.sdk.client.experimental.workspace - .sessionRestore({ id: input.workspaceID, sessionID: input.sessionID }) + .warp({ + id: input.workspaceID, + sessionID: input.sessionID, + }) .catch(() => undefined) if (!result?.data) { input.toast.show({ - message: `Failed to restore session: ${errorMessage(result?.error ?? "no response")}`, + message: `Failed to warp session: ${errorMessage(result?.error ?? "no response")}`, variant: "error", }) - return + return false } input.project.workspace.set(input.workspaceID) await input.sync.bootstrap({ fatal: false }).catch(() => undefined) - await Promise.all([input.project.workspace.sync(), input.sync.session.sync(input.sessionID)]) + await Promise.all([input.project.workspace.sync(), input.sync.session.refresh()]) - input.toast.show({ - message: "Session restored into the new workspace", - variant: "success", - }) input.done?.() - if (input.done) return + if (input.done) return true input.dialog.clear() + return true } -export function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promise | void }) { +export function DialogWorkspaceSelect(props: { + adapters?: Adapter[] + onSelect: (selection: WorkspaceSelection) => Promise | void +}) { const dialog = useDialog() - const sync = useSync() const project = useProject() + const sync = useSync() const sdk = useSDK() const toast = useToast() - const [creating, setCreating] = createSignal() - const [adapters, setAdapters] = createSignal() + const [adapters, setAdapters] = createSignal(props.adapters) onMount(() => { dialog.setSize("medium") void (async () => { - const dir = sync.path.directory || sdk.directory - const url = new URL("/experimental/workspace/adapter", sdk.url) - if (dir) url.searchParams.set("directory", dir) - const res = await sdk - .fetch(url) - .then((x) => x.json() as Promise) - .catch(() => undefined) - if (!res) { - toast.show({ - message: "Failed to load workspace adapters", - variant: "error", - }) - return - } + if (adapters()) return + const res = await loadWorkspaceAdapters({ sdk, sync, toast }) + if (!res) return setAdapters(res) })() }) - const options = createMemo(() => { - const type = creating() - if (type) { - return [ - { - title: `Creating ${type} workspace...`, - value: "creating" as const, - description: "This can take a while for remote environments", - }, - ] - } + const options = createMemo[]>(() => { const list = adapters() - if (!list) { - return [ - { - title: "Loading workspaces...", - value: "loading" as const, - description: "Fetching available workspace adapters", + if (!list) return [] + const recent = sync.data.session + .toSorted((a, b) => b.time.updated - a.time.updated) + .flatMap((session) => (session.workspaceID ? [session.workspaceID] : [])) + .filter((workspaceID, index, list) => list.indexOf(workspaceID) === index) + .slice(0, 3) + .flatMap((workspaceID) => { + const workspace = project.workspace.get(workspaceID) + return workspace ? [workspace] : [] + }) + return [ + ...list.map((adapter) => ({ + title: adapter.name, + value: { type: "new" as const, workspaceType: adapter.type, workspaceName: adapter.name }, + description: adapter.description, + category: "New workspace", + })), + { + title: "None", + value: { type: "none" as const }, + description: "Use the local project", + category: "Choose workspace", + }, + ...recent.map((workspace: Workspace) => ({ + title: workspace.name, + description: `(${workspace.type})`, + value: { + type: "existing" as const, + workspaceID: workspace.id, + workspaceType: workspace.type, + workspaceName: workspace.name, }, - ] - } - return list.map((item) => ({ - title: item.name, - value: item.type, - description: item.description, - })) + category: "Choose workspace", + })), + { + title: "View all workspaces", + value: { type: "existing-list" as const }, + description: "Choose from all workspaces", + category: "Choose workspace", + }, + ] }) - const create = async (type: string) => { - if (creating()) return - setCreating(type) - - const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch(() => { - toast.show({ - message: "Creating workspace failed", - variant: "error", - }) - return undefined - }) - - const workspace = result?.data - if (!workspace) { - setCreating(undefined) - toast.show({ - message: `Failed to create workspace: ${errorMessage(result?.error ?? "no response")}`, - variant: "error", - }) - return - } - - await project.workspace.sync() - await props.onSelect(workspace.id) - setCreating(undefined) - } - + if (!adapters()) return null return ( - + title="Warp" skipFilter={true} + renderFilter={false} options={options()} onSelect={(option) => { - if (option.value === "creating" || option.value === "loading") return - void create(option.value) + if (!option.value) return + if (option.value.type === "none") { + void props.onSelect(option.value) + return + } + if (option.value.type === "new") { + void props.onSelect(option.value) + return + } + if (option.value.type === "existing") { + void props.onSelect(option.value) + return + } + + dialog.replace(() => ) + }} + /> + ) +} + +function DialogExistingWorkspaceSelect(props: { onSelect: (selection: WorkspaceSelection) => Promise | void }) { + const project = useProject() + + const options = createMemo[]>(() => + project.workspace + .list() + .filter((workspace) => project.workspace.status(workspace.id) === "connected") + .map((workspace: Workspace) => ({ + title: workspace.name, + description: `(${workspace.type})`, + value: { workspace }, + })), + ) + + return ( + + title="Existing Workspace" + options={options()} + onSelect={(option) => { + void props.onSelect({ + type: "existing", + workspaceID: option.value.workspace.id, + workspaceType: option.value.workspace.type, + workspaceName: option.value.workspace.name, + }) }} /> ) 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 a6ba797f3..74332c77b 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -7,6 +7,7 @@ import { Filesystem } from "@/util/filesystem" import { useLocal } from "@tui/context/local" import { tint, useTheme } from "@tui/context/theme" import { EmptyBorder, SplitBorder } from "@tui/component/border" +import { Spinner } from "@tui/component/spinner" import { useSDK } from "@tui/context/sdk" import { useRoute } from "@tui/context/route" import { useProject } from "@tui/context/project" @@ -41,9 +42,11 @@ import { useKV } from "../../context/kv" import { createFadeIn } from "../../util/signal" import { useTextareaKeybindings } from "../textarea-keybindings" import { DialogSkill } from "../dialog-skill" -import { DialogWorkspaceCreate, restoreWorkspaceSession } from "../dialog-workspace-create" +import { openWorkspaceSelect, warpWorkspaceSession, type WorkspaceSelection } from "../dialog-workspace-create" import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable" import { useArgs } from "@tui/context/args" +import { Flag } from "@opencode-ai/core/flag/flag" +import { WorkspaceLabel, type WorkspaceStatus } from "../workspace-label" export type PromptProps = { sessionID?: string @@ -173,9 +176,92 @@ export function Prompt(props: PromptProps) { const [editorContextHover, setEditorContextHover] = createSignal(false) let lastSubmittedEditorSelectionKey: string | undefined const [auto, setAuto] = createSignal() + const [workspaceSelection, setWorkspaceSelection] = createSignal() + const [workspaceCreating, setWorkspaceCreating] = createSignal(false) + const [workspaceCreatingDots, setWorkspaceCreatingDots] = createSignal(3) + const [warpNotice, setWarpNotice] = createSignal() const currentProviderLabel = createMemo(() => local.model.parsed().provider) const hasRightContent = createMemo(() => Boolean(props.right)) + function selectWorkspace(selection: WorkspaceSelection | undefined) { + setWorkspaceSelection(selection) + } + + function setCreatingWorkspace(creating: boolean) { + setWorkspaceCreating(creating) + } + + function showWarpNotice(name: string) { + setWarpNotice(`Warped to ${name}`) + setTimeout(() => setWarpNotice(undefined), 4000) + } + + async function createWorkspace(selection: Extract) { + setCreatingWorkspace(true) + const result = await sdk.client.experimental.workspace + .create({ type: selection.workspaceType, branch: null }) + .catch(() => undefined) + if (result == undefined || result.error || !result.data) { + selectWorkspace(undefined) + setCreatingWorkspace(false) + toast.show({ + message: "Creating workspace failed", + variant: "error", + }) + return + } + + await project.workspace.sync() + const workspace = result.data + selectWorkspace({ + type: "existing", + workspaceID: workspace.id, + workspaceType: workspace.type, + workspaceName: workspace.name, + }) + setCreatingWorkspace(false) + return workspace + } + + async function warpSession(selection: WorkspaceSelection) { + if (!props.sessionID) { + selectWorkspace(selection) + dialog.clear() + if (selection.type === "new") void createWorkspace(selection) + return + } + selectWorkspace(selection) + dialog.clear() + + const workspace = + selection.type === "none" + ? { id: null, name: "local project" } + : selection.type === "existing" + ? { id: selection.workspaceID, name: selection.workspaceName } + : await createWorkspace(selection) + if (!workspace) return + + const warped = await warpWorkspaceSession({ + dialog, + sdk, + sync, + project, + toast, + workspaceID: workspace.id, + sessionID: props.sessionID, + }) + if (warped) showWarpNotice(workspace.name) + } + + createEffect(() => { + if (!workspaceCreating()) { + setWorkspaceCreatingDots(3) + return + } + const timer = setInterval(() => setWorkspaceCreatingDots((dots) => (dots % 3) + 1), 1000) + onCleanup(() => clearInterval(timer)) + }) + function promptModelWarning() { toast.show({ variant: "warning", @@ -213,6 +299,7 @@ export function Prompt(props: PromptProps) { }) createEffect(() => { + if (!input || input.isDestroyed) return if (props.disabled) input.cursorColor = theme.backgroundElement if (!props.disabled) input.cursorColor = theme.text }) @@ -489,6 +576,27 @@ export function Prompt(props: PromptProps) { )) }, }, + { + title: "Warp", + description: "Change the workspace for the session", + value: "workspace.set", + category: "Session", + enabled: Flag.OPENCODE_EXPERIMENTAL_WORKSPACES, + slash: { + name: "warp", + }, + onSelect: (dialog) => { + void openWorkspaceSelect({ + dialog, + sdk, + sync, + toast, + onSelect: (selection) => { + void warpSession(selection) + }, + }) + }, + }, ] }) @@ -699,6 +807,8 @@ export function Prompt(props: PromptProps) { ]) async function submit() { + setWarpNotice(undefined) + // IME: double-defer may fire before onContentChange flushes the last // composed character (e.g. Korean hangul) to the store, so read // plainText directly and sync before any downstream reads. @@ -707,6 +817,7 @@ export function Prompt(props: PromptProps) { syncExtmarksWithPromptParts() } if (props.disabled) return false + if (workspaceCreating()) return false if (autocomplete?.visible) return false if (!store.prompt.input) return false const agent = local.agent.current() @@ -729,21 +840,16 @@ export function Prompt(props: PromptProps) { dialog.replace(() => ( { - dialog.replace(() => ( - - restoreWorkspaceSession({ - dialog, - sdk, - sync, - project, - toast, - workspaceID: nextWorkspaceID, - sessionID: props.sessionID!, - }) - } - /> - )) + void openWorkspaceSelect({ + dialog, + sdk, + sync, + toast, + onSelect: (selection) => { + void warpSession(selection) + }, + }) + return false }} /> )) @@ -753,6 +859,14 @@ export function Prompt(props: PromptProps) { const variant = local.model.variant.current() let sessionID = props.sessionID if (sessionID == null) { + const workspace = workspaceSelection() + const workspaceID = iife(() => { + if (!workspace) return undefined + if (workspace.type === "none") return undefined + if (workspace.type === "existing") return workspace.workspaceID + return undefined + }) + const res = await sdk.client.session.create({ workspace: props.workspaceID, agent: agent.name, @@ -1025,6 +1139,29 @@ export function Prompt(props: PromptProps) { return `Ask anything... "${list()[store.placeholder % list().length]}"` }) + const workspaceLabel = createMemo< + | { type: "new"; workspaceType: string } + | { type: "existing"; workspaceType: string; workspaceName: string; status?: WorkspaceStatus } + | undefined + >(() => { + const selected = workspaceSelection() + if (!selected) return + if (selected.type === "none") return + if (props.sessionID && !workspaceCreating()) return + if (selected.type === "new") { + return { + type: "new", + workspaceType: selected.workspaceType, + } + } + return { + type: "existing", + workspaceType: selected.workspaceType, + workspaceName: selected.workspaceName, + status: selected.type === "existing" ? "connected" : undefined, + } + }) + const spinnerDef = createMemo(() => { const agent = local.agent.current() const color = agent ? local.agent.color(agent.name) : theme.border @@ -1281,7 +1418,7 @@ export function Prompt(props: PromptProps) { }} onMouseDown={(r: MouseEvent) => r.target?.focus()} focusedBackgroundColor={theme.backgroundElement} - cursorColor={theme.text} + cursorColor={props.disabled ? theme.backgroundElement : theme.text} syntaxStyle={syntax()} /> @@ -1351,86 +1488,124 @@ export function Prompt(props: PromptProps) { /> - }> - - - - [⋯]}> - - - - - {(() => { - const retry = createMemo(() => { - const s = status() - if (s.type !== "retry") return - return s - }) - const message = createMemo(() => { - const r = retry() - if (!r) return - if (r.message.includes("exceeded your current quota") && r.message.includes("gemini")) - return "gemini is way too hot right now" - if (r.message.length > 80) return r.message.slice(0, 80) + "..." - return r.message - }) - const isTruncated = createMemo(() => { - const r = retry() - if (!r) return false - return r.message.length > 120 - }) - const [seconds, setSeconds] = createSignal(0) - onMount(() => { - const timer = setInterval(() => { - const next = retry()?.next - if (next) setSeconds(Math.round((next - Date.now()) / 1000)) - }, 1000) - - onCleanup(() => { - clearInterval(timer) + + + + + + [⋯]}> + + + + + {(() => { + const retry = createMemo(() => { + const s = status() + if (s.type !== "retry") return + return s }) - }) - const handleMessageClick = () => { - const r = retry() - if (!r) return - if (isTruncated()) { - void DialogAlert.show(dialog, "Retry Error", r.message) + const message = createMemo(() => { + const r = retry() + if (!r) return + if (r.message.includes("exceeded your current quota") && r.message.includes("gemini")) + return "gemini is way too hot right now" + if (r.message.length > 80) return r.message.slice(0, 80) + "..." + return r.message + }) + const isTruncated = createMemo(() => { + const r = retry() + if (!r) return false + return r.message.length > 120 + }) + const [seconds, setSeconds] = createSignal(0) + onMount(() => { + const timer = setInterval(() => { + const next = retry()?.next + if (next) setSeconds(Math.round((next - Date.now()) / 1000)) + }, 1000) + + onCleanup(() => { + clearInterval(timer) + }) + }) + const handleMessageClick = () => { + const r = retry() + if (!r) return + if (isTruncated()) { + void DialogAlert.show(dialog, "Retry Error", r.message) + } } - } - const retryText = () => { - const r = retry() - if (!r) return "" - const baseMessage = message() - const truncatedHint = isTruncated() ? " (click to expand)" : "" - const duration = formatDuration(seconds()) - const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]` - return baseMessage + truncatedHint + retryInfo - } + const retryText = () => { + const r = retry() + if (!r) return "" + const baseMessage = message() + const truncatedHint = isTruncated() ? " (click to expand)" : "" + const duration = formatDuration(seconds()) + const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]` + return baseMessage + truncatedHint + retryInfo + } - return ( - - - {retryText()} - - - ) - })()} + return ( + + + {retryText()} + + + ) + })()} + + 0 ? theme.primary : theme.text}> + esc{" "} + 0 ? theme.primary : theme.textMuted }}> + {store.interrupt > 0 ? "again to interrupt" : "interrupt"} + + - 0 ? theme.primary : theme.text}> - esc{" "} - 0 ? theme.primary : theme.textMuted }}> - {store.interrupt > 0 ? "again to interrupt" : "interrupt"} - - - - + + + {(notice) => ( + + {notice()} + + )} + + + {(workspace) => ( + + + + + + {(() => { + const item = workspace() + if (item.type === "new") { + if (workspaceCreating()) + return `Creating ${item.workspaceType}${".".repeat(workspaceCreatingDots())}` + return ( + <> + Workspace (new {item.workspaceType}) + + ) + } + return ( + <> + Workspace {item.workspaceName} + + ) + })()} + + + )} + + {props.hint ?? } + diff --git a/packages/opencode/src/cli/cmd/tui/component/workspace-label.tsx b/packages/opencode/src/cli/cmd/tui/component/workspace-label.tsx new file mode 100644 index 000000000..efdbf7158 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/workspace-label.tsx @@ -0,0 +1,19 @@ +import { useTheme } from "@tui/context/theme" + +export type WorkspaceStatus = "connected" | "connecting" | "disconnected" | "error" + +export function WorkspaceLabel(props: { type: string; name: string; status?: WorkspaceStatus; icon?: boolean }) { + const { theme } = useTheme() + const color = () => { + if (props.status === "connected") return theme.success + if (props.status === "error") return theme.error + return theme.textMuted + } + + return ( + <> + {props.icon ? : undefined} + {props.name} ({props.type}) + + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 7adc4c1db..0f9214092 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -7,6 +7,7 @@ import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/inst import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime" import { getScrollAcceleration } from "../../util/scroll" +import { WorkspaceLabel } from "../../component/workspace-label" export function Sidebar(props: { sessionID: string; overlay?: boolean }) { const project = useProject() @@ -14,17 +15,10 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { const { theme } = useTheme() const tuiConfig = useTuiConfig() const session = createMemo(() => sync.session.get(props.sessionID)) - const workspaceStatus = () => { + const workspace = () => { const workspaceID = session()?.workspaceID - if (!workspaceID) return "error" - return project.workspace.status(workspaceID) ?? "error" - } - const workspaceLabel = () => { - const workspaceID = session()?.workspaceID - if (!workspaceID) return "unknown" - const info = project.workspace.get(workspaceID) - if (!info) return "unknown" - return `${info.type}: ${info.name}` + if (!workspaceID) return + return project.workspace.get(workspaceID) } const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig)) @@ -67,8 +61,19 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { - {" "} - {workspaceLabel()} + } + > + {(item) => ( + + )} + 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 4d68c4430..ef7d4bd3b 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -23,6 +23,7 @@ export interface DialogSelectProps { onFilter?: (query: string) => void onSelect?: (option: DialogSelectOption) => void skipFilter?: boolean + renderFilter?: boolean keybind?: { keybind?: Keybind.Info title: string @@ -81,7 +82,7 @@ export function DialogSelect(props: DialogSelectProps) { let input: InputRenderable const filtered = createMemo(() => { - if (props.skipFilter) return props.options.filter((x) => x.disabled !== true) + if (props.skipFilter || props.renderFilter === false) return props.options.filter((x) => x.disabled !== true) const needle = store.filter.toLowerCase() const options = pipe( props.options, @@ -250,30 +251,32 @@ export function DialogSelect(props: DialogSelectProps) { esc - - { - batch(() => { - setStore("filter", e) - props.onFilter?.(e) - }) - }} - focusedBackgroundColor={theme.backgroundPanel} - cursorColor={theme.primary} - focusedTextColor={theme.textMuted} - ref={(r) => { - input = r - input.traits = { status: "FILTER" } - setTimeout(() => { - if (!input) return - if (input.isDestroyed) return - input.focus() - }, 1) - }} - placeholder={props.placeholder ?? "Search"} - placeholderColor={theme.textMuted} - /> - + + + { + batch(() => { + setStore("filter", e) + props.onFilter?.(e) + }) + }} + focusedBackgroundColor={theme.backgroundPanel} + cursorColor={theme.primary} + focusedTextColor={theme.textMuted} + ref={(r) => { + input = r + input.traits = { status: "FILTER" } + setTimeout(() => { + if (!input) return + if (input.isDestroyed) return + input.focus() + }, 1) + }} + placeholder={props.placeholder ?? "Search"} + placeholderColor={theme.textMuted} + /> + + 0} diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 485cb2e92..fe651fe3e 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,10 +1,11 @@ -import { Context, Effect, FiberMap, Layer, Schema, Stream } from "effect" +import { Context, Effect, FiberMap, Iterable, Layer, Schema, Stream } from "effect" import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http" import { Database } from "@/storage/db" import { asc } from "drizzle-orm" import { eq } from "drizzle-orm" import { inArray } from "drizzle-orm" import { Project } from "@/project/project" +import { Instance } from "@/project/instance" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { Auth } from "@/auth" @@ -20,6 +21,7 @@ import { getAdapter } from "./adapters" import { type WorkspaceInfo, WorkspaceInfo as WorkspaceInfoSchema } from "./types" import { WorkspaceID } from "./schema" import { Session } from "@/session/session" +import { SessionPrompt } from "@/session/prompt" import { SessionTable } from "@/session/session.sql" import { SessionID } from "@/session/schema" import { errorData } from "@/util/error" @@ -38,13 +40,6 @@ export const ConnectionStatus = Schema.Struct({ }) export type ConnectionStatus = Schema.Schema.Type -const Restore = Schema.Struct({ - workspaceID: WorkspaceID, - sessionID: SessionID, - total: NonNegativeInt, - step: NonNegativeInt, -}) - export const Event = { Ready: BusEvent.define( "workspace.ready", @@ -58,7 +53,6 @@ export const Event = { message: Schema.String, }), ), - Restore: BusEvent.define("workspace.restore", Restore), Status: BusEvent.define("workspace.status", ConnectionStatus), } @@ -84,15 +78,15 @@ export const CreateInput = Schema.Struct({ type: Info.fields.type, branch: Info.fields.branch, projectID: ProjectID, - extra: Info.fields.extra, + extra: Schema.optional(Info.fields.extra), }).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) }))) export type CreateInput = Schema.Schema.Type -export const SessionRestoreInput = Schema.Struct({ - workspaceID: WorkspaceID, +export const SessionWarpInput = Schema.Struct({ + workspaceID: Schema.NullOr(WorkspaceID), sessionID: SessionID, }).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) }))) -export type SessionRestoreInput = Schema.Schema.Type +export type SessionWarpInput = Schema.Schema.Type export class SyncHttpError extends Schema.TaggedErrorClass()("WorkspaceSyncHttpError", { message: Schema.String, @@ -116,8 +110,8 @@ export class SessionEventsNotFoundError extends Schema.TaggedErrorClass()( - "WorkspaceSessionRestoreHttpError", +export class SessionWarpHttpError extends Schema.TaggedErrorClass()( + "WorkspaceSessionWarpHttpError", { message: Schema.String, workspaceID: WorkspaceID, @@ -138,17 +132,17 @@ export class SyncAbortedError extends Schema.TaggedErrorClass( }) {} type CreateError = Auth.AuthError -type SessionRestoreError = +type SessionWarpError = | WorkspaceNotFoundError | SessionEventsNotFoundError - | SessionRestoreHttpError + | SessionWarpHttpError | HttpClientError.HttpClientError type WaitForSyncError = SyncTimeoutError | SyncAbortedError type SyncLoopError = SyncHttpError | HttpClientError.HttpClientError export interface Interface { readonly create: (input: CreateInput) => Effect.Effect - readonly sessionRestore: (input: SessionRestoreInput) => Effect.Effect<{ total: number }, SessionRestoreError> + readonly sessionWarp: (input: SessionWarpInput) => Effect.Effect readonly list: (project: Project.Info) => Effect.Effect readonly get: (id: WorkspaceID) => Effect.Effect readonly remove: (id: WorkspaceID) => Effect.Effect @@ -169,6 +163,7 @@ export const layer = Layer.effect( Effect.gen(function* () { const auth = yield* Auth.Service const session = yield* Session.Service + const prompt = yield* SessionPrompt.Service const http = yield* HttpClient.HttpClient const sync = yield* SyncEvent.Service const connections = new Map() @@ -461,7 +456,7 @@ export const layer = Layer.effect( const id = WorkspaceID.ascending(input.id) const adapter = getAdapter(input.projectID, input.type) const config = yield* EffectBridge.fromPromise(() => - adapter.configure({ ...input, id, name: Slug.create(), directory: null }), + adapter.configure({ ...input, id, name: Slug.create(), directory: null, extra: input.extra ?? null }), ) const info: Info = { @@ -518,29 +513,93 @@ export const layer = Layer.effect( return info }) - const sessionRestore = Effect.fn("Workspace.sessionRestore")(function* (input: SessionRestoreInput) { + const sessionWarp = Effect.fn("Workspace.sessionWarp")(function* (input: SessionWarpInput) { return yield* Effect.gen(function* () { - log.info("session restore requested", { + log.info("session warp requested", { workspaceID: input.workspaceID, sessionID: input.sessionID, }) - const space = yield* get(input.workspaceID) + const current = yield* db((db) => + db + .select({ workspaceID: SessionTable.workspace_id }) + .from(SessionTable) + .where(eq(SessionTable.id, input.sessionID)) + .get(), + ) + + if (current?.workspaceID) { + const previous = yield* get(current.workspaceID) + if (previous) { + const adapter = getAdapter(previous.projectID, previous.type) + const target = yield* EffectBridge.fromPromise(() => adapter.target(previous)) + + if (target.type === "remote") { + yield* syncHistory(previous, target.url, target.headers).pipe( + Effect.catch((error) => + Effect.sync(() => { + log.warn("session warp final source sync failed", { + workspaceID: previous.id, + sessionID: input.sessionID, + error: errorData(error), + }) + }), + ), + ) + } else { + yield* prompt.cancel(input.sessionID) + } + + // "claim" this session so any future events coming from + // the old workspace are ignored + SyncEvent.claim(input.sessionID, input.workspaceID ?? Instance.project.id) + } + } + + if (input.workspaceID === null) { + yield* Effect.sync(() => + SyncEvent.run(Session.Event.Updated, { + sessionID: input.sessionID, + info: { + workspaceID: null, + }, + }), + ) + + log.info("session warp complete", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + target: "local", + }) + return + } + + const workspaceID = input.workspaceID + const space = yield* get(workspaceID) if (!space) return yield* new WorkspaceNotFoundError({ - message: `Workspace not found: ${input.workspaceID}`, - workspaceID: input.workspaceID, + message: `Workspace not found: ${workspaceID}`, + workspaceID, }) const adapter = getAdapter(space.projectID, space.type) const target = yield* EffectBridge.fromPromise(() => adapter.target(space)) - yield* sync.run(Session.Event.Updated, { - sessionID: input.sessionID, - info: { + if (target.type === "local") { + yield* sync.run(Session.Event.Updated, { + sessionID: input.sessionID, + info: { + workspaceID: input.workspaceID, + }, + }) + + log.info("session warp complete", { workspaceID: input.workspaceID, - }, - }) + sessionID: input.sessionID, + target: target.directory, + }) + return + } const rows = yield* db((db) => db @@ -562,130 +621,95 @@ export const layer = Layer.effect( sessionID: input.sessionID, }) - const size = 10 - // TODO: look into using effect APIs to process this in chunks - const sets = Array.from({ length: Math.ceil(rows.length / size) }, (_, i) => - rows.slice(i * size, (i + 1) * size), - ) - const total = sets.length + const batches = Iterable.chunksOf(rows, 10) + const total = Iterable.size(batches) - log.info("session restore prepared", { + log.info("session warp prepared", { workspaceID: input.workspaceID, sessionID: input.sessionID, - workspaceType: space.type, - directory: space.directory, - target: target.type === "remote" ? String(route(target.url, "/sync/replay")) : target.directory, + target: String(route(target.url, "/sync/replay")), events: rows.length, batches: total, first: rows[0]?.seq, last: rows.at(-1)?.seq, }) - yield* Effect.sync(() => - GlobalBus.emit("event", { - directory: "global", - workspace: input.workspaceID, - payload: { - type: Event.Restore.type, - properties: { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - total, - step: 0, - }, - }, - }), - ) - - for (const [i, events] of sets.entries()) { - log.info("session restore batch starting", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - step: i + 1, - total, - events: events.length, - first: events[0]?.seq, - last: events.at(-1)?.seq, - target: target.type === "remote" ? String(route(target.url, "/sync/replay")) : target.directory, - }) - - if (target.type === "local") { - yield* sync.replayAll(events) - log.info("session restore batch replayed locally", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - step: i + 1, - total, - events: events.length, - }) - } else { - const url = route(target.url, "/sync/replay") - const res = yield* http.execute( - HttpClientRequest.post(url, { - headers: new Headers(target.headers), - body: HttpBody.jsonUnsafe({ - directory: space.directory ?? "", - events, + yield* Effect.forEach( + batches, + (events, i) => + Effect.gen(function* () { + const response = yield* http.execute( + HttpClientRequest.post(route(target.url, "/sync/replay"), { + headers: new Headers(target.headers), + body: HttpBody.jsonUnsafe({ + directory: space.directory ?? "", + events, + }), }), - }), - ) + ) - if (res.status < 200 || res.status >= 300) { - const body = yield* res.text - log.error("session restore batch failed", { + if (response.status < 200 || response.status >= 300) { + const body = yield* response.text + log.error("session warp batch failed", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + status: response.status, + body, + }) + return yield* new SessionWarpHttpError({ + message: `Failed to warp session ${input.sessionID} into workspace ${workspaceID}: HTTP ${response.status} ${body}`, + workspaceID, + sessionID: input.sessionID, + status: response.status, + body, + }) + } + + log.info("session warp batch posted", { workspaceID: input.workspaceID, sessionID: input.sessionID, step: i + 1, total, - status: res.status, - body, + status: response.status, }) - return yield* new SessionRestoreHttpError({ - message: `Failed to replay session ${input.sessionID} into workspace ${input.workspaceID}: HTTP ${res.status} ${body}`, - workspaceID: input.workspaceID, - sessionID: input.sessionID, - status: res.status, - body, - }) - } - - log.info("session restore batch posted", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - step: i + 1, - total, - status: res.status, - }) - } - - yield* Effect.sync(() => - GlobalBus.emit("event", { - directory: "global", - workspace: input.workspaceID, - payload: { - type: Event.Restore.type, - properties: { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - total, - step: i + 1, - }, - }, }), - ) + { discard: true }, + ) + + const response = yield* http.execute( + HttpClientRequest.post(route(target.url, "/sync/steal"), { + headers: new Headers(target.headers), + body: HttpBody.jsonUnsafe({ sessionID: input.sessionID }), + }), + ) + if (response.status < 200 || response.status >= 300) { + const body = yield* response.text + log.error("session warp steal failed", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + status: response.status, + body, + }) + return yield* new SessionWarpHttpError({ + message: `Failed to steal session ${input.sessionID} into workspace ${workspaceID}: HTTP ${response.status} ${body}`, + workspaceID, + sessionID: input.sessionID, + status: response.status, + body, + }) } - log.info("session restore complete", { + log.info("session warp complete", { workspaceID: input.workspaceID, sessionID: input.sessionID, batches: total, }) - - return { total } }).pipe( Effect.tapError((err) => Effect.sync(() => - log.error("session restore failed", { + log.error("session warp failed", { workspaceID: input.workspaceID, sessionID: input.sessionID, error: errorData(err), @@ -814,7 +838,7 @@ export const layer = Layer.effect( return Service.of({ create, - sessionRestore, + sessionWarp, list, get, remove, @@ -830,6 +854,7 @@ export const defaultLayer = layer.pipe( Layer.provide(Auth.defaultLayer), Layer.provide(Session.defaultLayer), Layer.provide(SyncEvent.defaultLayer), + Layer.provide(SessionPrompt.defaultLayer), Layer.provide(FetchHttpClient.layer), ) diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index 21a7810ce..788aef317 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -10,10 +10,6 @@ import { zodObject } from "@/util/effect-zod" import { Instance } from "@/project/instance" import { errors } from "../../error" import { lazy } from "@/util/lazy" -import * as Log from "@opencode-ai/core/util/log" -import { errorData } from "@/util/error" - -const log = Log.create({ service: "server.workspace" }) export const WorkspaceRoutes = lazy(() => new Hono() @@ -151,60 +147,36 @@ export const WorkspaceRoutes = lazy(() => }, ) .post( - "/:id/session-restore", + "/warp", describeRoute({ - summary: "Restore session into workspace", - description: "Replay a session's sync events into the target workspace in batches.", - operationId: "experimental.workspace.sessionRestore", + summary: "Warp session into workspace", + description: "Move a session's sync history into the target workspace, or detach it to the local project.", + operationId: "experimental.workspace.warp", responses: { - 200: { - description: "Session replay started", - content: { - "application/json": { - schema: resolver( - z.object({ - total: z.number().int().min(0), - }), - ), - }, - }, + 204: { + description: "Session warped", }, ...errors(400), }, }), - validator("param", z.object({ id: zodObject(Workspace.Info).shape.id })), - validator("json", Workspace.SessionRestoreInput.zodObject.omit({ workspaceID: true })), + validator( + "json", + z.object({ + id: zodObject(Workspace.Info).shape.id.nullable(), + sessionID: Workspace.SessionWarpInput.zodObject.shape.sessionID, + }), + ), async (c) => { - const { id } = c.req.valid("param") - const body = c.req.valid("json") as Omit - log.info("session restore route requested", { - workspaceID: id, - sessionID: body.sessionID, - directory: Instance.directory, - }) - try { - const result = await AppRuntime.runPromise( - Workspace.Service.use((svc) => - svc.sessionRestore({ - workspaceID: id, - ...body, - }), - ), - ) - log.info("session restore route complete", { - workspaceID: id, - sessionID: body.sessionID, - total: result.total, - }) - return c.json(result) - } catch (err) { - log.error("session restore route failed", { - workspaceID: id, - sessionID: body.sessionID, - error: errorData(err), - }) - throw err - } + const body = c.req.valid("json") + await AppRuntime.runPromise( + Workspace.Service.use((workspace) => + workspace.sessionWarp({ + workspaceID: body.id, + sessionID: body.sessionID, + }), + ), + ) + return c.body(null, 204) }, ), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/sync.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/sync.ts index 58d30b4c7..442e65655 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/sync.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/sync.ts @@ -1,4 +1,5 @@ import { NonNegativeInt } from "@/util/schema" +import { SessionID } from "@/session/schema" import { Schema } from "effect" import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "../middleware/authorization" @@ -21,6 +22,9 @@ export const ReplayPayload = Schema.Struct({ export const ReplayResponse = Schema.Struct({ sessionID: Schema.String, }) +export const SessionPayload = Schema.Struct({ + sessionID: SessionID, +}) export const HistoryPayload = Schema.Record(Schema.String, NonNegativeInt) export const HistoryEvent = Schema.Struct({ id: Schema.String, @@ -33,6 +37,7 @@ export const HistoryEvent = Schema.Struct({ export const SyncPaths = { start: `${root}/start`, replay: `${root}/replay`, + steal: `${root}/steal`, history: `${root}/history`, } as const @@ -60,6 +65,17 @@ export const SyncApi = HttpApi.make("sync") description: "Validate and replay a complete sync event history.", }), ), + HttpApiEndpoint.post("steal", SyncPaths.steal, { + payload: SessionPayload, + success: described(SessionPayload, "Session stolen into workspace"), + error: HttpApiError.BadRequest, + }).annotateMerge( + OpenApi.annotations({ + identifier: "sync.steal", + summary: "Steal session into workspace", + description: "Update a session to belong to the current workspace through the sync event system.", + }), + ), HttpApiEndpoint.post("history", SyncPaths.history, { payload: HistoryPayload, success: described(Schema.Array(HistoryEvent), "Sync events"), diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/workspace.ts index 08e9e044b..f197ab976 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/workspace.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/workspace.ts @@ -1,21 +1,17 @@ import { Workspace } from "@/control-plane/workspace" import { WorkspaceAdapterEntry } from "@/control-plane/types" -import { NonNegativeInt } from "@/util/schema" import { Schema, Struct } from "effect" -import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "../middleware/authorization" import { InstanceContextMiddleware } from "../middleware/instance-context" import { WorkspaceRoutingMiddleware } from "../middleware/workspace-routing" import { described } from "./metadata" const root = "/experimental/workspace" -export const CreatePayload = Schema.Struct({ - ...Struct.omit(Workspace.CreateInput.fields, ["projectID", "extra"]), - extra: Schema.optional(Workspace.CreateInput.fields.extra), -}) -export const SessionRestorePayload = Schema.Struct(Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"])) -export const SessionRestoreResponse = Schema.Struct({ - total: NonNegativeInt, +export const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"])) +export const WarpPayload = Schema.Struct({ + id: Schema.NullOr(Workspace.Info.fields.id), + sessionID: Workspace.SessionWarpInput.fields.sessionID, }) export const WorkspacePaths = { @@ -23,7 +19,7 @@ export const WorkspacePaths = { list: root, status: `${root}/status`, remove: `${root}/:id`, - sessionRestore: `${root}/:id/session-restore`, + warp: `${root}/warp`, } as const export const WorkspaceApi = HttpApi.make("workspace") @@ -79,16 +75,15 @@ export const WorkspaceApi = HttpApi.make("workspace") description: "Remove an existing workspace.", }), ), - HttpApiEndpoint.post("sessionRestore", WorkspacePaths.sessionRestore, { - params: { id: Workspace.Info.fields.id }, - payload: SessionRestorePayload, - success: described(SessionRestoreResponse, "Session replay started"), + HttpApiEndpoint.post("warp", WorkspacePaths.warp, { + payload: WarpPayload, + success: described(HttpApiSchema.NoContent, "Session warped"), error: HttpApiError.BadRequest, }).annotateMerge( OpenApi.annotations({ - identifier: "experimental.workspace.sessionRestore", - summary: "Restore session into workspace", - description: "Replay a session's sync events into the target workspace in batches.", + identifier: "experimental.workspace.warp", + summary: "Warp session into workspace", + description: "Move a session's sync history into the target workspace, or detach it to the local project.", }), ), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts index f4a2f315c..152d22f98 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts @@ -1,5 +1,6 @@ import { Workspace } from "@/control-plane/workspace" import * as InstanceState from "@/effect/instance-state" +import { Session } from "@/session/session" import { Database } from "@/storage/db" import { SyncEvent } from "@/sync" import { EventTable } from "@/sync/event.sql" @@ -12,7 +13,7 @@ import { or } from "drizzle-orm" import { Effect, Scope } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" import { InstanceHttpApi } from "../api" -import { HistoryPayload, ReplayPayload } from "../groups/sync" +import { HistoryPayload, ReplayPayload, SessionPayload } from "../groups/sync" import * as Log from "@opencode-ai/core/util/log" const log = Log.create({ service: "server.sync" }) @@ -56,6 +57,25 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl return { sessionID: source } }) + const steal = Effect.fn("SyncHttpApi.steal")(function* (ctx: { payload: typeof SessionPayload.Type }) { + const workspaceID = yield* InstanceState.workspaceID + if (!workspaceID) throw new Error("Cannot steal session without workspace context") + + yield* sync.run(Session.Event.Updated, { + sessionID: ctx.payload.sessionID, + info: { + workspaceID, + }, + }) + + log.info("sync session stolen", { + sessionID: ctx.payload.sessionID, + workspaceID, + }) + + return { sessionID: ctx.payload.sessionID } + }) + const history = Effect.fn("SyncHttpApi.history")(function* (ctx: { payload: typeof HistoryPayload.Type }) { const exclude = Object.entries(ctx.payload) return Database.use((db) => @@ -72,6 +92,6 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl ) }) - return handlers.handle("start", start).handle("replay", replay).handle("history", history) + return handlers.handle("start", start).handle("replay", replay).handle("steal", steal).handle("history", history) }), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/workspace.ts index 570f355e5..b415943a6 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/workspace.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/workspace.ts @@ -4,7 +4,7 @@ import * as InstanceState from "@/effect/instance-state" import { Effect } from "effect" import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi" import { InstanceHttpApi } from "../api" -import { CreatePayload, SessionRestorePayload } from "../groups/workspace" +import { CreatePayload, WarpPayload } from "../groups/workspace" export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspace", (handlers) => Effect.gen(function* () { @@ -39,13 +39,10 @@ export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspac return yield* workspace.remove(ctx.params.id) }) - const sessionRestore = Effect.fn("WorkspaceHttpApi.sessionRestore")(function* (ctx: { - params: { id: Workspace.Info["id"] } - payload: typeof SessionRestorePayload.Type - }) { - return yield* workspace - .sessionRestore({ - workspaceID: ctx.params.id, + const warp = Effect.fn("WorkspaceHttpApi.warp")(function* (ctx: { payload: typeof WarpPayload.Type }) { + yield* workspace + .sessionWarp({ + workspaceID: ctx.payload.id, sessionID: ctx.payload.sessionID, }) .pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) @@ -57,6 +54,6 @@ export const workspaceHandlers = HttpApiBuilder.group(InstanceHttpApi, "workspac .handle("create", create) .handle("status", status) .handle("remove", remove) - .handle("sessionRestore", sessionRestore) + .handle("warp", warp) }), ) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 89b5641e5..71662dea9 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -155,7 +155,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket, opts?: CorsOptions): H app.get(WorkspacePaths.list, (c) => handler(c.req.raw, context)) app.get(WorkspacePaths.status, (c) => handler(c.req.raw, context)) app.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context)) - app.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context)) + app.post(WorkspacePaths.warp, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/src/server/routes/instance/sync.ts b/packages/opencode/src/server/routes/instance/sync.ts index b7bf413d4..9894d8c8e 100644 --- a/packages/opencode/src/server/routes/instance/sync.ts +++ b/packages/opencode/src/server/routes/instance/sync.ts @@ -16,6 +16,9 @@ import { Workspace } from "@/control-plane/workspace" import { AppRuntime } from "@/effect/app-runtime" import { Instance } from "@/project/instance" import { errors } from "../../error" +import { Session } from "@/session/session" +import { WorkspaceContext } from "@/control-plane/workspace-context" +import { SessionID } from "@/session/schema" const ReplayEvent = z.object({ id: z.string(), @@ -24,6 +27,9 @@ const ReplayEvent = z.object({ type: z.string(), data: z.record(z.string(), z.unknown()), }) +const SessionPayload = z.object({ + sessionID: SessionID.zod, +}) const log = Log.create({ service: "server.sync" }) @@ -108,6 +114,47 @@ export const SyncRoutes = lazy(() => }) }, ) + .post( + "/steal", + describeRoute({ + summary: "Steal session into workspace", + description: "Update a session to belong to the current workspace through the sync event system.", + operationId: "sync.steal", + responses: { + 200: { + description: "Session stolen into workspace", + content: { + "application/json": { + schema: resolver(SessionPayload), + }, + }, + }, + ...errors(400), + }, + }), + validator("json", SessionPayload), + async (c) => { + const body = c.req.valid("json") + const workspaceID = WorkspaceContext.workspaceID + if (!workspaceID) throw new Error("Cannot steal session without workspace context") + + SyncEvent.run(Session.Event.Updated, { + sessionID: body.sessionID, + info: { + workspaceID, + }, + }) + + log.info("sync session stolen", { + sessionID: body.sessionID, + workspaceID, + }) + + return c.json({ + sessionID: body.sessionID, + }) + }, + ) .post( "/history", describeRoute({ diff --git a/packages/opencode/src/sync/event.sql.ts b/packages/opencode/src/sync/event.sql.ts index b51b5a5df..547a80f0f 100644 --- a/packages/opencode/src/sync/event.sql.ts +++ b/packages/opencode/src/sync/event.sql.ts @@ -3,6 +3,7 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core" export const EventSequenceTable = sqliteTable("event_sequence", { aggregate_id: text().notNull().primaryKey(), seq: integer().notNull(), + owner_id: text(), }) export const EventTable = sqliteTable("event", { diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 2654767e9..62b30ccf9 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -59,8 +59,11 @@ export interface Interface { data: Event["data"], options?: { publish?: boolean }, ) => Effect.Effect - readonly replay: (event: SerializedEvent, options?: { publish: boolean }) => Effect.Effect - readonly replayAll: (events: SerializedEvent[], options?: { publish: boolean }) => Effect.Effect + readonly replay: (event: SerializedEvent, options?: { publish: boolean; ownerID?: string }) => Effect.Effect + readonly replayAll: ( + events: SerializedEvent[], + options?: { publish: boolean; ownerID?: string }, + ) => Effect.Effect readonly remove: (aggregateID: string) => Effect.Effect } @@ -76,7 +79,7 @@ export const layer = Layer.effect(Service)( const row = Database.use((db) => db - .select({ seq: EventSequenceTable.seq }) + .select({ seq: EventSequenceTable.seq, ownerID: EventSequenceTable.owner_id }) .from(EventSequenceTable) .where(eq(EventSequenceTable.aggregate_id, event.aggregateID)) .get(), @@ -85,6 +88,10 @@ export const layer = Layer.effect(Service)( const latest = row?.seq ?? -1 if (event.seq <= latest) return + if (row?.ownerID && row.ownerID !== options?.ownerID) { + return + } + const expected = latest + 1 if (event.seq !== expected) { throw new Error( @@ -99,7 +106,7 @@ export const layer = Layer.effect(Service)( workspace: yield* InstanceState.workspaceID, } : undefined - process(def, event, { publish, context }) + process(def, event, { publish, context, ownerID: options?.ownerID }) }) const replayAll: Interface["replayAll"] = Effect.fn("SyncEvent.replayAll")(function* (events, options) { @@ -263,7 +270,7 @@ export function project( function process( def: Def, event: Event, - options: { publish: boolean; context?: PublishContext }, + options: { publish: boolean; context?: PublishContext; ownerID?: string }, ) { if (projectors == null) { throw new Error("No projectors available. Call `SyncEvent.init` to install projectors") @@ -274,8 +281,6 @@ function process( throw new Error(`Projector not found for event: ${def.type}`) } - // idempotent: need to ignore any events already logged - Database.transaction((tx) => { projector(tx, event.data, event) @@ -284,6 +289,7 @@ function process( .values({ aggregate_id: event.aggregateID, seq: event.seq, + owner_id: options?.ownerID, }) .onConflictDoUpdate({ target: EventSequenceTable.aggregate_id, @@ -332,11 +338,11 @@ function process( }) } -export function replay(event: SerializedEvent, options?: { publish: boolean }) { +export function replay(event: SerializedEvent, options?: { publish: boolean; ownerID?: string }) { return runtime.runSync((sync) => sync.replay(event, options)) } -export function replayAll(events: SerializedEvent[], options?: { publish: boolean }) { +export function replayAll(events: SerializedEvent[], options?: { publish: boolean; ownerID?: string }) { return runtime.runSync((sync) => sync.replayAll(events, options)) } @@ -348,6 +354,16 @@ export function remove(aggregateID: string) { return runtime.runSync((sync) => sync.remove(aggregateID)) } +export function claim(aggregateID: string, ownerID: string) { + Database.use((db) => + db + .update(EventSequenceTable) + .set({ owner_id: ownerID }) + .where(eq(EventSequenceTable.aggregate_id, aggregateID)) + .run(), + ) +} + export function payloads() { return registry .entries() diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts index 10a05e3b1..84f567006 100644 --- a/packages/opencode/test/control-plane/workspace.test.ts +++ b/packages/opencode/test/control-plane/workspace.test.ts @@ -6,7 +6,7 @@ import { setTimeout as delay } from "node:timers/promises" import { NodeHttpServer } from "@effect/platform-node" import { Effect, Layer } from "effect" import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" -import { asc, eq } from "drizzle-orm" +import { eq } from "drizzle-orm" import * as Log from "@opencode-ai/core/util/log" import { Flag } from "@opencode-ai/core/flag/flag" import { GlobalBus, type GlobalEvent } from "@/bus/global" @@ -16,11 +16,10 @@ import { ProjectTable } from "@/project/project.sql" import { Instance } from "@/project/instance" import { WithInstance } from "../../src/project/with-instance" import { Session as SessionNs } from "@/session/session" -import { SessionID, MessageID, PartID } from "@/session/schema" +import { SessionID } from "@/session/schema" import { SessionTable } from "@/session/session.sql" -import { ModelID, ProviderID } from "@/provider/schema" import { SyncEvent } from "@/sync" -import { EventSequenceTable, EventTable } from "@/sync/event.sql" +import { EventSequenceTable } from "@/sync/event.sql" import { resetDatabase } from "../fixture/db" import { disposeAllInstances, provideTmpdirInstance, tmpdir } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -111,8 +110,8 @@ async function withInstance(fn: (dir: string) => T | Promise) { const runWorkspace = (effect: Effect.Effect) => AppRuntime.runPromise(effect) const createWorkspace = (input: WorkspaceOld.CreateInput) => runWorkspace(WorkspaceOld.Service.use((workspace) => workspace.create(input))) -const restoreWorkspaceSession = (input: WorkspaceOld.SessionRestoreInput) => - runWorkspace(WorkspaceOld.Service.use((workspace) => workspace.sessionRestore(input))) +const warpWorkspaceSession = (input: WorkspaceOld.SessionWarpInput) => + runWorkspace(WorkspaceOld.Service.use((workspace) => workspace.sessionWarp(input))) const listWorkspaces = (project: Parameters[0]) => runWorkspace(WorkspaceOld.Service.use((workspace) => workspace.list(project))) const getWorkspace = (id: WorkspaceID) => runWorkspace(WorkspaceOld.Service.use((workspace) => workspace.get(id))) @@ -317,48 +316,24 @@ function sessionSequence(sessionID: SessionID) { )?.seq } -function eventRows(sessionID: SessionID) { +function sessionSequenceOwner(sessionID: SessionID) { return Database.use((db) => db - .select({ seq: EventTable.seq, type: EventTable.type, data: EventTable.data }) - .from(EventTable) - .where(eq(EventTable.aggregate_id, sessionID)) - .orderBy(asc(EventTable.seq)) - .all(), - ) + .select({ ownerID: EventSequenceTable.owner_id }) + .from(EventSequenceTable) + .where(eq(EventSequenceTable.aggregate_id, sessionID)) + .get(), + )?.ownerID } function sessionUpdatedType() { return SyncEvent.versionedType(SessionNs.Event.Updated.type, SessionNs.Event.Updated.version) } -function replaceSessionEvents(sessionID: SessionID, count: number) { - Database.use((db) => { - db.delete(EventSequenceTable).where(eq(EventSequenceTable.aggregate_id, sessionID)).run() - if (count === 0) return - - db.insert(EventSequenceTable) - .values({ aggregate_id: sessionID, seq: count - 1 }) - .run() - db.insert(EventTable) - .values( - Array.from({ length: count }, (_, i) => ({ - id: `evt_${unique(`manual-${i}`)}`, - aggregate_id: sessionID, - seq: i, - type: sessionUpdatedType(), - data: { sessionID, info: { title: `manual ${i}` } }, - })), - ) - .run() - }) -} - describe("workspace-old schemas and exports", () => { test("keeps the historical event type names", () => { expect(WorkspaceOld.Event.Ready.type).toBe("workspace.ready") expect(WorkspaceOld.Event.Failed.type).toBe("workspace.failed") - expect(WorkspaceOld.Event.Restore.type).toBe("workspace.restore") expect(WorkspaceOld.Event.Status.type).toBe("workspace.status") }) @@ -375,17 +350,6 @@ describe("workspace-old schemas and exports", () => { expect(() => WorkspaceOld.CreateInput.zod.parse({ ...input, id: "bad" })).toThrow() expect(() => WorkspaceOld.CreateInput.zod.parse({ ...input, branch: 1 })).toThrow() }) - - test("validates session restore input", () => { - const input = { - workspaceID: WorkspaceID.ascending("wrk_schema_restore"), - sessionID: SessionID.descending("ses_schema_restore"), - } - - expect(WorkspaceOld.SessionRestoreInput.zod.parse(input)).toEqual(input) - expect(() => WorkspaceOld.SessionRestoreInput.zod.parse({ ...input, workspaceID: "bad" })).toThrow() - expect(() => WorkspaceOld.SessionRestoreInput.zod.parse({ ...input, sessionID: "bad" })).toThrow() - }) }) describe("workspace-old CRUD", () => { @@ -651,6 +615,144 @@ describe("workspace-old CRUD", () => { expect(await getWorkspace(info.id)).toBeUndefined() }) }) + + test("sessionWarp moves a session into a local workspace and claims ownership", async () => { + await withInstance(async (dir) => { + const previousType = unique("warp-prev-local") + const targetType = unique("warp-target-local") + const previous = workspaceInfo(Instance.project.id, previousType) + const target = workspaceInfo(Instance.project.id, targetType) + insertWorkspace(previous) + insertWorkspace(target) + registerAdapter(Instance.project.id, previousType, localAdapter(path.join(dir, "warp-prev-local")).adapter) + registerAdapter(Instance.project.id, targetType, localAdapter(path.join(dir, "warp-target-local")).adapter) + const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({}))) + attachSessionToWorkspace(session.id, previous.id) + + await warpWorkspaceSession({ workspaceID: target.id, sessionID: session.id }) + + expect( + Database.use((db) => + db + .select({ workspaceID: SessionTable.workspace_id }) + .from(SessionTable) + .where(eq(SessionTable.id, session.id)) + .get(), + )?.workspaceID, + ).toBe(target.id) + expect(sessionSequenceOwner(session.id)).toBe(target.id) + }) + }) + + test("sessionWarp detaches a session to the local project and claims project ownership", async () => { + await withInstance(async (dir) => { + const previousType = unique("warp-detach-local") + const previous = workspaceInfo(Instance.project.id, previousType) + insertWorkspace(previous) + registerAdapter(Instance.project.id, previousType, localAdapter(path.join(dir, "warp-detach-local")).adapter) + const session = await AppRuntime.runPromise(SessionNs.Service.use((svc) => svc.create({}))) + attachSessionToWorkspace(session.id, previous.id) + + await warpWorkspaceSession({ workspaceID: null, sessionID: session.id }) + + expect( + Database.use((db) => + db + .select({ workspaceID: SessionTable.workspace_id }) + .from(SessionTable) + .where(eq(SessionTable.id, session.id)) + .get(), + )?.workspaceID, + ).toBeNull() + expect(sessionSequenceOwner(session.id)).toBe(Instance.project.id) + }) + }) + + it.live("sessionWarp syncs previous remote history, replays it, steals, and claims the sequence", () => { + const calls: FetchCall[] = [] + let historySessionID: SessionID | undefined + let historyNextSeq = 0 + return Effect.gen(function* () { + yield* HttpServer.serveEffect()( + Effect.gen(function* () { + const req = yield* HttpServerRequest.HttpServerRequest + const bodyText = yield* req.text + const call = { + url: new URL(req.url, "http://localhost"), + method: req.method, + headers: new Headers(req.headers), + bodyText, + json: bodyText ? JSON.parse(bodyText) : undefined, + } + calls.push(call) + if (call.url.pathname === "/warp-source/sync/history") { + return yield* HttpServerResponse.json([ + { + id: `evt_${unique("warp-source-history")}`, + aggregate_id: historySessionID!, + seq: historyNextSeq, + type: sessionUpdatedType(), + data: { sessionID: historySessionID!, info: { title: "from source history" } }, + }, + ]) + } + if (call.url.pathname === "/warp-target/sync/replay") + return yield* HttpServerResponse.json({ sessionID: "ok" }) + if (call.url.pathname === "/warp-target/sync/steal") + return yield* HttpServerResponse.json({ sessionID: "ok" }) + return HttpServerResponse.text("unexpected", { status: 500 }) + }), + ) + const url = yield* serverUrl() + yield* provideTmpdirInstance( + () => + Effect.gen(function* () { + const workspace = yield* WorkspaceOld.Service + const sessionSvc = yield* SessionNs.Service + const previousType = unique("warp-remote-source") + const targetType = unique("warp-remote-target") + const previous = workspaceInfo(Instance.project.id, previousType) + const target = workspaceInfo(Instance.project.id, targetType, { directory: "remote-target-dir" }) + insertWorkspace(previous) + insertWorkspace(target) + registerAdapter(Instance.project.id, previousType, remoteAdapter(`${url}/warp-source`).adapter) + registerAdapter(Instance.project.id, targetType, remoteAdapter(`${url}/warp-target`).adapter) + const session = yield* sessionSvc.create({}) + attachSessionToWorkspace(session.id, previous.id) + historySessionID = session.id + historyNextSeq = (sessionSequence(session.id) ?? -1) + 1 + + yield* workspace.sessionWarp({ workspaceID: target.id, sessionID: session.id }) + + expect(calls.map((call) => `${call.method} ${call.url.pathname}`)).toEqual([ + "POST /warp-source/sync/history", + "POST /warp-target/sync/replay", + "POST /warp-target/sync/steal", + ]) + expect(calls[0].json).toEqual({ [session.id]: historyNextSeq - 1 }) + expect(calls[1].json).toMatchObject({ + directory: "remote-target-dir", + events: [ + { + aggregateID: session.id, + seq: 0, + type: SyncEvent.versionedType(SessionNs.Event.Created.type, SessionNs.Event.Created.version), + }, + { + aggregateID: session.id, + seq: historyNextSeq, + type: sessionUpdatedType(), + }, + ], + }) + expect(calls[2].json).toEqual({ sessionID: session.id }) + expect((yield* sessionSvc.get(session.id)).title).toBe("from source history") + expect(sessionSequenceOwner(session.id)).toBe(target.id) + }), + { git: true }, + ) + }) + }) }) describe("workspace-old sync state", () => { @@ -1215,313 +1317,3 @@ describe("workspace-old waitForSync", () => { }) }, 7000) }) - -describe("workspace-old sessionRestore", () => { - test("throws when the workspace is missing", async () => { - await withInstance(async () => { - await expect( - restoreWorkspaceSession({ - workspaceID: WorkspaceID.ascending("wrk_restore_missing"), - sessionID: SessionID.descending("ses_restore_missing_workspace"), - }), - ).rejects.toThrow("Workspace not found: wrk_restore_missing") - }) - }) - - test("throws when switching a missing session fails", async () => { - await withInstance(async (dir) => { - const type = unique("restore-missing-session") - const info = workspaceInfo(Instance.project.id, type, { directory: dir }) - insertWorkspace(info) - registerAdapter(Instance.project.id, type, localAdapter(dir).adapter) - - await expect( - restoreWorkspaceSession({ workspaceID: info.id, sessionID: SessionID.descending("ses_missing_restore") }), - ).rejects.toThrow("NotFoundError") - await removeWorkspace(info.id) - }) - }) - - it.live("posts remote replay batches of 10, emits progress, and includes the workspace update event", () => { - const replay: FetchCall[] = [] - return Effect.gen(function* () { - yield* HttpServer.serveEffect()( - Effect.gen(function* () { - const req = yield* HttpServerRequest.HttpServerRequest - const bodyText = yield* req.text - const call = { - url: new URL(req.url, "http://localhost"), - method: req.method, - headers: new Headers(req.headers), - bodyText, - json: bodyText ? JSON.parse(bodyText) : undefined, - } - if (call.url.pathname === "/restore/sync/replay") { - replay.push(call) - return HttpServerResponse.fromWeb(Response.json({ ok: true })) - } - return HttpServerResponse.text("unexpected", { status: 500 }) - }), - ) - const url = yield* serverUrl() - yield* provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const workspace = yield* WorkspaceOld.Service - const sessionSvc = yield* SessionNs.Service - const captured = captureGlobalEvents() - try { - const type = unique("restore-remote") - const info = workspaceInfo(Instance.project.id, type, { directory: dir }) - insertWorkspace(info) - registerAdapter( - Instance.project.id, - type, - remoteAdapter(`${url}/restore/?ignored=1#hash`, { - directory: dir, - headers: { authorization: "Bearer restore" }, - }).adapter, - ) - const session = yield* sessionSvc.create({ title: "restore remote" }) - replaceSessionEvents(session.id, 24) - - const result = yield* workspace.sessionRestore({ workspaceID: info.id, sessionID: session.id }) - - expect(result).toEqual({ total: 3 }) - expect(replay).toHaveLength(3) - expect(replay.map((call) => call.url.pathname + call.url.search + call.url.hash)).toEqual([ - "/restore/sync/replay", - "/restore/sync/replay", - "/restore/sync/replay", - ]) - expect(replay.every((call) => call.headers.get("authorization") === "Bearer restore")).toBe(true) - expect(replay.every((call) => call.headers.get("content-type") === "application/json")).toBe(true) - expect(replay.map((call) => (call.json as { events: unknown[] }).events.length)).toEqual([10, 10, 5]) - expect(replay.map((call) => (call.json as { directory: string }).directory)).toEqual([dir, dir, dir]) - expect( - replay.flatMap((call) => - (call.json as { events: Array<{ seq: number }> }).events.map((event) => event.seq), - ), - ).toEqual(Array.from({ length: 25 }, (_, i) => i)) - expect( - (replay[2].json as { events: Array<{ seq: number; type: string; data: unknown }> }).events.at(-1), - ).toMatchObject({ - seq: 24, - type: sessionUpdatedType(), - data: { sessionID: session.id, info: { workspaceID: info.id } }, - }) - expect((yield* sessionSvc.get(session.id)).workspaceID).toBe(info.id) - expect( - captured.events - .filter( - (event) => event.workspace === info.id && event.payload.type === WorkspaceOld.Event.Restore.type, - ) - .map((event) => event.payload.properties.step), - ).toEqual([0, 1, 2, 3]) - yield* workspace.remove(info.id) - } finally { - captured.dispose() - } - }), - { git: true }, - ) - }) - }) - - it.live("remote restore sends an empty directory string when the workspace directory is null", () => { - const replay: FetchCall[] = [] - return Effect.gen(function* () { - yield* HttpServer.serveEffect()( - Effect.gen(function* () { - const req = yield* HttpServerRequest.HttpServerRequest - const bodyText = yield* req.text - replay.push({ - url: new URL(req.url, "http://localhost"), - method: req.method, - headers: new Headers(req.headers), - bodyText, - json: bodyText ? JSON.parse(bodyText) : undefined, - }) - return HttpServerResponse.fromWeb(Response.json({ ok: true })) - }), - ) - const url = yield* serverUrl() - yield* provideTmpdirInstance( - () => - Effect.gen(function* () { - const workspace = yield* WorkspaceOld.Service - const sessionSvc = yield* SessionNs.Service - const type = unique("restore-null-dir") - const info = workspaceInfo(Instance.project.id, type, { directory: null }) - insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/null-dir`, { directory: null }).adapter) - const session = yield* sessionSvc.create({ title: "null dir" }) - replaceSessionEvents(session.id, 0) - - expect(yield* workspace.sessionRestore({ workspaceID: info.id, sessionID: session.id })).toEqual({ - total: 1, - }) - expect((replay[0].json as { directory: string }).directory).toBe("") - expect((replay[0].json as { events: unknown[] }).events).toHaveLength(1) - yield* workspace.remove(info.id) - }), - { git: true }, - ) - }) - }) - - it.live("remote restore failures include status and body and do not emit completed batch progress", () => { - const replay: FetchCall[] = [] - return Effect.gen(function* () { - yield* HttpServer.serveEffect()( - Effect.gen(function* () { - const req = yield* HttpServerRequest.HttpServerRequest - const bodyText = yield* req.text - replay.push({ - url: new URL(req.url, "http://localhost"), - method: req.method, - headers: new Headers(req.headers), - bodyText, - json: bodyText ? JSON.parse(bodyText) : undefined, - }) - return HttpServerResponse.text("replay failed", { status: 503 }) - }), - ) - const url = yield* serverUrl() - yield* provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const workspace = yield* WorkspaceOld.Service - const sessionSvc = yield* SessionNs.Service - const captured = captureGlobalEvents() - try { - const type = unique("restore-remote-fail") - const info = workspaceInfo(Instance.project.id, type, { directory: dir }) - insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/fail`, { directory: dir }).adapter) - const session = yield* sessionSvc.create({ title: "restore fail" }) - replaceSessionEvents(session.id, 11) - - const error = yield* Effect.flip( - workspace.sessionRestore({ workspaceID: info.id, sessionID: session.id }), - ) - expect((error as Error).message).toContain( - `Failed to replay session ${session.id} into workspace ${info.id}: HTTP 503 replay failed`, - ) - - expect(replay).toHaveLength(1) - expect( - captured.events - .filter( - (event) => event.workspace === info.id && event.payload.type === WorkspaceOld.Event.Restore.type, - ) - .map((event) => event.payload.properties.step), - ).toEqual([0]) - yield* workspace.remove(info.id) - } finally { - captured.dispose() - } - }), - { git: true }, - ) - }) - }) - - it.live("local restore replays batches and emits progress", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const workspace = yield* WorkspaceOld.Service - const sessionSvc = yield* SessionNs.Service - const captured = captureGlobalEvents() - try { - const type = unique("restore-local") - const info = workspaceInfo(Instance.project.id, type, { directory: dir }) - insertWorkspace(info) - registerAdapter(Instance.project.id, type, localAdapter(dir).adapter) - const session = yield* sessionSvc.create({ title: "restore local" }) - replaceSessionEvents(session.id, 20) - - expect(yield* workspace.sessionRestore({ workspaceID: info.id, sessionID: session.id })).toEqual({ - total: 3, - }) - expect((yield* sessionSvc.get(session.id)).workspaceID).toBe(info.id) - expect(eventRows(session.id).map((row) => row.seq)).toEqual(Array.from({ length: 21 }, (_, i) => i)) - expect( - captured.events - .filter( - (event) => event.workspace === info.id && event.payload.type === WorkspaceOld.Event.Restore.type, - ) - .map((event) => event.payload.properties.step), - ).toEqual([0, 1, 2, 3]) - yield* workspace.remove(info.id) - } finally { - captured.dispose() - } - }), - { git: true }, - ), - ) - - it.live("session restore includes real message and part events in sequence order", () => { - const replay: FetchCall[] = [] - return Effect.gen(function* () { - yield* HttpServer.serveEffect()( - Effect.gen(function* () { - const req = yield* HttpServerRequest.HttpServerRequest - const bodyText = yield* req.text - replay.push({ - url: new URL(req.url, "http://localhost"), - method: req.method, - headers: new Headers(req.headers), - bodyText, - json: bodyText ? JSON.parse(bodyText) : undefined, - }) - return HttpServerResponse.fromWeb(Response.json({ ok: true })) - }), - ) - const url = yield* serverUrl() - yield* provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const workspace = yield* WorkspaceOld.Service - const sessionSvc = yield* SessionNs.Service - const type = unique("restore-real-events") - const info = workspaceInfo(Instance.project.id, type, { directory: dir }) - insertWorkspace(info) - registerAdapter(Instance.project.id, type, remoteAdapter(`${url}/real`, { directory: dir }).adapter) - const session = yield* sessionSvc.create({ title: "real events" }) - for (let i = 0; i < 3; i++) { - const msg = yield* sessionSvc.updateMessage({ - id: MessageID.ascending(), - role: "user", - sessionID: session.id, - agent: "build", - model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, - time: { created: Date.now() }, - }) - yield* sessionSvc.updatePart({ - id: PartID.ascending(), - sessionID: session.id, - messageID: msg.id, - type: "text", - text: `message ${i}`, - }) - } - const before = eventRows(session.id) - - expect(yield* workspace.sessionRestore({ workspaceID: info.id, sessionID: session.id })).toEqual({ - total: 1, - }) - - const posted = (replay[0].json as { events: Array<{ seq: number; type: string }> }).events - expect(posted.map((event) => event.seq)).toEqual([...before.map((row) => row.seq), before.at(-1)!.seq + 1]) - expect(posted.map((event) => event.type).slice(0, -1)).toEqual(before.map((row) => row.type)) - expect(posted.at(-1)?.type).toBe(sessionUpdatedType()) - yield* workspace.remove(info.id) - }), - { git: true }, - ) - }) - }) -}) diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts index 193c2971a..21bf4120c 100644 --- a/packages/opencode/test/server/httpapi-workspace.test.ts +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -168,22 +168,19 @@ describe("workspace HttpApi", () => { const created = yield* request(WorkspacePaths.list, dir, { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ type: "local-test", branch: null, extra: null }), + body: JSON.stringify({ type: "local-test", branch: null }), }) expect(created.status).toBe(200) const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info expect(workspace).toMatchObject({ type: "local-test", name: "local-test" }) const session = yield* Session.Service.use((svc) => svc.create({})).pipe(provideInstance(dir)) - const restored = yield* request(WorkspacePaths.sessionRestore.replace(":id", workspace.id), dir, { + const warped = yield* request(WorkspacePaths.warp, dir, { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ sessionID: session.id }), - }) - expect(restored.status).toBe(200) - expect((yield* Effect.promise(() => restored.json())) as { total: number }).toMatchObject({ - total: expect.any(Number), + body: JSON.stringify({ id: workspace.id, sessionID: session.id }), }) + expect(warped.status).toBe(204) const removed = yield* request(WorkspacePaths.remove.replace(":id", workspace.id), dir, { method: "DELETE" }) expect(removed.status).toBe(200) @@ -212,7 +209,6 @@ describe("workspace HttpApi", () => { expect((yield* Effect.promise(() => created.json())) as Workspace.Info).toMatchObject({ type: "local-test", name: "local-test", - extra: null, }) }), ) @@ -257,7 +253,6 @@ describe("workspace HttpApi", () => { expect((yield* Effect.promise(() => created.json())) as Workspace.Info).toMatchObject({ type: "local-test", name: "local-test", - extra: null, }) }), ) @@ -272,7 +267,7 @@ describe("workspace HttpApi", () => { const created = yield* request(WorkspacePaths.list, dir, { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ type: "local-target", branch: null, extra: null }), + body: JSON.stringify({ type: "local-target", branch: null }), }) const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info @@ -327,7 +322,7 @@ describe("workspace HttpApi", () => { const created = yield* request(WorkspacePaths.list, dir, { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ type: "remote-target", branch: null, extra: null }), + body: JSON.stringify({ type: "remote-target", branch: null }), }) const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info @@ -394,7 +389,7 @@ describe("workspace HttpApi", () => { const created = yield* request(WorkspacePaths.list, dir, { method: "POST", headers: { "content-type": "application/json" }, - body: JSON.stringify({ type: "remote-session-target", branch: null, extra: null }), + body: JSON.stringify({ type: "remote-session-target", branch: null }), }) const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info const session = yield* Session.Service.use((svc) => svc.create()).pipe( diff --git a/packages/opencode/test/sync/index.test.ts b/packages/opencode/test/sync/index.test.ts index 234c5246e..0986b3904 100644 --- a/packages/opencode/test/sync/index.test.ts +++ b/packages/opencode/test/sync/index.test.ts @@ -5,7 +5,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Bus } from "../../src/bus" import { SyncEvent } from "../../src/sync" import { Database } from "@/storage/db" -import { EventTable } from "../../src/sync/event.sql" +import { EventSequenceTable, EventTable } from "../../src/sync/event.sql" import { MessageID } from "../../src/session/schema" import { Flag } from "@opencode-ai/core/flag/flag" import { initProjectors } from "../../src/server/projectors" @@ -252,5 +252,76 @@ describe("SyncEvent", () => { }), ), ) + + it.live( + "claims unowned event sequence on replay with ownerID", + provideTmpdirInstance(() => + Effect.gen(function* () { + const { Created } = setup() + const id = MessageID.ascending() + + yield* SyncEvent.use.replay( + { + id: "evt_1", + type: SyncEvent.versionedType(Created.type, Created.version), + seq: 0, + aggregateID: id, + data: { id, name: "owned" }, + }, + { publish: false, ownerID: "owner-1" }, + ) + + const row = Database.use((db) => + db + .select({ seq: EventSequenceTable.seq, ownerID: EventSequenceTable.owner_id }) + .from(EventSequenceTable) + .get(), + ) + expect(row).toEqual({ seq: 0, ownerID: "owner-1" }) + }), + ), + ) + + it.live( + "ignores replay from a different owner after sequence is claimed", + provideTmpdirInstance(() => + Effect.gen(function* () { + const { Created } = setup() + const id = MessageID.ascending() + + yield* SyncEvent.use.replay( + { + id: "evt_1", + type: SyncEvent.versionedType(Created.type, Created.version), + seq: 0, + aggregateID: id, + data: { id, name: "first" }, + }, + { publish: false, ownerID: "owner-1" }, + ) + yield* SyncEvent.use.replay( + { + id: "evt_2", + type: SyncEvent.versionedType(Created.type, Created.version), + seq: 1, + aggregateID: id, + data: { id, name: "ignored" }, + }, + { publish: false, ownerID: "owner-2" }, + ) + + const events = Database.use((db) => db.select().from(EventTable).all()) + const sequence = Database.use((db) => + db + .select({ seq: EventSequenceTable.seq, ownerID: EventSequenceTable.owner_id }) + .from(EventSequenceTable) + .get(), + ) + expect(events).toHaveLength(1) + expect(events[0].id).toBe("evt_1") + expect(sequence).toEqual({ seq: 0, ownerID: "owner-1" }) + }), + ), + ) }) }) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index e94132c2b..ab191b056 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -4,58 +4,84 @@ import { client } from "./client.gen.js" import { buildClientParams, type Client, type Options as Options2, type TDataShape } from "./client/index.js" import type { AgentPartInput, + AppAgentsErrors, AppAgentsResponses, AppLogErrors, AppLogResponses, + AppSkillsErrors, AppSkillsResponses, Auth as Auth3, AuthRemoveErrors, AuthRemoveResponses, AuthSetErrors, AuthSetResponses, + CommandListErrors, CommandListResponses, Config as Config3, + ConfigGetErrors, ConfigGetResponses, + ConfigProvidersErrors, ConfigProvidersResponses, ConfigUpdateErrors, ConfigUpdateResponses, + EventSubscribeErrors, EventSubscribeResponses, EventTuiCommandExecute2, EventTuiPromptAppend2, EventTuiSessionSelect2, EventTuiToastShow2, + ExperimentalConsoleGetErrors, ExperimentalConsoleGetResponses, + ExperimentalConsoleListOrgsErrors, ExperimentalConsoleListOrgsResponses, ExperimentalConsoleSwitchOrgResponses, + ExperimentalResourceListErrors, ExperimentalResourceListResponses, + ExperimentalSessionListErrors, ExperimentalSessionListResponses, + ExperimentalWorkspaceAdapterListErrors, ExperimentalWorkspaceAdapterListResponses, ExperimentalWorkspaceCreateErrors, ExperimentalWorkspaceCreateResponses, + ExperimentalWorkspaceListErrors, ExperimentalWorkspaceListResponses, ExperimentalWorkspaceRemoveErrors, ExperimentalWorkspaceRemoveResponses, - ExperimentalWorkspaceSessionRestoreErrors, - ExperimentalWorkspaceSessionRestoreResponses, + ExperimentalWorkspaceStatusErrors, ExperimentalWorkspaceStatusResponses, + ExperimentalWorkspaceWarpErrors, + ExperimentalWorkspaceWarpResponses, + FileListErrors, FileListResponses, FilePartInput, FilePartSource, + FileReadErrors, FileReadResponses, + FileStatusErrors, FileStatusResponses, + FindFilesErrors, FindFilesResponses, + FindSymbolsErrors, FindSymbolsResponses, + FindTextErrors, FindTextResponses, + FormatterStatusErrors, FormatterStatusResponses, + GlobalConfigGetErrors, GlobalConfigGetResponses, GlobalConfigUpdateErrors, GlobalConfigUpdateResponses, + GlobalDisposeErrors, GlobalDisposeResponses, + GlobalEventErrors, GlobalEventResponses, + GlobalHealthErrors, GlobalHealthResponses, GlobalUpgradeErrors, GlobalUpgradeResponses, + InstanceDisposeErrors, InstanceDisposeResponses, + LspStatusErrors, LspStatusResponses, McpAddErrors, McpAddResponses, @@ -67,10 +93,13 @@ import type { McpAuthRemoveResponses, McpAuthStartErrors, McpAuthStartResponses, + McpConnectErrors, McpConnectResponses, + McpDisconnectErrors, McpDisconnectResponses, McpLocalConfig, McpRemoteConfig, + McpStatusErrors, McpStatusResponses, OutputFormat, Part as Part2, @@ -78,20 +107,27 @@ import type { PartDeleteResponses, PartUpdateErrors, PartUpdateResponses, + PathGetErrors, PathGetResponses, + PermissionListErrors, PermissionListResponses, PermissionReplyErrors, PermissionReplyResponses, PermissionRespondErrors, PermissionRespondResponses, PermissionRuleset, + ProjectCurrentErrors, ProjectCurrentResponses, + ProjectInitGitErrors, ProjectInitGitResponses, + ProjectListErrors, ProjectListResponses, ProjectUpdateErrors, ProjectUpdateResponses, Prompt, + ProviderAuthErrors, ProviderAuthResponses, + ProviderListErrors, ProviderListResponses, ProviderOauthAuthorizeErrors, ProviderOauthAuthorizeResponses, @@ -105,13 +141,16 @@ import type { PtyCreateResponses, PtyGetErrors, PtyGetResponses, + PtyListErrors, PtyListResponses, PtyRemoveErrors, PtyRemoveResponses, + PtyShellsErrors, PtyShellsResponses, PtyUpdateErrors, PtyUpdateResponses, QuestionAnswer, + QuestionListErrors, QuestionListResponses, QuestionRejectErrors, QuestionRejectResponses, @@ -130,12 +169,15 @@ import type { SessionDeleteMessageResponses, SessionDeleteResponses, SessionDelivery, + SessionDiffErrors, SessionDiffResponses, + SessionForkErrors, SessionForkResponses, SessionGetErrors, SessionGetResponses, SessionInitErrors, SessionInitResponses, + SessionListErrors, SessionListResponses, SessionMessageErrors, SessionMessageResponses, @@ -168,7 +210,10 @@ import type { SyncHistoryListResponses, SyncReplayErrors, SyncReplayResponses, + SyncStartErrors, SyncStartResponses, + SyncStealErrors, + SyncStealResponses, TextPartInput, ToolIdsErrors, ToolIdsResponses, @@ -176,34 +221,50 @@ import type { ToolListResponses, TuiAppendPromptErrors, TuiAppendPromptResponses, + TuiClearPromptErrors, TuiClearPromptResponses, + TuiControlNextErrors, TuiControlNextResponses, + TuiControlResponseErrors, TuiControlResponseResponses, TuiExecuteCommandErrors, TuiExecuteCommandResponses, + TuiOpenHelpErrors, TuiOpenHelpResponses, + TuiOpenModelsErrors, TuiOpenModelsResponses, + TuiOpenSessionsErrors, TuiOpenSessionsResponses, + TuiOpenThemesErrors, TuiOpenThemesResponses, TuiPublishErrors, TuiPublishResponses, TuiSelectSessionErrors, TuiSelectSessionResponses, + TuiShowToastErrors, TuiShowToastResponses, + TuiSubmitPromptErrors, TuiSubmitPromptResponses, + V2SessionCompactErrors, V2SessionCompactResponses, + V2SessionContextErrors, V2SessionContextResponses, V2SessionListErrors, V2SessionListResponses, V2SessionMessagesErrors, V2SessionMessagesResponses, + V2SessionPromptErrors, V2SessionPromptResponses, + V2SessionWaitErrors, V2SessionWaitResponses, + VcsDiffErrors, VcsDiffResponses, + VcsGetErrors, VcsGetResponses, WorktreeCreateErrors, WorktreeCreateInput, WorktreeCreateResponses, + WorktreeListErrors, WorktreeListResponses, WorktreeRemoveErrors, WorktreeRemoveInput, @@ -381,7 +442,7 @@ export class App extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/agent", ...options, ...params, @@ -411,7 +472,7 @@ export class App extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/skill", ...options, ...params, @@ -426,7 +487,7 @@ export class Config extends HeyApiClient { * Retrieve the current global OpenCode configuration settings and preferences. */ public get(options?: Options) { - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/global/config", ...options, }) @@ -464,7 +525,7 @@ export class Global extends HeyApiClient { * Get health information about the OpenCode server. */ public health(options?: Options) { - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/global/health", ...options, }) @@ -476,7 +537,7 @@ export class Global extends HeyApiClient { * Subscribe to global events from the OpenCode system using server-sent events. */ public event(options?: Options) { - return (options?.client ?? this.client).sse.get({ + return (options?.client ?? this.client).sse.get({ url: "/global/event", ...options, }) @@ -488,7 +549,7 @@ export class Global extends HeyApiClient { * Clean up and dispose all OpenCode instances, releasing all resources. */ public dispose(options?: Options) { - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/global/dispose", ...options, }) @@ -548,7 +609,7 @@ export class Event extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).sse.get({ + return (options?.client ?? this.client).sse.get({ url: "/event", ...options, ...params, @@ -580,7 +641,7 @@ export class Config2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/config", ...options, ...params, @@ -647,7 +708,7 @@ export class Config2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/config/providers", ...options, ...params, @@ -679,7 +740,11 @@ export class Console extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalConsoleGetResponses, + ExperimentalConsoleGetErrors, + ThrowOnError + >({ url: "/experimental/console", ...options, ...params, @@ -709,7 +774,11 @@ export class Console extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalConsoleListOrgsResponses, + ExperimentalConsoleListOrgsErrors, + ThrowOnError + >({ url: "/experimental/console/orgs", ...options, ...params, @@ -792,7 +861,11 @@ export class Session extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalSessionListResponses, + ExperimentalSessionListErrors, + ThrowOnError + >({ url: "/experimental/session", ...options, ...params, @@ -824,7 +897,11 @@ export class Resource extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalResourceListResponses, + ExperimentalResourceListErrors, + ThrowOnError + >({ url: "/experimental/resource", ...options, ...params, @@ -856,7 +933,11 @@ export class Adapter extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalWorkspaceAdapterListResponses, + ExperimentalWorkspaceAdapterListErrors, + ThrowOnError + >({ url: "/experimental/workspace/adapter", ...options, ...params, @@ -888,7 +969,11 @@ export class Workspace extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalWorkspaceListResponses, + ExperimentalWorkspaceListErrors, + ThrowOnError + >({ url: "/experimental/workspace", ...options, ...params, @@ -965,7 +1050,11 @@ export class Workspace extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get< + ExperimentalWorkspaceStatusResponses, + ExperimentalWorkspaceStatusErrors, + ThrowOnError + >({ url: "/experimental/workspace/status", ...options, ...params, @@ -1009,15 +1098,15 @@ export class Workspace extends HeyApiClient { } /** - * Restore session into workspace + * Warp session into workspace * - * Replay a session's sync events into the target workspace in batches. + * Move a session's sync history into the target workspace, or detach it to the local project. */ - public sessionRestore( - parameters: { - id: string + public warp( + parameters?: { directory?: string workspace?: string + id?: string | null sessionID?: string }, options?: Options, @@ -1027,20 +1116,20 @@ export class Workspace extends HeyApiClient { [ { args: [ - { in: "path", key: "id" }, { in: "query", key: "directory" }, { in: "query", key: "workspace" }, + { in: "body", key: "id" }, { in: "body", key: "sessionID" }, ], }, ], ) return (options?.client ?? this.client).post< - ExperimentalWorkspaceSessionRestoreResponses, - ExperimentalWorkspaceSessionRestoreErrors, + ExperimentalWorkspaceWarpResponses, + ExperimentalWorkspaceWarpErrors, ThrowOnError >({ - url: "/experimental/workspace/{id}/session-restore", + url: "/experimental/workspace/warp", ...options, ...params, headers: { @@ -1206,7 +1295,7 @@ export class Worktree extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/experimental/worktree", ...options, ...params, @@ -1314,7 +1403,7 @@ export class Find extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/find", ...options, ...params, @@ -1352,7 +1441,7 @@ export class Find extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/find/file", ...options, ...params, @@ -1384,7 +1473,7 @@ export class Find extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/find/symbol", ...options, ...params, @@ -1418,7 +1507,7 @@ export class File extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/file", ...options, ...params, @@ -1450,7 +1539,7 @@ export class File extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/file/content", ...options, ...params, @@ -1480,7 +1569,7 @@ export class File extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/file/status", ...options, ...params, @@ -1512,7 +1601,7 @@ export class Instance extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/instance/dispose", ...options, ...params, @@ -1544,7 +1633,7 @@ export class Path extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/path", ...options, ...params, @@ -1576,7 +1665,7 @@ export class Vcs extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/vcs", ...options, ...params, @@ -1608,7 +1697,7 @@ export class Vcs extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/vcs/diff", ...options, ...params, @@ -1640,7 +1729,7 @@ export class Command extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/command", ...options, ...params, @@ -1672,7 +1761,7 @@ export class Lsp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/lsp", ...options, ...params, @@ -1704,7 +1793,7 @@ export class Formatter extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/formatter", ...options, ...params, @@ -1875,7 +1964,7 @@ export class Mcp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/mcp", ...options, ...params, @@ -1944,7 +2033,7 @@ export class Mcp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/mcp/{name}/connect", ...options, ...params, @@ -1974,7 +2063,7 @@ export class Mcp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/mcp/{name}/disconnect", ...options, ...params, @@ -2011,7 +2100,7 @@ export class Project extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/project", ...options, ...params, @@ -2041,7 +2130,7 @@ export class Project extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/project/current", ...options, ...params, @@ -2071,7 +2160,7 @@ export class Project extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/project/git/init", ...options, ...params, @@ -2155,7 +2244,7 @@ export class Pty extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/pty/shells", ...options, ...params, @@ -2185,7 +2274,7 @@ export class Pty extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/pty", ...options, ...params, @@ -2436,7 +2525,7 @@ export class Question extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/question", ...options, ...params, @@ -2539,7 +2628,7 @@ export class Permission extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/permission", ...options, ...params, @@ -2749,7 +2838,7 @@ export class Provider extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/provider", ...options, ...params, @@ -2779,7 +2868,7 @@ export class Provider extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/provider/auth", ...options, ...params, @@ -2828,7 +2917,7 @@ export class Session2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/session", ...options, ...params, @@ -3116,7 +3205,7 @@ export class Session2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/session/{sessionID}/diff", ...options, ...params, @@ -3318,7 +3407,7 @@ export class Session2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/session/{sessionID}/fork", ...options, ...params, @@ -3894,7 +3983,7 @@ export class Sync extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/sync/start", ...options, ...params, @@ -3956,6 +4045,43 @@ export class Sync extends HeyApiClient { }) } + /** + * Steal session into workspace + * + * Update a session to belong to the current workspace through the sync event system. + */ + public steal( + parameters?: { + directory?: string + workspace?: string + sessionID?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "sessionID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/sync/steal", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + private _history?: History get history(): History { return (this._history ??= new History({ client: this.client })) @@ -4022,7 +4148,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/prompt", ...options, ...params, @@ -4059,7 +4185,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/compact", ...options, ...params, @@ -4091,7 +4217,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/wait", ...options, ...params, @@ -4123,7 +4249,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/api/session/{sessionID}/context", ...options, ...params, @@ -4194,7 +4320,7 @@ export class Control extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/tui/control/next", ...options, ...params, @@ -4226,7 +4352,7 @@ export class Control extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/control/response", ...options, ...params, @@ -4300,7 +4426,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-help", ...options, ...params, @@ -4330,7 +4456,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-sessions", ...options, ...params, @@ -4360,7 +4486,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-themes", ...options, ...params, @@ -4390,7 +4516,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-models", ...options, ...params, @@ -4420,7 +4546,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/submit-prompt", ...options, ...params, @@ -4450,7 +4576,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/clear-prompt", ...options, ...params, @@ -4525,7 +4651,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/show-toast", ...options, ...params, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 86c5a762b..a40b567f8 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -35,7 +35,6 @@ export type Event = | EventVcsBranchUpdated | EventWorkspaceReady | EventWorkspaceFailed - | EventWorkspaceRestore | EventWorkspaceStatus | EventWorktreeReady | EventWorktreeFailed @@ -801,7 +800,6 @@ export type GlobalEvent = { | EventVcsBranchUpdated | EventWorkspaceReady | EventWorkspaceFailed - | EventWorkspaceRestore | EventWorkspaceStatus | EventWorktreeReady | EventWorktreeFailed @@ -2478,17 +2476,6 @@ export type EventWorkspaceFailed = { } } -export type EventWorkspaceRestore = { - id: string - type: "workspace.restore" - properties: { - workspaceID: string - sessionID: string - total: number - step: number - } -} - export type EventWorkspaceStatus = { id: string type: "workspace.status" @@ -3358,6 +3345,15 @@ export type GlobalHealthData = { url: "/global/health" } +export type GlobalHealthErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type GlobalHealthError = GlobalHealthErrors[keyof GlobalHealthErrors] + export type GlobalHealthResponses = { /** * Health information @@ -3377,6 +3373,15 @@ export type GlobalEventData = { url: "/global/event" } +export type GlobalEventErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type GlobalEventError = GlobalEventErrors[keyof GlobalEventErrors] + export type GlobalEventResponses = { /** * Event stream @@ -3393,6 +3398,15 @@ export type GlobalConfigGetData = { url: "/global/config" } +export type GlobalConfigGetErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type GlobalConfigGetError = GlobalConfigGetErrors[keyof GlobalConfigGetErrors] + export type GlobalConfigGetResponses = { /** * Get global config info @@ -3434,6 +3448,15 @@ export type GlobalDisposeData = { url: "/global/dispose" } +export type GlobalDisposeErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type GlobalDisposeError = GlobalDisposeErrors[keyof GlobalDisposeErrors] + export type GlobalDisposeResponses = { /** * Global disposed @@ -3488,6 +3511,15 @@ export type EventSubscribeData = { url: "/event" } +export type EventSubscribeErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type EventSubscribeError = EventSubscribeErrors[keyof EventSubscribeErrors] + export type EventSubscribeResponses = { /** * Event stream @@ -3507,6 +3539,15 @@ export type ConfigGetData = { url: "/config" } +export type ConfigGetErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ConfigGetError = ConfigGetErrors[keyof ConfigGetErrors] + export type ConfigGetResponses = { /** * Get config info @@ -3554,6 +3595,15 @@ export type ConfigProvidersData = { url: "/config/providers" } +export type ConfigProvidersErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ConfigProvidersError = ConfigProvidersErrors[keyof ConfigProvidersErrors] + export type ConfigProvidersResponses = { /** * List of providers @@ -3578,6 +3628,15 @@ export type ExperimentalConsoleGetData = { url: "/experimental/console" } +export type ExperimentalConsoleGetErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalConsoleGetError = ExperimentalConsoleGetErrors[keyof ExperimentalConsoleGetErrors] + export type ExperimentalConsoleGetResponses = { /** * Active Console provider metadata @@ -3597,6 +3656,16 @@ export type ExperimentalConsoleListOrgsData = { url: "/experimental/console/orgs" } +export type ExperimentalConsoleListOrgsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalConsoleListOrgsError = + ExperimentalConsoleListOrgsErrors[keyof ExperimentalConsoleListOrgsErrors] + export type ExperimentalConsoleListOrgsResponses = { /** * Switchable Console orgs @@ -3735,6 +3804,15 @@ export type WorktreeListData = { url: "/experimental/worktree" } +export type WorktreeListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type WorktreeListError = WorktreeListErrors[keyof WorktreeListErrors] + export type WorktreeListResponses = { /** * List of worktree directories @@ -3816,6 +3894,15 @@ export type ExperimentalSessionListData = { url: "/experimental/session" } +export type ExperimentalSessionListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalSessionListError = ExperimentalSessionListErrors[keyof ExperimentalSessionListErrors] + export type ExperimentalSessionListResponses = { /** * List of sessions @@ -3835,6 +3922,15 @@ export type ExperimentalResourceListData = { url: "/experimental/resource" } +export type ExperimentalResourceListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalResourceListError = ExperimentalResourceListErrors[keyof ExperimentalResourceListErrors] + export type ExperimentalResourceListResponses = { /** * MCP resources @@ -3858,6 +3954,15 @@ export type FindTextData = { url: "/find" } +export type FindTextErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FindTextError = FindTextErrors[keyof FindTextErrors] + export type FindTextResponses = { /** * Matches @@ -3897,6 +4002,15 @@ export type FindFilesData = { url: "/find/file" } +export type FindFilesErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FindFilesError = FindFilesErrors[keyof FindFilesErrors] + export type FindFilesResponses = { /** * File paths @@ -3917,6 +4031,15 @@ export type FindSymbolsData = { url: "/find/symbol" } +export type FindSymbolsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FindSymbolsError = FindSymbolsErrors[keyof FindSymbolsErrors] + export type FindSymbolsResponses = { /** * Symbols @@ -3937,6 +4060,15 @@ export type FileListData = { url: "/file" } +export type FileListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FileListError = FileListErrors[keyof FileListErrors] + export type FileListResponses = { /** * Files and directories @@ -3957,6 +4089,15 @@ export type FileReadData = { url: "/file/content" } +export type FileReadErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FileReadError = FileReadErrors[keyof FileReadErrors] + export type FileReadResponses = { /** * File content @@ -3976,6 +4117,15 @@ export type FileStatusData = { url: "/file/status" } +export type FileStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FileStatusError = FileStatusErrors[keyof FileStatusErrors] + export type FileStatusResponses = { /** * File status @@ -3995,6 +4145,15 @@ export type InstanceDisposeData = { url: "/instance/dispose" } +export type InstanceDisposeErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type InstanceDisposeError = InstanceDisposeErrors[keyof InstanceDisposeErrors] + export type InstanceDisposeResponses = { /** * Instance disposed @@ -4014,6 +4173,15 @@ export type PathGetData = { url: "/path" } +export type PathGetErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PathGetError = PathGetErrors[keyof PathGetErrors] + export type PathGetResponses = { /** * Path @@ -4033,6 +4201,15 @@ export type VcsGetData = { url: "/vcs" } +export type VcsGetErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type VcsGetError = VcsGetErrors[keyof VcsGetErrors] + export type VcsGetResponses = { /** * VCS info @@ -4053,6 +4230,15 @@ export type VcsDiffData = { url: "/vcs/diff" } +export type VcsDiffErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type VcsDiffError = VcsDiffErrors[keyof VcsDiffErrors] + export type VcsDiffResponses = { /** * VCS diff @@ -4072,6 +4258,15 @@ export type CommandListData = { url: "/command" } +export type CommandListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type CommandListError = CommandListErrors[keyof CommandListErrors] + export type CommandListResponses = { /** * List of commands @@ -4091,6 +4286,15 @@ export type AppAgentsData = { url: "/agent" } +export type AppAgentsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type AppAgentsError = AppAgentsErrors[keyof AppAgentsErrors] + export type AppAgentsResponses = { /** * List of agents @@ -4110,6 +4314,15 @@ export type AppSkillsData = { url: "/skill" } +export type AppSkillsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type AppSkillsError = AppSkillsErrors[keyof AppSkillsErrors] + export type AppSkillsResponses = { /** * List of skills @@ -4134,6 +4347,15 @@ export type LspStatusData = { url: "/lsp" } +export type LspStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type LspStatusError = LspStatusErrors[keyof LspStatusErrors] + export type LspStatusResponses = { /** * LSP server status @@ -4153,6 +4375,15 @@ export type FormatterStatusData = { url: "/formatter" } +export type FormatterStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type FormatterStatusError = FormatterStatusErrors[keyof FormatterStatusErrors] + export type FormatterStatusResponses = { /** * Formatter status @@ -4172,6 +4403,15 @@ export type McpStatusData = { url: "/mcp" } +export type McpStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type McpStatusError = McpStatusErrors[keyof McpStatusErrors] + export type McpStatusResponses = { /** * MCP server status @@ -4229,6 +4469,10 @@ export type McpAuthRemoveData = { } export type McpAuthRemoveErrors = { + /** + * Bad request + */ + 400: BadRequestError /** * Not found */ @@ -4262,7 +4506,7 @@ export type McpAuthStartData = { export type McpAuthStartErrors = { /** - * McpUnsupportedOAuthError + * McpUnsupportedOAuthError | BadRequest */ 400: McpUnsupportedOAuthError /** @@ -4335,7 +4579,7 @@ export type McpAuthAuthenticateData = { export type McpAuthAuthenticateErrors = { /** - * McpUnsupportedOAuthError + * McpUnsupportedOAuthError | BadRequest */ 400: McpUnsupportedOAuthError /** @@ -4367,6 +4611,15 @@ export type McpConnectData = { url: "/mcp/{name}/connect" } +export type McpConnectErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type McpConnectError = McpConnectErrors[keyof McpConnectErrors] + export type McpConnectResponses = { /** * MCP server connected successfully @@ -4388,6 +4641,15 @@ export type McpDisconnectData = { url: "/mcp/{name}/disconnect" } +export type McpDisconnectErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type McpDisconnectError = McpDisconnectErrors[keyof McpDisconnectErrors] + export type McpDisconnectResponses = { /** * MCP server disconnected successfully @@ -4407,6 +4669,15 @@ export type ProjectListData = { url: "/project" } +export type ProjectListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ProjectListError = ProjectListErrors[keyof ProjectListErrors] + export type ProjectListResponses = { /** * List of projects @@ -4426,6 +4697,15 @@ export type ProjectCurrentData = { url: "/project/current" } +export type ProjectCurrentErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ProjectCurrentError = ProjectCurrentErrors[keyof ProjectCurrentErrors] + export type ProjectCurrentResponses = { /** * Current project information @@ -4445,6 +4725,15 @@ export type ProjectInitGitData = { url: "/project/git/init" } +export type ProjectInitGitErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ProjectInitGitError = ProjectInitGitErrors[keyof ProjectInitGitErrors] + export type ProjectInitGitResponses = { /** * Project information after git initialization @@ -4511,6 +4800,15 @@ export type PtyShellsData = { url: "/pty/shells" } +export type PtyShellsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PtyShellsError = PtyShellsErrors[keyof PtyShellsErrors] + export type PtyShellsResponses = { /** * List of shells @@ -4534,6 +4832,15 @@ export type PtyListData = { url: "/pty" } +export type PtyListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PtyListError = PtyListErrors[keyof PtyListErrors] + export type PtyListResponses = { /** * List of sessions @@ -4592,6 +4899,10 @@ export type PtyRemoveData = { } export type PtyRemoveErrors = { + /** + * Bad request + */ + 400: BadRequestError /** * Not found */ @@ -4622,6 +4933,10 @@ export type PtyGetData = { } export type PtyGetErrors = { + /** + * Bad request + */ + 400: BadRequestError /** * Not found */ @@ -4688,6 +5003,10 @@ export type PtyConnectTokenData = { } export type PtyConnectTokenErrors = { + /** + * Bad request + */ + 400: BadRequestError /** * Forbidden */ @@ -4722,6 +5041,15 @@ export type QuestionListData = { url: "/question" } +export type QuestionListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type QuestionListError = QuestionListErrors[keyof QuestionListErrors] + export type QuestionListResponses = { /** * List of pending questions @@ -4814,6 +5142,15 @@ export type PermissionListData = { url: "/permission" } +export type PermissionListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type PermissionListError = PermissionListErrors[keyof PermissionListErrors] + export type PermissionListResponses = { /** * List of pending permissions @@ -4870,6 +5207,15 @@ export type ProviderListData = { url: "/provider" } +export type ProviderListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ProviderListError = ProviderListErrors[keyof ProviderListErrors] + export type ProviderListResponses = { /** * List of providers @@ -4895,6 +5241,15 @@ export type ProviderAuthData = { url: "/provider/auth" } +export type ProviderAuthErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ProviderAuthError2 = ProviderAuthErrors[keyof ProviderAuthErrors] + export type ProviderAuthResponses = { /** * Provider auth methods @@ -4996,6 +5351,15 @@ export type SessionListData = { url: "/session" } +export type SessionListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SessionListError = SessionListErrors[keyof SessionListErrors] + export type SessionListResponses = { /** * List of sessions @@ -5263,6 +5627,15 @@ export type SessionDiffData = { url: "/session/{sessionID}/diff" } +export type SessionDiffErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors] + export type SessionDiffResponses = { /** * Successfully retrieved diff @@ -5450,6 +5823,15 @@ export type SessionForkData = { url: "/session/{sessionID}/fork" } +export type SessionForkErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SessionForkError = SessionForkErrors[keyof SessionForkErrors] + export type SessionForkResponses = { /** * 200 @@ -5973,6 +6355,15 @@ export type SyncStartData = { url: "/sync/start" } +export type SyncStartErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SyncStartError = SyncStartErrors[keyof SyncStartErrors] + export type SyncStartResponses = { /** * Workspace sync started @@ -6023,6 +6414,38 @@ export type SyncReplayResponses = { export type SyncReplayResponse = SyncReplayResponses[keyof SyncReplayResponses] +export type SyncStealData = { + body?: { + sessionID: string + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/sync/steal" +} + +export type SyncStealErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SyncStealError = SyncStealErrors[keyof SyncStealErrors] + +export type SyncStealResponses = { + /** + * Session stolen into workspace + */ + 200: { + sessionID: string + } +} + +export type SyncStealResponse = SyncStealResponses[keyof SyncStealResponses] + export type SyncHistoryListData = { body?: { [key: string]: number @@ -6104,6 +6527,15 @@ export type V2SessionPromptData = { url: "/api/session/{sessionID}/prompt" } +export type V2SessionPromptErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type V2SessionPromptError = V2SessionPromptErrors[keyof V2SessionPromptErrors] + export type V2SessionPromptResponses = { /** * Session.Message @@ -6125,6 +6557,15 @@ export type V2SessionCompactData = { url: "/api/session/{sessionID}/compact" } +export type V2SessionCompactErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type V2SessionCompactError = V2SessionCompactErrors[keyof V2SessionCompactErrors] + export type V2SessionCompactResponses = { /** * @@ -6146,6 +6587,15 @@ export type V2SessionWaitData = { url: "/api/session/{sessionID}/wait" } +export type V2SessionWaitErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type V2SessionWaitError = V2SessionWaitErrors[keyof V2SessionWaitErrors] + export type V2SessionWaitResponses = { /** * @@ -6167,6 +6617,15 @@ export type V2SessionContextData = { url: "/api/session/{sessionID}/context" } +export type V2SessionContextErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type V2SessionContextError = V2SessionContextErrors[keyof V2SessionContextErrors] + export type V2SessionContextResponses = { /** * Success @@ -6246,6 +6705,15 @@ export type TuiOpenHelpData = { url: "/tui/open-help" } +export type TuiOpenHelpErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiOpenHelpError = TuiOpenHelpErrors[keyof TuiOpenHelpErrors] + export type TuiOpenHelpResponses = { /** * Help dialog opened successfully @@ -6265,6 +6733,15 @@ export type TuiOpenSessionsData = { url: "/tui/open-sessions" } +export type TuiOpenSessionsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiOpenSessionsError = TuiOpenSessionsErrors[keyof TuiOpenSessionsErrors] + export type TuiOpenSessionsResponses = { /** * Session dialog opened successfully @@ -6284,6 +6761,15 @@ export type TuiOpenThemesData = { url: "/tui/open-themes" } +export type TuiOpenThemesErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiOpenThemesError = TuiOpenThemesErrors[keyof TuiOpenThemesErrors] + export type TuiOpenThemesResponses = { /** * Theme dialog opened successfully @@ -6303,6 +6789,15 @@ export type TuiOpenModelsData = { url: "/tui/open-models" } +export type TuiOpenModelsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiOpenModelsError = TuiOpenModelsErrors[keyof TuiOpenModelsErrors] + export type TuiOpenModelsResponses = { /** * Model dialog opened successfully @@ -6322,6 +6817,15 @@ export type TuiSubmitPromptData = { url: "/tui/submit-prompt" } +export type TuiSubmitPromptErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiSubmitPromptError = TuiSubmitPromptErrors[keyof TuiSubmitPromptErrors] + export type TuiSubmitPromptResponses = { /** * Prompt submitted successfully @@ -6341,6 +6845,15 @@ export type TuiClearPromptData = { url: "/tui/clear-prompt" } +export type TuiClearPromptErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiClearPromptError = TuiClearPromptErrors[keyof TuiClearPromptErrors] + export type TuiClearPromptResponses = { /** * Prompt cleared successfully @@ -6395,6 +6908,15 @@ export type TuiShowToastData = { url: "/tui/show-toast" } +export type TuiShowToastErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiShowToastError = TuiShowToastErrors[keyof TuiShowToastErrors] + export type TuiShowToastResponses = { /** * Toast notification shown successfully @@ -6479,6 +7001,15 @@ export type TuiControlNextData = { url: "/tui/control/next" } +export type TuiControlNextErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiControlNextError = TuiControlNextErrors[keyof TuiControlNextErrors] + export type TuiControlNextResponses = { /** * Next TUI request @@ -6501,6 +7032,15 @@ export type TuiControlResponseData = { url: "/tui/control/response" } +export type TuiControlResponseErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type TuiControlResponseError = TuiControlResponseErrors[keyof TuiControlResponseErrors] + export type TuiControlResponseResponses = { /** * Response submitted successfully @@ -6520,6 +7060,16 @@ export type ExperimentalWorkspaceAdapterListData = { url: "/experimental/workspace/adapter" } +export type ExperimentalWorkspaceAdapterListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalWorkspaceAdapterListError = + ExperimentalWorkspaceAdapterListErrors[keyof ExperimentalWorkspaceAdapterListErrors] + export type ExperimentalWorkspaceAdapterListResponses = { /** * Workspace adapters @@ -6544,6 +7094,15 @@ export type ExperimentalWorkspaceListData = { url: "/experimental/workspace" } +export type ExperimentalWorkspaceListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalWorkspaceListError = ExperimentalWorkspaceListErrors[keyof ExperimentalWorkspaceListErrors] + export type ExperimentalWorkspaceListResponses = { /** * Workspaces @@ -6559,7 +7118,7 @@ export type ExperimentalWorkspaceCreateData = { id?: string type: string branch: string | null - extra?: unknown | null + extra: unknown | null } path?: never query?: { @@ -6599,6 +7158,16 @@ export type ExperimentalWorkspaceStatusData = { url: "/experimental/workspace/status" } +export type ExperimentalWorkspaceStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalWorkspaceStatusError = + ExperimentalWorkspaceStatusErrors[keyof ExperimentalWorkspaceStatusErrors] + export type ExperimentalWorkspaceStatusResponses = { /** * Workspace status @@ -6644,41 +7213,37 @@ export type ExperimentalWorkspaceRemoveResponses = { export type ExperimentalWorkspaceRemoveResponse = ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses] -export type ExperimentalWorkspaceSessionRestoreData = { +export type ExperimentalWorkspaceWarpData = { body?: { + id: string | null sessionID: string } - path: { - id: string - } + path?: never query?: { directory?: string workspace?: string } - url: "/experimental/workspace/{id}/session-restore" + url: "/experimental/workspace/warp" } -export type ExperimentalWorkspaceSessionRestoreErrors = { +export type ExperimentalWorkspaceWarpErrors = { /** * Bad request */ 400: BadRequestError } -export type ExperimentalWorkspaceSessionRestoreError = - ExperimentalWorkspaceSessionRestoreErrors[keyof ExperimentalWorkspaceSessionRestoreErrors] +export type ExperimentalWorkspaceWarpError = ExperimentalWorkspaceWarpErrors[keyof ExperimentalWorkspaceWarpErrors] -export type ExperimentalWorkspaceSessionRestoreResponses = { +export type ExperimentalWorkspaceWarpResponses = { /** - * Session replay started + * Session warped */ - 200: { - total: number - } + 204: void } -export type ExperimentalWorkspaceSessionRestoreResponse = - ExperimentalWorkspaceSessionRestoreResponses[keyof ExperimentalWorkspaceSessionRestoreResponses] +export type ExperimentalWorkspaceWarpResponse = + ExperimentalWorkspaceWarpResponses[keyof ExperimentalWorkspaceWarpResponses] export type PtyConnectData = { body?: never @@ -6693,6 +7258,10 @@ export type PtyConnectData = { } export type PtyConnectErrors = { + /** + * Bad request + */ + 400: BadRequestError /** * Forbidden */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 6ff18b515..1a2f1e947 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -218,6 +218,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get health information about the OpenCode server.", @@ -245,6 +255,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Subscribe to global events from the OpenCode system using server-sent events.", @@ -272,6 +292,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the current global OpenCode configuration settings and preferences.", @@ -344,6 +374,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Clean up and dispose all OpenCode instances, releasing all resources.", @@ -470,6 +510,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get events", @@ -514,6 +564,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the current OpenCode configuration settings and preferences.", @@ -636,6 +696,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all configured AI providers and their default models.", @@ -680,6 +750,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get the active Console org name and the set of provider IDs managed by that Console org.", @@ -757,6 +837,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get the available Console orgs across logged-in accounts, including the current active org.", @@ -993,6 +1083,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "List all sandbox worktrees for the current project.", @@ -1292,6 +1392,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", @@ -1340,6 +1450,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get all available MCP resources from connected servers. Optionally filter by name.", @@ -1456,6 +1576,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Search for text patterns across files in the project using ripgrep.", @@ -1540,6 +1670,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Search for files or directories by name or pattern in the project directory.", @@ -1596,6 +1736,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Search for workspace symbols like functions, classes, and variables using LSP.", @@ -1652,6 +1802,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "List files and directories in a specified path.", @@ -1704,6 +1864,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Read the content of a specified file.", @@ -1752,6 +1922,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get the git status of all files in the project.", @@ -1797,6 +1977,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Clean up and dispose the current OpenCode instance, releasing all resources.", @@ -1841,6 +2031,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the current working directory and related path information for the OpenCode instance.", @@ -1885,6 +2085,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve version control system (VCS) information for the current project, such as git branch.", @@ -1942,6 +2152,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the current git diff for the working tree or against the default branch.", @@ -1990,6 +2210,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all available commands in the OpenCode system.", @@ -2038,6 +2268,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all available AI agents in the OpenCode system.", @@ -2102,6 +2342,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all available skills in the OpenCode system.", @@ -2150,6 +2400,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get LSP server status", @@ -2198,6 +2458,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get formatter status", @@ -2246,6 +2516,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get the status of all Model Context Protocol (MCP) servers.", @@ -2393,7 +2673,7 @@ } }, "400": { - "description": "McpUnsupportedOAuthError", + "description": "McpUnsupportedOAuthError | BadRequest", "content": { "application/json": { "schema": { @@ -2471,6 +2751,16 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -2622,7 +2912,7 @@ } }, "400": { - "description": "McpUnsupportedOAuthError", + "description": "McpUnsupportedOAuthError | BadRequest", "content": { "application/json": { "schema": { @@ -2693,6 +2983,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Connect an MCP server.", @@ -2745,6 +3045,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Disconnect an MCP server.", @@ -2792,6 +3102,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of projects that have been opened with OpenCode.", @@ -2836,6 +3156,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the currently active project that OpenCode is working with.", @@ -2880,6 +3210,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Create a git repository for the current project and return the refreshed project info.", @@ -3053,6 +3393,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of available shells on the system.", @@ -3101,6 +3451,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.", @@ -3240,6 +3600,16 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3393,6 +3763,16 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "404": { "description": "Not found", "content": { @@ -3468,6 +3848,16 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "403": { "description": "Forbidden", "content": { @@ -3535,6 +3925,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get all pending question requests across all sessions.", @@ -3751,6 +4151,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get all pending permission requests across all sessions.", @@ -3912,6 +4322,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all available AI providers, including both available and connected ones.", @@ -3963,6 +4383,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve available authentication methods for all AI providers.", @@ -4236,6 +4666,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", @@ -4852,6 +5292,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get the file changes (diff) that resulted from a specific user message in the session.", @@ -5342,6 +5792,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Create a new session by forking an existing session at a specific message point.", @@ -6668,6 +7128,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Start sync loops for workspaces in the current project that have active sessions.", @@ -6785,6 +7255,84 @@ ] } }, + "/sync/steal": { + "post": { + "tags": ["sync"], + "operationId": "sync.steal", + "parameters": [ + { + "name": "directory", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Session stolen into workspace", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"], + "additionalProperties": false, + "description": "Session stolen into workspace" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Update a session to belong to the current workspace through the sync event system.", + "summary": "Steal session into workspace", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"], + "additionalProperties": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.sync.steal({\n ...\n})" + } + ] + } + }, "/sync/history": { "post": { "tags": ["sync"], @@ -6971,6 +7519,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Create a v2 session message and queue it for the agent loop.", @@ -7036,6 +7594,16 @@ "responses": { "204": { "description": "" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Compact a v2 session conversation.", @@ -7082,6 +7650,16 @@ "responses": { "204": { "description": "" + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Wait for a v2 session agent loop to become idle.", @@ -7138,6 +7716,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the active context messages for a v2 session (all messages after the last compaction).", @@ -7317,6 +7905,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Open the help dialog in the TUI to display user assistance information.", @@ -7362,6 +7960,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Open the session dialog.", @@ -7407,6 +8015,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Open the theme dialog.", @@ -7452,6 +8070,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Open the model dialog.", @@ -7497,6 +8125,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Submit the prompt.", @@ -7542,6 +8180,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Clear the prompt.", @@ -7658,6 +8306,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Show a toast notification in the TUI.", @@ -7897,6 +8555,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Retrieve the next TUI request from the queue for processing.", @@ -7942,6 +8610,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Submit a response to the TUI request queue to complete a pending request.", @@ -8010,6 +8688,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "List all available workspace adapters for the current project.", @@ -8058,6 +8746,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "List all workspaces.", @@ -8145,7 +8843,7 @@ ] } }, - "required": ["type", "branch"], + "required": ["type", "branch", "extra"], "additionalProperties": false } } @@ -8206,6 +8904,16 @@ } } } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } } }, "description": "Get connection status for workspaces in the current project.", @@ -8281,10 +8989,10 @@ ] } }, - "/experimental/workspace/{id}/session-restore": { + "/experimental/workspace/warp": { "post": { "tags": ["workspace"], - "operationId": "experimental.workspace.sessionRestore", + "operationId": "experimental.workspace.warp", "parameters": [ { "name": "directory", @@ -8301,36 +9009,11 @@ "schema": { "type": "string" } - }, - { - "name": "id", - "in": "path", - "schema": { - "type": "string", - "pattern": "^wrk.*" - }, - "required": true } ], "responses": { - "200": { - "description": "Session replay started", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "total": { - "type": "integer", - "minimum": 0 - } - }, - "required": ["total"], - "additionalProperties": false, - "description": "Session replay started" - } - } - } + "204": { + "description": "Session warped" }, "400": { "description": "Bad request", @@ -8343,19 +9026,22 @@ } } }, - "description": "Replay a session's sync events into the target workspace in batches.", - "summary": "Restore session into workspace", + "description": "Move a session's sync history into the target workspace, or detach it to the local project.", + "summary": "Warp session into workspace", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { + "id": { + "type": "string" + }, "sessionID": { "type": "string" } }, - "required": ["sessionID"], + "required": ["id", "sessionID"], "additionalProperties": false } } @@ -8364,7 +9050,7 @@ "x-codeSamples": [ { "lang": "js", - "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.sessionRestore({\n ...\n})" + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.warp({\n ...\n})" } ] } @@ -8412,6 +9098,16 @@ } } }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, "403": { "description": "Forbidden", "content": { @@ -8538,9 +9234,6 @@ { "$ref": "#/components/schemas/EventWorkspaceFailed" }, - { - "$ref": "#/components/schemas/EventWorkspaceRestore" - }, { "$ref": "#/components/schemas/EventWorkspaceStatus" }, @@ -10737,9 +11430,6 @@ { "$ref": "#/components/schemas/EventWorkspaceFailed" }, - { - "$ref": "#/components/schemas/EventWorkspaceRestore" - }, { "$ref": "#/components/schemas/EventWorkspaceStatus" }, @@ -15793,41 +16483,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventWorkspaceRestore": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["workspace.restore"] - }, - "properties": { - "type": "object", - "properties": { - "workspaceID": { - "type": "string" - }, - "sessionID": { - "type": "string" - }, - "total": { - "type": "integer", - "minimum": 0 - }, - "step": { - "type": "integer", - "minimum": 0 - } - }, - "required": ["workspaceID", "sessionID", "total", "step"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "EventWorkspaceStatus": { "type": "object", "properties": { From f33b17e8ac157237fdf3c4d3ff06ced126fb4752 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 5 May 2026 01:29:49 +0000 Subject: [PATCH 07/18] chore: generate --- packages/sdk/js/src/v2/gen/sdk.gen.ts | 207 +++----- packages/sdk/js/src/v2/gen/types.gen.ts | 562 +-------------------- packages/sdk/openapi.json | 646 +----------------------- 3 files changed, 67 insertions(+), 1348 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index ab191b056..ffc0970c0 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -4,84 +4,58 @@ import { client } from "./client.gen.js" import { buildClientParams, type Client, type Options as Options2, type TDataShape } from "./client/index.js" import type { AgentPartInput, - AppAgentsErrors, AppAgentsResponses, AppLogErrors, AppLogResponses, - AppSkillsErrors, AppSkillsResponses, Auth as Auth3, AuthRemoveErrors, AuthRemoveResponses, AuthSetErrors, AuthSetResponses, - CommandListErrors, CommandListResponses, Config as Config3, - ConfigGetErrors, ConfigGetResponses, - ConfigProvidersErrors, ConfigProvidersResponses, ConfigUpdateErrors, ConfigUpdateResponses, - EventSubscribeErrors, EventSubscribeResponses, EventTuiCommandExecute2, EventTuiPromptAppend2, EventTuiSessionSelect2, EventTuiToastShow2, - ExperimentalConsoleGetErrors, ExperimentalConsoleGetResponses, - ExperimentalConsoleListOrgsErrors, ExperimentalConsoleListOrgsResponses, ExperimentalConsoleSwitchOrgResponses, - ExperimentalResourceListErrors, ExperimentalResourceListResponses, - ExperimentalSessionListErrors, ExperimentalSessionListResponses, - ExperimentalWorkspaceAdapterListErrors, ExperimentalWorkspaceAdapterListResponses, ExperimentalWorkspaceCreateErrors, ExperimentalWorkspaceCreateResponses, - ExperimentalWorkspaceListErrors, ExperimentalWorkspaceListResponses, ExperimentalWorkspaceRemoveErrors, ExperimentalWorkspaceRemoveResponses, - ExperimentalWorkspaceStatusErrors, ExperimentalWorkspaceStatusResponses, ExperimentalWorkspaceWarpErrors, ExperimentalWorkspaceWarpResponses, - FileListErrors, FileListResponses, FilePartInput, FilePartSource, - FileReadErrors, FileReadResponses, - FileStatusErrors, FileStatusResponses, - FindFilesErrors, FindFilesResponses, - FindSymbolsErrors, FindSymbolsResponses, - FindTextErrors, FindTextResponses, - FormatterStatusErrors, FormatterStatusResponses, - GlobalConfigGetErrors, GlobalConfigGetResponses, GlobalConfigUpdateErrors, GlobalConfigUpdateResponses, - GlobalDisposeErrors, GlobalDisposeResponses, - GlobalEventErrors, GlobalEventResponses, - GlobalHealthErrors, GlobalHealthResponses, GlobalUpgradeErrors, GlobalUpgradeResponses, - InstanceDisposeErrors, InstanceDisposeResponses, - LspStatusErrors, LspStatusResponses, McpAddErrors, McpAddResponses, @@ -93,13 +67,10 @@ import type { McpAuthRemoveResponses, McpAuthStartErrors, McpAuthStartResponses, - McpConnectErrors, McpConnectResponses, - McpDisconnectErrors, McpDisconnectResponses, McpLocalConfig, McpRemoteConfig, - McpStatusErrors, McpStatusResponses, OutputFormat, Part as Part2, @@ -107,27 +78,20 @@ import type { PartDeleteResponses, PartUpdateErrors, PartUpdateResponses, - PathGetErrors, PathGetResponses, - PermissionListErrors, PermissionListResponses, PermissionReplyErrors, PermissionReplyResponses, PermissionRespondErrors, PermissionRespondResponses, PermissionRuleset, - ProjectCurrentErrors, ProjectCurrentResponses, - ProjectInitGitErrors, ProjectInitGitResponses, - ProjectListErrors, ProjectListResponses, ProjectUpdateErrors, ProjectUpdateResponses, Prompt, - ProviderAuthErrors, ProviderAuthResponses, - ProviderListErrors, ProviderListResponses, ProviderOauthAuthorizeErrors, ProviderOauthAuthorizeResponses, @@ -141,16 +105,13 @@ import type { PtyCreateResponses, PtyGetErrors, PtyGetResponses, - PtyListErrors, PtyListResponses, PtyRemoveErrors, PtyRemoveResponses, - PtyShellsErrors, PtyShellsResponses, PtyUpdateErrors, PtyUpdateResponses, QuestionAnswer, - QuestionListErrors, QuestionListResponses, QuestionRejectErrors, QuestionRejectResponses, @@ -169,15 +130,12 @@ import type { SessionDeleteMessageResponses, SessionDeleteResponses, SessionDelivery, - SessionDiffErrors, SessionDiffResponses, - SessionForkErrors, SessionForkResponses, SessionGetErrors, SessionGetResponses, SessionInitErrors, SessionInitResponses, - SessionListErrors, SessionListResponses, SessionMessageErrors, SessionMessageResponses, @@ -210,7 +168,6 @@ import type { SyncHistoryListResponses, SyncReplayErrors, SyncReplayResponses, - SyncStartErrors, SyncStartResponses, SyncStealErrors, SyncStealResponses, @@ -221,50 +178,34 @@ import type { ToolListResponses, TuiAppendPromptErrors, TuiAppendPromptResponses, - TuiClearPromptErrors, TuiClearPromptResponses, - TuiControlNextErrors, TuiControlNextResponses, - TuiControlResponseErrors, TuiControlResponseResponses, TuiExecuteCommandErrors, TuiExecuteCommandResponses, - TuiOpenHelpErrors, TuiOpenHelpResponses, - TuiOpenModelsErrors, TuiOpenModelsResponses, - TuiOpenSessionsErrors, TuiOpenSessionsResponses, - TuiOpenThemesErrors, TuiOpenThemesResponses, TuiPublishErrors, TuiPublishResponses, TuiSelectSessionErrors, TuiSelectSessionResponses, - TuiShowToastErrors, TuiShowToastResponses, - TuiSubmitPromptErrors, TuiSubmitPromptResponses, - V2SessionCompactErrors, V2SessionCompactResponses, - V2SessionContextErrors, V2SessionContextResponses, V2SessionListErrors, V2SessionListResponses, V2SessionMessagesErrors, V2SessionMessagesResponses, - V2SessionPromptErrors, V2SessionPromptResponses, - V2SessionWaitErrors, V2SessionWaitResponses, - VcsDiffErrors, VcsDiffResponses, - VcsGetErrors, VcsGetResponses, WorktreeCreateErrors, WorktreeCreateInput, WorktreeCreateResponses, - WorktreeListErrors, WorktreeListResponses, WorktreeRemoveErrors, WorktreeRemoveInput, @@ -442,7 +383,7 @@ export class App extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/agent", ...options, ...params, @@ -472,7 +413,7 @@ export class App extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/skill", ...options, ...params, @@ -487,7 +428,7 @@ export class Config extends HeyApiClient { * Retrieve the current global OpenCode configuration settings and preferences. */ public get(options?: Options) { - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/global/config", ...options, }) @@ -525,7 +466,7 @@ export class Global extends HeyApiClient { * Get health information about the OpenCode server. */ public health(options?: Options) { - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/global/health", ...options, }) @@ -537,7 +478,7 @@ export class Global extends HeyApiClient { * Subscribe to global events from the OpenCode system using server-sent events. */ public event(options?: Options) { - return (options?.client ?? this.client).sse.get({ + return (options?.client ?? this.client).sse.get({ url: "/global/event", ...options, }) @@ -549,7 +490,7 @@ export class Global extends HeyApiClient { * Clean up and dispose all OpenCode instances, releasing all resources. */ public dispose(options?: Options) { - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/global/dispose", ...options, }) @@ -609,7 +550,7 @@ export class Event extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).sse.get({ + return (options?.client ?? this.client).sse.get({ url: "/event", ...options, ...params, @@ -641,7 +582,7 @@ export class Config2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/config", ...options, ...params, @@ -708,7 +649,7 @@ export class Config2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/config/providers", ...options, ...params, @@ -740,11 +681,7 @@ export class Console extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalConsoleGetResponses, - ExperimentalConsoleGetErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/console", ...options, ...params, @@ -774,11 +711,7 @@ export class Console extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalConsoleListOrgsResponses, - ExperimentalConsoleListOrgsErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/console/orgs", ...options, ...params, @@ -861,11 +794,7 @@ export class Session extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalSessionListResponses, - ExperimentalSessionListErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/session", ...options, ...params, @@ -897,11 +826,7 @@ export class Resource extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalResourceListResponses, - ExperimentalResourceListErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/resource", ...options, ...params, @@ -933,11 +858,7 @@ export class Adapter extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalWorkspaceAdapterListResponses, - ExperimentalWorkspaceAdapterListErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/workspace/adapter", ...options, ...params, @@ -969,11 +890,7 @@ export class Workspace extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalWorkspaceListResponses, - ExperimentalWorkspaceListErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/workspace", ...options, ...params, @@ -1050,11 +967,7 @@ export class Workspace extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get< - ExperimentalWorkspaceStatusResponses, - ExperimentalWorkspaceStatusErrors, - ThrowOnError - >({ + return (options?.client ?? this.client).get({ url: "/experimental/workspace/status", ...options, ...params, @@ -1106,7 +1019,7 @@ export class Workspace extends HeyApiClient { parameters?: { directory?: string workspace?: string - id?: string | null + id?: string sessionID?: string }, options?: Options, @@ -1295,7 +1208,7 @@ export class Worktree extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/experimental/worktree", ...options, ...params, @@ -1403,7 +1316,7 @@ export class Find extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/find", ...options, ...params, @@ -1441,7 +1354,7 @@ export class Find extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/find/file", ...options, ...params, @@ -1473,7 +1386,7 @@ export class Find extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/find/symbol", ...options, ...params, @@ -1507,7 +1420,7 @@ export class File extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/file", ...options, ...params, @@ -1539,7 +1452,7 @@ export class File extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/file/content", ...options, ...params, @@ -1569,7 +1482,7 @@ export class File extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/file/status", ...options, ...params, @@ -1601,7 +1514,7 @@ export class Instance extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/instance/dispose", ...options, ...params, @@ -1633,7 +1546,7 @@ export class Path extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/path", ...options, ...params, @@ -1665,7 +1578,7 @@ export class Vcs extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/vcs", ...options, ...params, @@ -1697,7 +1610,7 @@ export class Vcs extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/vcs/diff", ...options, ...params, @@ -1729,7 +1642,7 @@ export class Command extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/command", ...options, ...params, @@ -1761,7 +1674,7 @@ export class Lsp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/lsp", ...options, ...params, @@ -1793,7 +1706,7 @@ export class Formatter extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/formatter", ...options, ...params, @@ -1964,7 +1877,7 @@ export class Mcp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/mcp", ...options, ...params, @@ -2033,7 +1946,7 @@ export class Mcp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/mcp/{name}/connect", ...options, ...params, @@ -2063,7 +1976,7 @@ export class Mcp extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/mcp/{name}/disconnect", ...options, ...params, @@ -2100,7 +2013,7 @@ export class Project extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/project", ...options, ...params, @@ -2130,7 +2043,7 @@ export class Project extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/project/current", ...options, ...params, @@ -2160,7 +2073,7 @@ export class Project extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/project/git/init", ...options, ...params, @@ -2244,7 +2157,7 @@ export class Pty extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/pty/shells", ...options, ...params, @@ -2274,7 +2187,7 @@ export class Pty extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/pty", ...options, ...params, @@ -2525,7 +2438,7 @@ export class Question extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/question", ...options, ...params, @@ -2628,7 +2541,7 @@ export class Permission extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/permission", ...options, ...params, @@ -2838,7 +2751,7 @@ export class Provider extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/provider", ...options, ...params, @@ -2868,7 +2781,7 @@ export class Provider extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/provider/auth", ...options, ...params, @@ -2917,7 +2830,7 @@ export class Session2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/session", ...options, ...params, @@ -3205,7 +3118,7 @@ export class Session2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/session/{sessionID}/diff", ...options, ...params, @@ -3407,7 +3320,7 @@ export class Session2 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/session/{sessionID}/fork", ...options, ...params, @@ -3983,7 +3896,7 @@ export class Sync extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/sync/start", ...options, ...params, @@ -4148,7 +4061,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/prompt", ...options, ...params, @@ -4185,7 +4098,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/compact", ...options, ...params, @@ -4217,7 +4130,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/api/session/{sessionID}/wait", ...options, ...params, @@ -4249,7 +4162,7 @@ export class Session3 extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/api/session/{sessionID}/context", ...options, ...params, @@ -4320,7 +4233,7 @@ export class Control extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ + return (options?.client ?? this.client).get({ url: "/tui/control/next", ...options, ...params, @@ -4352,7 +4265,7 @@ export class Control extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/control/response", ...options, ...params, @@ -4426,7 +4339,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-help", ...options, ...params, @@ -4456,7 +4369,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-sessions", ...options, ...params, @@ -4486,7 +4399,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-themes", ...options, ...params, @@ -4516,7 +4429,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/open-models", ...options, ...params, @@ -4546,7 +4459,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/submit-prompt", ...options, ...params, @@ -4576,7 +4489,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/clear-prompt", ...options, ...params, @@ -4651,7 +4564,7 @@ export class Tui extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).post({ + return (options?.client ?? this.client).post({ url: "/tui/show-toast", ...options, ...params, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a40b567f8..c0255754d 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3345,15 +3345,6 @@ export type GlobalHealthData = { url: "/global/health" } -export type GlobalHealthErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type GlobalHealthError = GlobalHealthErrors[keyof GlobalHealthErrors] - export type GlobalHealthResponses = { /** * Health information @@ -3373,15 +3364,6 @@ export type GlobalEventData = { url: "/global/event" } -export type GlobalEventErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type GlobalEventError = GlobalEventErrors[keyof GlobalEventErrors] - export type GlobalEventResponses = { /** * Event stream @@ -3398,15 +3380,6 @@ export type GlobalConfigGetData = { url: "/global/config" } -export type GlobalConfigGetErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type GlobalConfigGetError = GlobalConfigGetErrors[keyof GlobalConfigGetErrors] - export type GlobalConfigGetResponses = { /** * Get global config info @@ -3448,15 +3421,6 @@ export type GlobalDisposeData = { url: "/global/dispose" } -export type GlobalDisposeErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type GlobalDisposeError = GlobalDisposeErrors[keyof GlobalDisposeErrors] - export type GlobalDisposeResponses = { /** * Global disposed @@ -3511,15 +3475,6 @@ export type EventSubscribeData = { url: "/event" } -export type EventSubscribeErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type EventSubscribeError = EventSubscribeErrors[keyof EventSubscribeErrors] - export type EventSubscribeResponses = { /** * Event stream @@ -3539,15 +3494,6 @@ export type ConfigGetData = { url: "/config" } -export type ConfigGetErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ConfigGetError = ConfigGetErrors[keyof ConfigGetErrors] - export type ConfigGetResponses = { /** * Get config info @@ -3595,15 +3541,6 @@ export type ConfigProvidersData = { url: "/config/providers" } -export type ConfigProvidersErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ConfigProvidersError = ConfigProvidersErrors[keyof ConfigProvidersErrors] - export type ConfigProvidersResponses = { /** * List of providers @@ -3628,15 +3565,6 @@ export type ExperimentalConsoleGetData = { url: "/experimental/console" } -export type ExperimentalConsoleGetErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalConsoleGetError = ExperimentalConsoleGetErrors[keyof ExperimentalConsoleGetErrors] - export type ExperimentalConsoleGetResponses = { /** * Active Console provider metadata @@ -3656,16 +3584,6 @@ export type ExperimentalConsoleListOrgsData = { url: "/experimental/console/orgs" } -export type ExperimentalConsoleListOrgsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalConsoleListOrgsError = - ExperimentalConsoleListOrgsErrors[keyof ExperimentalConsoleListOrgsErrors] - export type ExperimentalConsoleListOrgsResponses = { /** * Switchable Console orgs @@ -3804,15 +3722,6 @@ export type WorktreeListData = { url: "/experimental/worktree" } -export type WorktreeListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type WorktreeListError = WorktreeListErrors[keyof WorktreeListErrors] - export type WorktreeListResponses = { /** * List of worktree directories @@ -3894,15 +3803,6 @@ export type ExperimentalSessionListData = { url: "/experimental/session" } -export type ExperimentalSessionListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalSessionListError = ExperimentalSessionListErrors[keyof ExperimentalSessionListErrors] - export type ExperimentalSessionListResponses = { /** * List of sessions @@ -3922,15 +3822,6 @@ export type ExperimentalResourceListData = { url: "/experimental/resource" } -export type ExperimentalResourceListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalResourceListError = ExperimentalResourceListErrors[keyof ExperimentalResourceListErrors] - export type ExperimentalResourceListResponses = { /** * MCP resources @@ -3954,15 +3845,6 @@ export type FindTextData = { url: "/find" } -export type FindTextErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FindTextError = FindTextErrors[keyof FindTextErrors] - export type FindTextResponses = { /** * Matches @@ -4002,15 +3884,6 @@ export type FindFilesData = { url: "/find/file" } -export type FindFilesErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FindFilesError = FindFilesErrors[keyof FindFilesErrors] - export type FindFilesResponses = { /** * File paths @@ -4031,15 +3904,6 @@ export type FindSymbolsData = { url: "/find/symbol" } -export type FindSymbolsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FindSymbolsError = FindSymbolsErrors[keyof FindSymbolsErrors] - export type FindSymbolsResponses = { /** * Symbols @@ -4060,15 +3924,6 @@ export type FileListData = { url: "/file" } -export type FileListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FileListError = FileListErrors[keyof FileListErrors] - export type FileListResponses = { /** * Files and directories @@ -4089,15 +3944,6 @@ export type FileReadData = { url: "/file/content" } -export type FileReadErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FileReadError = FileReadErrors[keyof FileReadErrors] - export type FileReadResponses = { /** * File content @@ -4117,15 +3963,6 @@ export type FileStatusData = { url: "/file/status" } -export type FileStatusErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FileStatusError = FileStatusErrors[keyof FileStatusErrors] - export type FileStatusResponses = { /** * File status @@ -4145,15 +3982,6 @@ export type InstanceDisposeData = { url: "/instance/dispose" } -export type InstanceDisposeErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type InstanceDisposeError = InstanceDisposeErrors[keyof InstanceDisposeErrors] - export type InstanceDisposeResponses = { /** * Instance disposed @@ -4173,15 +4001,6 @@ export type PathGetData = { url: "/path" } -export type PathGetErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type PathGetError = PathGetErrors[keyof PathGetErrors] - export type PathGetResponses = { /** * Path @@ -4201,15 +4020,6 @@ export type VcsGetData = { url: "/vcs" } -export type VcsGetErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type VcsGetError = VcsGetErrors[keyof VcsGetErrors] - export type VcsGetResponses = { /** * VCS info @@ -4230,15 +4040,6 @@ export type VcsDiffData = { url: "/vcs/diff" } -export type VcsDiffErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type VcsDiffError = VcsDiffErrors[keyof VcsDiffErrors] - export type VcsDiffResponses = { /** * VCS diff @@ -4258,15 +4059,6 @@ export type CommandListData = { url: "/command" } -export type CommandListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type CommandListError = CommandListErrors[keyof CommandListErrors] - export type CommandListResponses = { /** * List of commands @@ -4286,15 +4078,6 @@ export type AppAgentsData = { url: "/agent" } -export type AppAgentsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type AppAgentsError = AppAgentsErrors[keyof AppAgentsErrors] - export type AppAgentsResponses = { /** * List of agents @@ -4314,15 +4097,6 @@ export type AppSkillsData = { url: "/skill" } -export type AppSkillsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type AppSkillsError = AppSkillsErrors[keyof AppSkillsErrors] - export type AppSkillsResponses = { /** * List of skills @@ -4347,15 +4121,6 @@ export type LspStatusData = { url: "/lsp" } -export type LspStatusErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type LspStatusError = LspStatusErrors[keyof LspStatusErrors] - export type LspStatusResponses = { /** * LSP server status @@ -4375,15 +4140,6 @@ export type FormatterStatusData = { url: "/formatter" } -export type FormatterStatusErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type FormatterStatusError = FormatterStatusErrors[keyof FormatterStatusErrors] - export type FormatterStatusResponses = { /** * Formatter status @@ -4403,15 +4159,6 @@ export type McpStatusData = { url: "/mcp" } -export type McpStatusErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type McpStatusError = McpStatusErrors[keyof McpStatusErrors] - export type McpStatusResponses = { /** * MCP server status @@ -4469,10 +4216,6 @@ export type McpAuthRemoveData = { } export type McpAuthRemoveErrors = { - /** - * Bad request - */ - 400: BadRequestError /** * Not found */ @@ -4506,7 +4249,7 @@ export type McpAuthStartData = { export type McpAuthStartErrors = { /** - * McpUnsupportedOAuthError | BadRequest + * McpUnsupportedOAuthError */ 400: McpUnsupportedOAuthError /** @@ -4579,7 +4322,7 @@ export type McpAuthAuthenticateData = { export type McpAuthAuthenticateErrors = { /** - * McpUnsupportedOAuthError | BadRequest + * McpUnsupportedOAuthError */ 400: McpUnsupportedOAuthError /** @@ -4611,15 +4354,6 @@ export type McpConnectData = { url: "/mcp/{name}/connect" } -export type McpConnectErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type McpConnectError = McpConnectErrors[keyof McpConnectErrors] - export type McpConnectResponses = { /** * MCP server connected successfully @@ -4641,15 +4375,6 @@ export type McpDisconnectData = { url: "/mcp/{name}/disconnect" } -export type McpDisconnectErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type McpDisconnectError = McpDisconnectErrors[keyof McpDisconnectErrors] - export type McpDisconnectResponses = { /** * MCP server disconnected successfully @@ -4669,15 +4394,6 @@ export type ProjectListData = { url: "/project" } -export type ProjectListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ProjectListError = ProjectListErrors[keyof ProjectListErrors] - export type ProjectListResponses = { /** * List of projects @@ -4697,15 +4413,6 @@ export type ProjectCurrentData = { url: "/project/current" } -export type ProjectCurrentErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ProjectCurrentError = ProjectCurrentErrors[keyof ProjectCurrentErrors] - export type ProjectCurrentResponses = { /** * Current project information @@ -4725,15 +4432,6 @@ export type ProjectInitGitData = { url: "/project/git/init" } -export type ProjectInitGitErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ProjectInitGitError = ProjectInitGitErrors[keyof ProjectInitGitErrors] - export type ProjectInitGitResponses = { /** * Project information after git initialization @@ -4800,15 +4498,6 @@ export type PtyShellsData = { url: "/pty/shells" } -export type PtyShellsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type PtyShellsError = PtyShellsErrors[keyof PtyShellsErrors] - export type PtyShellsResponses = { /** * List of shells @@ -4832,15 +4521,6 @@ export type PtyListData = { url: "/pty" } -export type PtyListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type PtyListError = PtyListErrors[keyof PtyListErrors] - export type PtyListResponses = { /** * List of sessions @@ -4899,10 +4579,6 @@ export type PtyRemoveData = { } export type PtyRemoveErrors = { - /** - * Bad request - */ - 400: BadRequestError /** * Not found */ @@ -4933,10 +4609,6 @@ export type PtyGetData = { } export type PtyGetErrors = { - /** - * Bad request - */ - 400: BadRequestError /** * Not found */ @@ -5003,10 +4675,6 @@ export type PtyConnectTokenData = { } export type PtyConnectTokenErrors = { - /** - * Bad request - */ - 400: BadRequestError /** * Forbidden */ @@ -5041,15 +4709,6 @@ export type QuestionListData = { url: "/question" } -export type QuestionListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type QuestionListError = QuestionListErrors[keyof QuestionListErrors] - export type QuestionListResponses = { /** * List of pending questions @@ -5142,15 +4801,6 @@ export type PermissionListData = { url: "/permission" } -export type PermissionListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type PermissionListError = PermissionListErrors[keyof PermissionListErrors] - export type PermissionListResponses = { /** * List of pending permissions @@ -5207,15 +4857,6 @@ export type ProviderListData = { url: "/provider" } -export type ProviderListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ProviderListError = ProviderListErrors[keyof ProviderListErrors] - export type ProviderListResponses = { /** * List of providers @@ -5241,15 +4882,6 @@ export type ProviderAuthData = { url: "/provider/auth" } -export type ProviderAuthErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ProviderAuthError2 = ProviderAuthErrors[keyof ProviderAuthErrors] - export type ProviderAuthResponses = { /** * Provider auth methods @@ -5351,15 +4983,6 @@ export type SessionListData = { url: "/session" } -export type SessionListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type SessionListError = SessionListErrors[keyof SessionListErrors] - export type SessionListResponses = { /** * List of sessions @@ -5627,15 +5250,6 @@ export type SessionDiffData = { url: "/session/{sessionID}/diff" } -export type SessionDiffErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors] - export type SessionDiffResponses = { /** * Successfully retrieved diff @@ -5823,15 +5437,6 @@ export type SessionForkData = { url: "/session/{sessionID}/fork" } -export type SessionForkErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type SessionForkError = SessionForkErrors[keyof SessionForkErrors] - export type SessionForkResponses = { /** * 200 @@ -6355,15 +5960,6 @@ export type SyncStartData = { url: "/sync/start" } -export type SyncStartErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type SyncStartError = SyncStartErrors[keyof SyncStartErrors] - export type SyncStartResponses = { /** * Workspace sync started @@ -6527,15 +6123,6 @@ export type V2SessionPromptData = { url: "/api/session/{sessionID}/prompt" } -export type V2SessionPromptErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type V2SessionPromptError = V2SessionPromptErrors[keyof V2SessionPromptErrors] - export type V2SessionPromptResponses = { /** * Session.Message @@ -6557,15 +6144,6 @@ export type V2SessionCompactData = { url: "/api/session/{sessionID}/compact" } -export type V2SessionCompactErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type V2SessionCompactError = V2SessionCompactErrors[keyof V2SessionCompactErrors] - export type V2SessionCompactResponses = { /** * @@ -6587,15 +6165,6 @@ export type V2SessionWaitData = { url: "/api/session/{sessionID}/wait" } -export type V2SessionWaitErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type V2SessionWaitError = V2SessionWaitErrors[keyof V2SessionWaitErrors] - export type V2SessionWaitResponses = { /** * @@ -6617,15 +6186,6 @@ export type V2SessionContextData = { url: "/api/session/{sessionID}/context" } -export type V2SessionContextErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type V2SessionContextError = V2SessionContextErrors[keyof V2SessionContextErrors] - export type V2SessionContextResponses = { /** * Success @@ -6705,15 +6265,6 @@ export type TuiOpenHelpData = { url: "/tui/open-help" } -export type TuiOpenHelpErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiOpenHelpError = TuiOpenHelpErrors[keyof TuiOpenHelpErrors] - export type TuiOpenHelpResponses = { /** * Help dialog opened successfully @@ -6733,15 +6284,6 @@ export type TuiOpenSessionsData = { url: "/tui/open-sessions" } -export type TuiOpenSessionsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiOpenSessionsError = TuiOpenSessionsErrors[keyof TuiOpenSessionsErrors] - export type TuiOpenSessionsResponses = { /** * Session dialog opened successfully @@ -6761,15 +6303,6 @@ export type TuiOpenThemesData = { url: "/tui/open-themes" } -export type TuiOpenThemesErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiOpenThemesError = TuiOpenThemesErrors[keyof TuiOpenThemesErrors] - export type TuiOpenThemesResponses = { /** * Theme dialog opened successfully @@ -6789,15 +6322,6 @@ export type TuiOpenModelsData = { url: "/tui/open-models" } -export type TuiOpenModelsErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiOpenModelsError = TuiOpenModelsErrors[keyof TuiOpenModelsErrors] - export type TuiOpenModelsResponses = { /** * Model dialog opened successfully @@ -6817,15 +6341,6 @@ export type TuiSubmitPromptData = { url: "/tui/submit-prompt" } -export type TuiSubmitPromptErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiSubmitPromptError = TuiSubmitPromptErrors[keyof TuiSubmitPromptErrors] - export type TuiSubmitPromptResponses = { /** * Prompt submitted successfully @@ -6845,15 +6360,6 @@ export type TuiClearPromptData = { url: "/tui/clear-prompt" } -export type TuiClearPromptErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiClearPromptError = TuiClearPromptErrors[keyof TuiClearPromptErrors] - export type TuiClearPromptResponses = { /** * Prompt cleared successfully @@ -6908,15 +6414,6 @@ export type TuiShowToastData = { url: "/tui/show-toast" } -export type TuiShowToastErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiShowToastError = TuiShowToastErrors[keyof TuiShowToastErrors] - export type TuiShowToastResponses = { /** * Toast notification shown successfully @@ -7001,15 +6498,6 @@ export type TuiControlNextData = { url: "/tui/control/next" } -export type TuiControlNextErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiControlNextError = TuiControlNextErrors[keyof TuiControlNextErrors] - export type TuiControlNextResponses = { /** * Next TUI request @@ -7032,15 +6520,6 @@ export type TuiControlResponseData = { url: "/tui/control/response" } -export type TuiControlResponseErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type TuiControlResponseError = TuiControlResponseErrors[keyof TuiControlResponseErrors] - export type TuiControlResponseResponses = { /** * Response submitted successfully @@ -7060,16 +6539,6 @@ export type ExperimentalWorkspaceAdapterListData = { url: "/experimental/workspace/adapter" } -export type ExperimentalWorkspaceAdapterListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalWorkspaceAdapterListError = - ExperimentalWorkspaceAdapterListErrors[keyof ExperimentalWorkspaceAdapterListErrors] - export type ExperimentalWorkspaceAdapterListResponses = { /** * Workspace adapters @@ -7094,15 +6563,6 @@ export type ExperimentalWorkspaceListData = { url: "/experimental/workspace" } -export type ExperimentalWorkspaceListErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalWorkspaceListError = ExperimentalWorkspaceListErrors[keyof ExperimentalWorkspaceListErrors] - export type ExperimentalWorkspaceListResponses = { /** * Workspaces @@ -7118,7 +6578,7 @@ export type ExperimentalWorkspaceCreateData = { id?: string type: string branch: string | null - extra: unknown | null + extra?: unknown | null } path?: never query?: { @@ -7158,16 +6618,6 @@ export type ExperimentalWorkspaceStatusData = { url: "/experimental/workspace/status" } -export type ExperimentalWorkspaceStatusErrors = { - /** - * Bad request - */ - 400: BadRequestError -} - -export type ExperimentalWorkspaceStatusError = - ExperimentalWorkspaceStatusErrors[keyof ExperimentalWorkspaceStatusErrors] - export type ExperimentalWorkspaceStatusResponses = { /** * Workspace status @@ -7215,7 +6665,7 @@ export type ExperimentalWorkspaceRemoveResponse = export type ExperimentalWorkspaceWarpData = { body?: { - id: string | null + id: string sessionID: string } path?: never @@ -7258,10 +6708,6 @@ export type PtyConnectData = { } export type PtyConnectErrors = { - /** - * Bad request - */ - 400: BadRequestError /** * Forbidden */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 1a2f1e947..db8889f1a 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -218,16 +218,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get health information about the OpenCode server.", @@ -255,16 +245,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Subscribe to global events from the OpenCode system using server-sent events.", @@ -292,16 +272,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the current global OpenCode configuration settings and preferences.", @@ -374,16 +344,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Clean up and dispose all OpenCode instances, releasing all resources.", @@ -510,16 +470,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get events", @@ -564,16 +514,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the current OpenCode configuration settings and preferences.", @@ -696,16 +636,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all configured AI providers and their default models.", @@ -750,16 +680,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get the active Console org name and the set of provider IDs managed by that Console org.", @@ -837,16 +757,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get the available Console orgs across logged-in accounts, including the current active org.", @@ -1083,16 +993,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "List all sandbox worktrees for the current project.", @@ -1392,16 +1292,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", @@ -1450,16 +1340,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get all available MCP resources from connected servers. Optionally filter by name.", @@ -1576,16 +1456,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Search for text patterns across files in the project using ripgrep.", @@ -1670,16 +1540,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Search for files or directories by name or pattern in the project directory.", @@ -1736,16 +1596,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Search for workspace symbols like functions, classes, and variables using LSP.", @@ -1802,16 +1652,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "List files and directories in a specified path.", @@ -1864,16 +1704,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Read the content of a specified file.", @@ -1922,16 +1752,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get the git status of all files in the project.", @@ -1977,16 +1797,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Clean up and dispose the current OpenCode instance, releasing all resources.", @@ -2031,16 +1841,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the current working directory and related path information for the OpenCode instance.", @@ -2085,16 +1885,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve version control system (VCS) information for the current project, such as git branch.", @@ -2152,16 +1942,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the current git diff for the working tree or against the default branch.", @@ -2210,16 +1990,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all available commands in the OpenCode system.", @@ -2268,16 +2038,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all available AI agents in the OpenCode system.", @@ -2342,16 +2102,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all available skills in the OpenCode system.", @@ -2400,16 +2150,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get LSP server status", @@ -2458,16 +2198,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get formatter status", @@ -2516,16 +2246,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get the status of all Model Context Protocol (MCP) servers.", @@ -2673,7 +2393,7 @@ } }, "400": { - "description": "McpUnsupportedOAuthError | BadRequest", + "description": "McpUnsupportedOAuthError", "content": { "application/json": { "schema": { @@ -2751,16 +2471,6 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "404": { "description": "Not found", "content": { @@ -2912,7 +2622,7 @@ } }, "400": { - "description": "McpUnsupportedOAuthError | BadRequest", + "description": "McpUnsupportedOAuthError", "content": { "application/json": { "schema": { @@ -2983,16 +2693,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Connect an MCP server.", @@ -3045,16 +2745,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Disconnect an MCP server.", @@ -3102,16 +2792,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of projects that have been opened with OpenCode.", @@ -3156,16 +2836,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the currently active project that OpenCode is working with.", @@ -3210,16 +2880,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Create a git repository for the current project and return the refreshed project info.", @@ -3393,16 +3053,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of available shells on the system.", @@ -3451,16 +3101,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.", @@ -3600,16 +3240,6 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "404": { "description": "Not found", "content": { @@ -3763,16 +3393,6 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "404": { "description": "Not found", "content": { @@ -3848,16 +3468,6 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "403": { "description": "Forbidden", "content": { @@ -3925,16 +3535,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get all pending question requests across all sessions.", @@ -4151,16 +3751,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get all pending permission requests across all sessions.", @@ -4322,16 +3912,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all available AI providers, including both available and connected ones.", @@ -4383,16 +3963,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve available authentication methods for all AI providers.", @@ -4666,16 +4236,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get a list of all OpenCode sessions, sorted by most recently updated.", @@ -5292,16 +4852,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get the file changes (diff) that resulted from a specific user message in the session.", @@ -5792,16 +5342,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Create a new session by forking an existing session at a specific message point.", @@ -7128,16 +6668,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Start sync loops for workspaces in the current project that have active sessions.", @@ -7519,16 +7049,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Create a v2 session message and queue it for the agent loop.", @@ -7594,16 +7114,6 @@ "responses": { "204": { "description": "" - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Compact a v2 session conversation.", @@ -7650,16 +7160,6 @@ "responses": { "204": { "description": "" - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Wait for a v2 session agent loop to become idle.", @@ -7716,16 +7216,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the active context messages for a v2 session (all messages after the last compaction).", @@ -7905,16 +7395,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Open the help dialog in the TUI to display user assistance information.", @@ -7960,16 +7440,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Open the session dialog.", @@ -8015,16 +7485,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Open the theme dialog.", @@ -8070,16 +7530,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Open the model dialog.", @@ -8125,16 +7575,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Submit the prompt.", @@ -8180,16 +7620,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Clear the prompt.", @@ -8306,16 +7736,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Show a toast notification in the TUI.", @@ -8555,16 +7975,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Retrieve the next TUI request from the queue for processing.", @@ -8610,16 +8020,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Submit a response to the TUI request queue to complete a pending request.", @@ -8688,16 +8088,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "List all available workspace adapters for the current project.", @@ -8746,16 +8136,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "List all workspaces.", @@ -8843,7 +8223,7 @@ ] } }, - "required": ["type", "branch", "extra"], + "required": ["type", "branch"], "additionalProperties": false } } @@ -8904,16 +8284,6 @@ } } } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } } }, "description": "Get connection status for workspaces in the current project.", @@ -9098,16 +8468,6 @@ } } }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, "403": { "description": "Forbidden", "content": { From 2740d398fa26df560eeb0566226004677c84d0c2 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 5 May 2026 11:37:18 +1000 Subject: [PATCH 08/18] devex: Enable Electron MCP servers with DevTools debug port (#25795) --- packages/desktop-electron/src/main/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index c9f16606a..af7fd4258 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -74,6 +74,7 @@ setupApp() function setupApp() { ensureLoopbackNoProxy() app.commandLine.appendSwitch("proxy-bypass-list", "<-loopback>") + if (!app.isPackaged) app.commandLine.appendSwitch("remote-debugging-port", "9222") if (!app.requestSingleInstanceLock()) { app.quit() From edd480f56be832bd3daa871b5bbb6c124bc10a4e Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 4 May 2026 22:06:33 -0400 Subject: [PATCH 09/18] fix(tui): fix type error for calling workspace.warp (#25801) --- .../src/cli/cmd/tui/component/dialog-workspace-create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index e2af0d63e..ad4063757 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -77,7 +77,7 @@ export async function warpWorkspaceSession(input: { }): Promise { const result = await input.sdk.client.experimental.workspace .warp({ - id: input.workspaceID, + id: input.workspaceID ?? undefined, sessionID: input.sessionID, }) .catch(() => undefined) From f6a3615f59e51dec879a7f8d0cce584b05d4c9e2 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 5 May 2026 10:15:00 +0800 Subject: [PATCH 10/18] fix(console): remove Cloudflare cache config from download fetch (#25804) --- .../app/src/routes/download/[channel]/[platform].ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/console/app/src/routes/download/[channel]/[platform].ts b/packages/console/app/src/routes/download/[channel]/[platform].ts index 4ae8e2465..7a4b5ef65 100644 --- a/packages/console/app/src/routes/download/[channel]/[platform].ts +++ b/packages/console/app/src/routes/download/[channel]/[platform].ts @@ -32,13 +32,6 @@ export async function GET({ params: { platform, channel } }: APIEvent) { const resp = await fetch( `https://github.com/anomalyco/${channel === "stable" ? "opencode" : "opencode-beta"}/releases/latest/download/${assetName}`, - { - cf: { - // in case gh releases has rate limits - cacheTtl: 60 * 5, - cacheEverything: true, - }, - } as any, ) const downloadName = downloadNames[platform] From 0df2bb0f3b29b8b98d80c0bd3b1d5c8aac21098f Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 4 May 2026 22:22:39 -0400 Subject: [PATCH 11/18] docs: restore v2 todo --- specs/v2/todo.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 specs/v2/todo.md diff --git a/specs/v2/todo.md b/specs/v2/todo.md new file mode 100644 index 000000000..3a4b9cf24 --- /dev/null +++ b/specs/v2/todo.md @@ -0,0 +1,59 @@ +# TODO + +ok we need to work towards a launch of v2 so we can get out of this rebuild phase + +## Kill Hono - Kit + +Hono needs to go away so zod can go away. this is almost done + +## New Data Mode - Dax + +This is mostly done. I'm working through modeling subagents, skill invocations +and shell commands. + +## Rework agent loop - Kit? + +I think this needs to be done so we can take advantage of the simpler data +model. It can stop doing all the + +## Rework compaction - Aiden? + +The new agent loop needs to trigger compaction properly + +## Plugin API design - ??? + +We need to figure out how we want server plugins to work and what hooks are useful. + +Some ideas: + +- plugins get immer drafts so bad mutations can be thrown away +- plugins get global "opencode" instance like in that post i showed +- opencode instance has stuff like `opencode.session.prompt()` or + `opencode.tool.register({...})` + +## Rework Config - ??? + +We should do another pass on config to clean up any mistakes we made with it and +simplify as much as possible. Old configs should get auto-converted to new + +## Auth - ??? + +I have a basic auth system that can track any kind of auth, not just providers + +## Model Database - ??? + +I have a basic model service that allows for models to be registered dynamically + +## Provider - ??? + +Providers should register as plugins and autoload based on whatever logic they +want / config. They should register models into model database + +## Event - Kit/James + +I have this v2/event.ts but it needs to be self contained instead of using the +old bus system + +## Everything is hotreloadable - ??? + +Instead of needing to tear down things when something changes every service should emit granular events so services can react to them and reconfigure themselves. Allows frontend to receive these too, eg model.added. also prevents startup from blocking From 39c88f9afb2281ae3df290f4d88acaf2f8e8398b Mon Sep 17 00:00:00 2001 From: Dax Date: Mon, 4 May 2026 22:35:21 -0400 Subject: [PATCH 12/18] Improve v2 session message rendering (#25634) --- packages/core/src/global.ts | 2 + .../src/cli/cmd/tui/context/sync-v2.tsx | 16 +- .../tui/feature-plugins/system/session-v2.tsx | 193 +++++++++----- packages/opencode/src/id/id.ts | 1 + packages/opencode/src/session/processor.ts | 9 +- .../opencode/src/session/projectors-next.ts | 6 +- packages/opencode/src/session/prompt.ts | 9 +- packages/opencode/src/v2/auth.ts | 246 ++++++++++++++++++ packages/opencode/src/v2/model.ts | 192 ++++++++++++++ packages/opencode/src/v2/session-event.ts | 23 +- .../src/v2/session-message-updater.ts | 6 +- packages/opencode/src/v2/session-message.ts | 12 +- packages/opencode/src/v2/session.ts | 76 ++++-- .../test/server/httpapi-session.test.ts | 7 +- .../test/v2/session-message-updater.test.ts | 19 +- specs/v2/session-concepts-gap.md | 131 ---------- specs/v2/todo.md | 4 +- 17 files changed, 677 insertions(+), 275 deletions(-) create mode 100644 packages/opencode/src/v2/auth.ts create mode 100644 packages/opencode/src/v2/model.ts delete mode 100644 specs/v2/session-concepts-gap.md diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index 1acc3f47f..6560d308c 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -71,6 +71,8 @@ export const layer = Layer.effect( Effect.sync(() => Service.of(make())), ) +export const defaultLayer = layer + export const layerWith = (input: Partial) => Layer.effect( Service, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync-v2.tsx b/packages/opencode/src/cli/cmd/tui/context/sync-v2.tsx index 9801f0a2f..d9d23999d 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync-v2.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync-v2.tsx @@ -11,21 +11,21 @@ import { createSimpleContext } from "./helper" import { useSDK } from "./sdk" function activeAssistant(messages: SessionMessage[]) { - const index = messages.findLastIndex((message) => message.type === "assistant" && !message.time.completed) + const index = messages.findIndex((message) => message.type === "assistant" && !message.time.completed) if (index < 0) return const assistant = messages[index] return assistant?.type === "assistant" ? assistant : undefined } function activeCompaction(messages: SessionMessage[]) { - const index = messages.findLastIndex((message) => message.type === "compaction") + const index = messages.findIndex((message) => message.type === "compaction") if (index < 0) return const compaction = messages[index] return compaction?.type === "compaction" ? compaction : undefined } function activeShell(messages: SessionMessage[], callID: string) { - const index = messages.findLastIndex((message) => message.type === "shell" && message.callID === callID) + const index = messages.findIndex((message) => message.type === "shell" && message.callID === callID) if (index < 0) return const shell = messages[index] return shell?.type === "shell" ? shell : undefined @@ -74,7 +74,7 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext( switch (event.type) { case "session.next.prompted": { update(event.properties.sessionID, (draft) => { - draft.push({ + draft.unshift({ id: event.id, type: "user", text: event.properties.prompt.text, @@ -87,7 +87,7 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext( } case "session.next.synthetic": update(event.properties.sessionID, (draft) => { - draft.push({ + draft.unshift({ id: event.id, type: "synthetic", sessionID: event.properties.sessionID, @@ -98,7 +98,7 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext( break case "session.next.shell.started": update(event.properties.sessionID, (draft) => { - draft.push({ + draft.unshift({ id: event.id, type: "shell", callID: event.properties.callID, @@ -120,7 +120,7 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext( update(event.properties.sessionID, (draft) => { const currentAssistant = activeAssistant(draft) if (currentAssistant) currentAssistant.time.completed = event.properties.timestamp - draft.push({ + draft.unshift({ id: event.id, type: "assistant", agent: event.properties.agent, @@ -259,7 +259,7 @@ export const { use: useSyncV2, provider: SyncProviderV2 } = createSimpleContext( break case "session.next.compaction.started": update(event.properties.sessionID, (draft) => { - draft.push({ + draft.unshift({ id: event.id, type: "compaction", reason: event.properties.reason, diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx index 7270a9c3b..2e5cea980 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/system/session-v2.tsx @@ -5,7 +5,7 @@ import { Spinner } from "@tui/component/spinner" import { useTheme } from "@tui/context/theme" import { useLocal } from "@tui/context/local" import { useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" -import type { SyntaxStyle } from "@opentui/core" +import { TextAttributes, type BoxRenderable, type SyntaxStyle } from "@opentui/core" import { Locale } from "@/util/locale" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import path from "path" @@ -44,6 +44,10 @@ function View(props: { api: TuiPluginApi; sessionID: string }) { const messages = createMemo(() => sync.data.messages[props.sessionID] ?? []) const renderedMessages = createMemo(() => messages().toReversed()) const lastAssistant = createMemo(() => renderedMessages().findLast((message) => message.type === "assistant")) + const lastUserCreated = (index: number) => + renderedMessages() + .slice(0, index) + .findLast((message) => message.type === "user")?.time.created createEffect(() => { void sync.session.message.sync(props.sessionID) @@ -83,10 +87,11 @@ function View(props: { api: TuiPluginApi; sessionID: string }) { last={lastAssistant()?.id === message.id} syntax={syntax()} subtleSyntax={subtleSyntax()} + start={lastUserCreated(index())} /> - + <> @@ -146,63 +151,36 @@ function UserMessage(props: { message: SessionMessageUser; index: number }) { - - - } - > - {props.message.text} - - - - - {(file) => ( - - {file.mime} - {file.name ?? file.uri} - - )} - - - {(agent) => ( - - agent - {agent.name} - - )} - - - - {Locale.todayTimeOrDateTime(props.message.time.created)} - - - ) -} - -function SyntheticMessage(props: { message: SessionMessageSynthetic; index: number }) { - const { theme } = useTheme() - return ( - - Synthetic {props.message.text} + + + + {(file) => ( + + {file.mime} + {file.name ?? file.uri} + + )} + + + {(agent) => ( + + agent + {agent.name} + + )} + + + ) } @@ -237,7 +215,7 @@ function ShellMessage(props: { message: SessionMessageShell }) { } function CompactionMessage(props: { message: SessionMessageCompaction }) { - const { theme } = useTheme() + const { theme, syntax } = useTheme() return ( - {props.message.summary} + {(summary) => ( + + + + )} ) @@ -294,12 +284,13 @@ function AssistantMessage(props: { last: boolean syntax: SyntaxStyle subtleSyntax: SyntaxStyle + start?: number }) { const { theme } = useTheme() const local = useLocal() const duration = createMemo(() => { if (!props.message.time.completed) return 0 - return props.message.time.completed - props.message.time.created + return props.message.time.completed - (props.start ?? props.message.time.created) }) const model = createMemo(() => { const variant = props.message.model.variant ? `/${props.message.model.variant}` : "" @@ -361,7 +352,7 @@ function AssistantText(props: { part: SessionMessageAssistantText; syntax: Synta const { theme } = useTheme() return ( - + (props.part.state.status === "error" ? props.part.state.error.message : undefined)) + const complete = createMemo(() => !!props.complete) const denied = createMemo(() => { const message = error() if (!message) return false return ( message.includes("QuestionRejectedError") || message.includes("rejected permission") || + message.includes("specified a rule") || message.includes("user dismissed") ) }) + const fg = createMemo(() => { + if (error()) return theme.error + if (complete()) return theme.textMuted + return theme.text + }) + const attributes = createMemo(() => (denied() ? TextAttributes.STRIKETHROUGH : undefined)) return ( - - - - {props.children} - - - - ~ {props.pending}} when={props.complete}> - {props.icon} {props.children} - - - - - - {error()} - + error() && setHover(true)} + onMouseOut={() => setHover(false)} + onMouseUp={() => { + if (!error()) return + if (renderer.getSelection()?.getSelectedText()) return + setShowError((prev) => !prev) + }} + renderBefore={function () { + const el = this as BoxRenderable + const parent = el.parent + if (!parent) return + const previous = parent.getChildren()[parent.getChildren().indexOf(el) - 1] + if (!previous) { + setMargin(0) + return + } + if (previous.id.startsWith("text")) setMargin(1) + }} + > + + + + + + + + {props.icon} + + + + + ~ + + + + + + + + + + {props.children} + + + + + {props.pending} + + + + + + + {error()} + + + ) } diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index 46c210fa5..6d9a6447a 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -13,6 +13,7 @@ const prefixes = { tool: "tool", workspace: "wrk", entry: "ent", + account: "act", } as const export function schema(prefix: keyof typeof prefixes) { diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index cf1a7e0ae..f22da9292 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -22,6 +22,7 @@ import * as Log from "@opencode-ai/core/util/log" import { isRecord } from "@/util/record" import { EventV2 } from "@/v2/event" import { SessionEvent } from "@/v2/session-event" +import { Modelv2 } from "@/v2/model" import * as DateTime from "effect/DateTime" const DOOM_LOOP_THRESHOLD = 3 @@ -432,9 +433,9 @@ export const layer: Layer.Layer< sessionID: ctx.sessionID, agent: input.assistantMessage.agent, model: { - id: ctx.model.id, - providerID: ctx.model.providerID, - variant: input.assistantMessage.variant, + id: Modelv2.ID.make(ctx.model.id), + providerID: Modelv2.ProviderID.make(ctx.model.providerID), + variant: Modelv2.VariantID.make(input.assistantMessage.variant ?? "default"), }, snapshot: ctx.snapshot, timestamp: DateTime.makeUnsafe(Date.now()), @@ -655,7 +656,7 @@ export const layer: Layer.Layer< EventV2.run(SessionEvent.Step.Failed.Sync, { sessionID: ctx.sessionID, error: { - type: error.name, + type: "unknown", message: errorMessage(e), }, timestamp: DateTime.makeUnsafe(Date.now()), diff --git a/packages/opencode/src/session/projectors-next.ts b/packages/opencode/src/session/projectors-next.ts index 88f73acf1..93298170c 100644 --- a/packages/opencode/src/session/projectors-next.ts +++ b/packages/opencode/src/session/projectors-next.ts @@ -132,11 +132,7 @@ export default [ SyncEvent.project(SessionEvent.ModelSwitched.Sync, (db, data, event) => { db.update(SessionTable) .set({ - model: { - id: data.id, - providerID: data.providerID, - variant: data.variant, - }, + model: data.model, time_updated: DateTime.toEpochMillis(data.timestamp), }) .where(eq(SessionTable.id, data.sessionID)) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0590fc382..e1fa81abf 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -56,6 +56,7 @@ import { SessionRunState } from "./run-state" import { EffectBridge } from "@/effect/bridge" import { EventV2 } from "@/v2/event" import { SessionEvent } from "@/v2/session-event" +import { Modelv2 } from "@/v2/model" import { AgentAttachment, FileAttachment, Source } from "@/v2/session-prompt" import * as DateTime from "effect/DateTime" import { eq } from "@/storage/db" @@ -978,9 +979,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the EventV2.run(SessionEvent.ModelSwitched.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(info.time.created), - id: info.model.modelID, - providerID: info.model.providerID, - variant: info.model.variant, + model: { + id: Modelv2.ID.make(info.model.modelID), + providerID: Modelv2.ProviderID.make(info.model.providerID), + variant: Modelv2.VariantID.make(info.model.variant ?? "default"), + }, }) } diff --git a/packages/opencode/src/v2/auth.ts b/packages/opencode/src/v2/auth.ts new file mode 100644 index 000000000..1cc443974 --- /dev/null +++ b/packages/opencode/src/v2/auth.ts @@ -0,0 +1,246 @@ +import path from "path" +import { Effect, Layer, Option, Schema, Context, SynchronizedRef } from "effect" +import { Identifier } from "@opencode-ai/core/util/identifier" +import { NonNegativeInt, withStatics } from "@/util/schema" +import { Global } from "@opencode-ai/core/global" +import { AppFileSystem } from "@opencode-ai/core/filesystem" + +export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" + +const AccountID = Schema.String.pipe( + Schema.brand("AccountID"), + withStatics((schema) => ({ create: () => schema.make("acc_" + Identifier.ascending()) })), +) +export type AccountID = typeof AccountID.Type + +export const ServiceID = Schema.String.pipe(Schema.brand("ServiceID")) +export type ServiceID = typeof ServiceID.Type + +export class OAuthCredential extends Schema.Class("AuthV2.OAuthCredential")({ + type: Schema.Literal("oauth"), + refresh: Schema.String, + access: Schema.String, + expires: NonNegativeInt, +}) {} + +export class ApiKeyCredential extends Schema.Class("AuthV2.ApiKeyCredential")({ + type: Schema.Literal("api"), + key: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}) {} + +export const Credential = Schema.Union([OAuthCredential, ApiKeyCredential]) + .pipe(Schema.toTaggedUnion("type")) + .annotate({ + identifier: "AuthV2.Credential", + }) +export type Credential = Schema.Schema.Type + +export class Account extends Schema.Class("AuthV2.Account")({ + id: AccountID, + serviceID: ServiceID, + description: Schema.String, + credential: Credential, +}) {} + +export class AuthFileWriteError extends Schema.TaggedErrorClass()("AuthV2.FileWriteError", { + operation: Schema.Union([Schema.Literal("migrate"), Schema.Literal("write")]), + cause: Schema.Defect, +}) {} + +export type AuthError = AuthFileWriteError + +interface Writable { + version: 2 + accounts: Record + active: Record +} + +const decodeV1 = Schema.decodeUnknownOption(Schema.Record(Schema.String, Credential)) + +function migrate(old: Record): Writable { + const accounts: Record = {} + const active: Record = {} + for (const [serviceID, value] of Object.entries(old)) { + const decoded = Option.getOrElse(decodeV1({ [serviceID]: value }), () => ({})) + const parsed = (decoded as Record)[serviceID] + if (!parsed) continue + const id = Identifier.ascending() + const accountID = AccountID.make(id) + const brandedServiceID = ServiceID.make(serviceID) + accounts[id] = new Account({ + id: accountID, + serviceID: brandedServiceID, + description: "default", + credential: parsed, + }) + active[brandedServiceID] = accountID + } + return { version: 2, accounts, active } +} + +export interface Interface { + readonly get: (accountID: AccountID) => Effect.Effect + readonly all: () => Effect.Effect + readonly create: (input: { + serviceID: ServiceID + credential: Credential + description?: string + active?: boolean + }) => Effect.Effect + readonly update: ( + accountID: AccountID, + updates: Partial>, + ) => Effect.Effect + readonly remove: (accountID: AccountID) => Effect.Effect + readonly activate: (accountID: AccountID) => Effect.Effect + readonly active: (serviceID: ServiceID) => Effect.Effect + readonly forService: (serviceID: ServiceID) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/v2/Auth") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const fsys = yield* AppFileSystem.Service + const global = yield* Global.Service + const file = path.join(global.data, "auth-v2.json") + + const load: () => Effect.Effect = Effect.fnUntraced(function* () { + if (process.env.OPENCODE_AUTH_CONTENT) { + try { + return JSON.parse(process.env.OPENCODE_AUTH_CONTENT) + } catch {} + } + + const raw = yield* fsys.readJson(file).pipe(Effect.orElseSucceed(() => null)) + + if (!raw || typeof raw !== "object") return { version: 2, accounts: {}, active: {} } + + if ("version" in raw && raw.version === 2) return raw as Writable + + const migrated = migrate(raw as Record) + yield* fsys + .writeJson(file, migrated, 0o600) + .pipe(Effect.mapError((cause) => new AuthFileWriteError({ operation: "migrate", cause }))) + return migrated + }) + + const write = (data: Writable) => + fsys + .writeJson(file, data, 0o600) + .pipe(Effect.mapError((cause) => new AuthFileWriteError({ operation: "write", cause }))) + + const state = SynchronizedRef.makeUnsafe(yield* load()) + + const result: Interface = { + get: Effect.fn("AuthV2.get")(function* (accountID) { + return (yield* SynchronizedRef.get(state)).accounts[accountID] + }), + + all: Effect.fn("AuthV2.all")(function* () { + return Object.values((yield* SynchronizedRef.get(state)).accounts) + }), + + active: Effect.fn("AuthV2.active")(function* (serviceID) { + const data = yield* SynchronizedRef.get(state) + return ( + data.accounts[data.active[serviceID]] ?? Object.values(data.accounts).find((a) => a.serviceID === serviceID) + ) + }), + + forService: Effect.fn("AuthV2.list")(function* (serviceID) { + return Object.values((yield* SynchronizedRef.get(state)).accounts).filter((a) => a.serviceID === serviceID) + }), + + create: Effect.fn("AuthV2.add")(function* (input) { + return yield* SynchronizedRef.modifyEffect( + state, + Effect.fnUntraced(function* (data) { + const account = new Account({ + id: AccountID.make(Identifier.ascending()), + serviceID: input.serviceID, + description: input.description ?? "default", + credential: input.credential, + }) + const next = { + ...data, + accounts: { ...data.accounts, [account.id]: account }, + active: + (input.active ?? Object.values(data.accounts).every((a) => a.serviceID !== input.serviceID)) + ? { ...data.active, [input.serviceID]: account.id } + : data.active, + } + + yield* write(next) + return [account, next] as const + }), + ) + }), + + update: Effect.fn("AuthV2.update")(function* (accountID, updates) { + yield* SynchronizedRef.modifyEffect( + state, + Effect.fnUntraced(function* (data) { + const existing = data.accounts[accountID] + if (!existing) return [undefined, data] as const + + const next = { + ...data, + accounts: { + ...data.accounts, + [accountID]: new Account({ + id: accountID, + serviceID: existing.serviceID, + description: updates.description ?? existing.description, + credential: updates.credential ?? existing.credential, + }), + }, + } + + yield* write(next) + return [undefined, next] as const + }), + ) + }), + + remove: Effect.fn("AuthV2.remove")(function* (accountID) { + yield* SynchronizedRef.modifyEffect( + state, + Effect.fnUntraced(function* (data) { + const accounts = { ...data.accounts } + const active = { ...data.active } + if (accounts[accountID] && active[accounts[accountID].serviceID] === accountID) + delete active[accounts[accountID].serviceID] + delete accounts[accountID] + + const next = { ...data, accounts, active } + yield* write(next) + return [undefined, next] as const + }), + ) + }), + + activate: Effect.fn("AuthV2.activate")(function* (accountID) { + yield* SynchronizedRef.modifyEffect( + state, + Effect.fnUntraced(function* (data) { + const account = data.accounts[accountID] + if (!account) return [undefined, data] as const + + const next = { ...data, active: { ...data.active, [account.serviceID]: accountID } } + yield* write(next) + return [undefined, next] as const + }), + ) + }), + } + + return Service.of(result) + }), +) + +export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer), Layer.provide(Global.defaultLayer)) + +export * as AuthV2 from "./auth" diff --git a/packages/opencode/src/v2/model.ts b/packages/opencode/src/v2/model.ts new file mode 100644 index 000000000..db66199a5 --- /dev/null +++ b/packages/opencode/src/v2/model.ts @@ -0,0 +1,192 @@ +import { withStatics } from "@/util/schema" +import { Array, Context, Effect, HashMap, Layer, Option, Order, pipe, Schema } from "effect" +import { DateTimeUtcFromMillis } from "effect/Schema" + +export const ID = Schema.String.pipe(Schema.brand("Model.ID")) +export type ID = typeof ID.Type + +export const ProviderID = Schema.String.pipe( + Schema.brand("Model.ProviderID"), + withStatics((schema) => ({ + // Well-known providers + opencode: schema.make("opencode"), + anthropic: schema.make("anthropic"), + openai: schema.make("openai"), + google: schema.make("google"), + googleVertex: schema.make("google-vertex"), + githubCopilot: schema.make("github-copilot"), + amazonBedrock: schema.make("amazon-bedrock"), + azure: schema.make("azure"), + openrouter: schema.make("openrouter"), + mistral: schema.make("mistral"), + gitlab: schema.make("gitlab"), + })), +) +export type ProviderID = typeof ProviderID.Type + +export const VariantID = Schema.String.pipe(Schema.brand("VariantID")) +export type VariantID = typeof VariantID.Type + +// Grouping of models, eg claude opus, claude sonnet +export const Family = Schema.String.pipe(Schema.brand("Family")) +export type Family = typeof Family.Type + +const OpenAIResponses = Schema.Struct({ + type: Schema.Literal("openai/responses"), + url: Schema.String, + websocket: Schema.optional(Schema.Boolean), +}) + +const OpenAICompletions = Schema.Struct({ + type: Schema.Literal("openai/completions"), + url: Schema.String, + reasoning: Schema.Union([ + Schema.Struct({ + type: Schema.Literal("reasoning_content"), + }), + Schema.Struct({ + type: Schema.Literal("reasoning_details"), + }), + ]).pipe(Schema.optional), +}) +export type OpenAICompletions = typeof OpenAICompletions.Type + +const AnthropicMessages = Schema.Struct({ + type: Schema.Literal("anthropic/messages"), + url: Schema.String, +}) + +export const Endpoint = Schema.Union([OpenAIResponses, OpenAICompletions, AnthropicMessages]).pipe( + Schema.toTaggedUnion("type"), +) +export type Endpoint = typeof Endpoint.Type + +export const Capabilities = Schema.Struct({ + tools: Schema.Boolean, + // mime patterns, image, audio, video/*, text/* + input: Schema.String.pipe(Schema.Array), + output: Schema.String.pipe(Schema.Array), +}) +export type Capabilities = typeof Capabilities.Type + +export const Options = Schema.Struct({ + headers: Schema.Record(Schema.String, Schema.String), + body: Schema.Record(Schema.String, Schema.Any), +}) +export type Options = typeof Options.Type + +export const Cost = Schema.Struct({ + tier: Schema.Struct({ + type: Schema.Literal("context"), + size: Schema.Int, + }).pipe(Schema.optional), + input: Schema.Finite, + output: Schema.Finite, + cache: Schema.Struct({ + read: Schema.Finite, + write: Schema.Finite, + }), +}) + +export const Ref = Schema.Struct({ + id: ID, + providerID: ProviderID, + variant: VariantID, +}) +export type Ref = typeof Ref.Type + +export class Info extends Schema.Class("Model.Info")({ + id: ID, + providerID: ProviderID, + family: Family.pipe(Schema.optional), + name: Schema.String, + endpoint: Endpoint, + capabilities: Capabilities, + options: Schema.Struct({ + ...Options.fields, + variant: Schema.String.pipe(Schema.optional), + }), + variants: Schema.Struct({ + id: VariantID, + ...Options.fields, + }).pipe(Schema.Array), + time: Schema.Struct({ + released: DateTimeUtcFromMillis, + }), + cost: Cost.pipe(Schema.Array), + status: Schema.Literals(["alpha", "beta", "deprecated", "active"]), + limit: Schema.Struct({ + context: Schema.Int, + input: Schema.Int.pipe(Schema.optional), + output: Schema.Int, + }), +}) {} + +export function parse(input: string): { providerID: ProviderID; modelID: ID } { + const [providerID, ...modelID] = input.split("/") + return { + providerID: ProviderID.make(providerID), + modelID: ID.make(modelID.join("/")), + } +} + +export interface Interface { + readonly get: (providerID: ProviderID, modelID: ID) => Effect.Effect> + readonly add: (model: Info) => Effect.Effect + readonly remove: (providerID: ProviderID, modelID: ID) => Effect.Effect + readonly all: () => Effect.Effect + readonly default: () => Effect.Effect> + readonly small: (provider: ProviderID) => Effect.Effect> +} + +export class Service extends Context.Service()("@opencode/v2/Model") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + let models = HashMap.empty() + + function key(providerID: ProviderID, modelID: ID) { + return `${providerID}/${modelID}` + } + + const result: Interface = { + get: Effect.fn("V2Model.get")(function* (providerID, modelID) { + return HashMap.get(models, key(providerID, modelID)) + }), + + add: Effect.fn("V2Model.add")(function* (model) { + models = HashMap.set(models, key(model.providerID, model.id), model) + }), + + remove: Effect.fn("V2Model.remove")(function* (providerID, modelID) { + models = HashMap.remove(models, key(providerID, modelID)) + }), + + all: Effect.fn("V2Model.all")(function* () { + return pipe( + models, + HashMap.toValues, + Array.sortWith((item) => item.time.released.epochMilliseconds, Order.flip(Order.Number)), + ) + }), + + default: Effect.fn("V2Model.default")(function* () { + const all = yield* result.all() + return Option.fromUndefinedOr(all[0]) + }), + + small: Effect.fn("V2Model.small")(function* (providerID) { + const all = yield* result.all() + const match = all.find((model) => model.providerID === providerID && model.id.toLowerCase().includes("small")) + return Option.fromUndefinedOr(match) + }), + } + + return Service.of(result) + }), +) + +export const defaultLayer = layer + +export * as Modelv2 from "./model" diff --git a/packages/opencode/src/v2/session-event.ts b/packages/opencode/src/v2/session-event.ts index 47938dcbe..7c768bd55 100644 --- a/packages/opencode/src/v2/session-event.ts +++ b/packages/opencode/src/v2/session-event.ts @@ -5,8 +5,8 @@ import { FileAttachment, Prompt } from "./session-prompt" import { Schema } from "effect" export { FileAttachment } import { ToolOutput } from "./tool-output" -import { ModelID, ProviderID } from "@/provider/schema" import { V2Schema } from "./schema" +import { Modelv2 } from "./model" export const Source = Schema.Struct({ start: NonNegativeInt, @@ -22,10 +22,13 @@ const Base = { sessionID: SessionID, } -const Error = Schema.Struct({ - type: Schema.String, +export const UnknownError = Schema.Struct({ + type: Schema.Literal("unknown"), message: Schema.String, +}).annotate({ + identifier: "Session.Error.Unknown", }) +export type UnknownError = Schema.Schema.Type export const AgentSwitched = EventV2.define({ type: "session.next.agent.switched", @@ -44,9 +47,7 @@ export const ModelSwitched = EventV2.define({ version: 1, schema: { ...Base, - id: ModelID, - providerID: ProviderID, - variant: Schema.String.pipe(Schema.optional), + model: Modelv2.Ref, }, }) export type ModelSwitched = Schema.Schema.Type @@ -103,11 +104,7 @@ export namespace Step { schema: { ...Base, agent: Schema.String, - model: Schema.Struct({ - id: Schema.String, - providerID: Schema.String, - variant: Schema.String.pipe(Schema.optional), - }), + model: Modelv2.Ref, snapshot: Schema.String.pipe(Schema.optional), }, }) @@ -139,7 +136,7 @@ export namespace Step { aggregate: "sessionID", schema: { ...Base, - error: Error, + error: UnknownError, }, }) export type Failed = Schema.Schema.Type @@ -296,7 +293,7 @@ export namespace Tool { schema: { ...Base, callID: Schema.String, - error: Error, + error: UnknownError, provider: Schema.Struct({ executed: Schema.Boolean, metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), diff --git a/packages/opencode/src/v2/session-message-updater.ts b/packages/opencode/src/v2/session-message-updater.ts index d5d5aac7b..80ecb1011 100644 --- a/packages/opencode/src/v2/session-message-updater.ts +++ b/packages/opencode/src/v2/session-message-updater.ts @@ -109,11 +109,7 @@ export function update(adapter: Adapter, event: SessionEvent.Eve id: event.id, type: "model-switched", metadata: event.metadata, - model: { - id: event.data.id, - providerID: event.data.providerID, - variant: event.data.variant, - }, + model: event.data.model, time: { created: event.data.timestamp }, }), ) diff --git a/packages/opencode/src/v2/session-message.ts b/packages/opencode/src/v2/session-message.ts index 94f6b1cac..024e28c45 100644 --- a/packages/opencode/src/v2/session-message.ts +++ b/packages/opencode/src/v2/session-message.ts @@ -4,6 +4,7 @@ import { SessionEvent } from "./session-event" import { EventV2 } from "./event" import { ToolOutput } from "./tool-output" import { V2Schema } from "./schema" +import { Modelv2 } from "./model" export const ID = EventV2.ID export type ID = Schema.Schema.Type @@ -25,11 +26,7 @@ export class AgentSwitched extends Schema.Class("Session.Message. export class ModelSwitched extends Schema.Class("Session.Message.ModelSwitched")({ ...Base, type: Schema.Literal("model-switched"), - model: Schema.Struct({ - id: SessionEvent.ModelSwitched.fields.data.fields.id, - providerID: SessionEvent.ModelSwitched.fields.data.fields.providerID, - variant: SessionEvent.ModelSwitched.fields.data.fields.variant, - }), + model: Modelv2.Ref, }) {} export class User extends Schema.Class("Session.Message.User")({ @@ -87,10 +84,7 @@ export class ToolStateError extends Schema.Class("Session.Messag input: Schema.Record(Schema.String, Schema.Unknown), content: ToolOutput.Content.pipe(Schema.Array), structured: ToolOutput.Structured, - error: Schema.Struct({ - type: Schema.String, - message: Schema.String, - }), + error: SessionEvent.UnknownError, }) {} export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).pipe( diff --git a/packages/opencode/src/v2/session.ts b/packages/opencode/src/v2/session.ts index 1f4cbcf1e..bb86f039b 100644 --- a/packages/opencode/src/v2/session.ts +++ b/packages/opencode/src/v2/session.ts @@ -3,17 +3,17 @@ import { SessionID } from "@/session/schema" import { WorkspaceID } from "@/control-plane/schema" import { and, asc, desc, eq, gt, gte, isNull, like, lt, or, type SQL } from "@/storage/db" import * as Database from "@/storage/db" -import { Context, DateTime, Effect, Layer, Schema } from "effect" +import { Context, DateTime, Effect, Layer, Option, Schema } from "effect" import { SessionMessage } from "./session-message" import type { Prompt } from "./session-prompt" import { EventV2 } from "./event" import { ProjectID } from "@/project/schema" -import { ModelID, ProviderID } from "@/provider/schema" import { SessionEvent } from "./session-event" import { V2Schema } from "./schema" import { optionalOmitUndefined } from "@/util/schema" +import { Modelv2 } from "./model" -export const Delivery = Schema.Union([Schema.Literal("immediate"), Schema.Literal("deferred")]).annotate({ +export const Delivery = Schema.Literals(["immediate", "deferred"]).annotate({ identifier: "Session.Delivery", }) export type Delivery = Schema.Schema.Type @@ -27,11 +27,7 @@ export class Info extends Schema.Class("Session.Info")({ workspaceID: optionalOmitUndefined(WorkspaceID), path: optionalOmitUndefined(Schema.String), agent: optionalOmitUndefined(Schema.String), - model: Schema.Struct({ - id: ModelID, - providerID: ProviderID, - variant: optionalOmitUndefined(Schema.String), - }).pipe(optionalOmitUndefined), + model: Modelv2.Ref.pipe(optionalOmitUndefined), time: Schema.Struct({ created: V2Schema.DateTimeUtcFromMillis, updated: V2Schema.DateTimeUtcFromMillis, @@ -53,7 +49,18 @@ export class Info extends Schema.Class("Session.Info")({ */ }) {} +export class NotFoundError extends Schema.TaggedErrorClass()("Session.NotFoundError", { + sessionID: SessionID, +}) {} + export interface Interface { + readonly create: (input?: { + agent?: string + model?: Modelv2.Ref + parentID?: SessionID + workspaceID?: WorkspaceID + }) => Effect.Effect + readonly get: (sessionID: SessionID) => Effect.Effect readonly list: (input: { limit?: number order?: "asc" | "desc" @@ -88,13 +95,15 @@ export interface Interface { }) => Effect.Effect readonly shell: (input: { id?: EventV2.ID; sessionID: SessionID; command: string }) => Effect.Effect readonly skill: (input: { id?: EventV2.ID; sessionID: SessionID; skill: string }) => Effect.Effect + readonly subagent: (input: { + id?: EventV2.ID + parentID: SessionID + prompt: Prompt + agent: string + model?: Modelv2.Ref + }) => Effect.Effect readonly switchAgent: (input: { sessionID: SessionID; agent: string }) => Effect.Effect - readonly switchModel: (input: { - sessionID: SessionID - id: ModelID - providerID: ProviderID - variant?: string - }) => Effect.Effect + readonly switchModel: (input: { sessionID: SessionID; model: Modelv2.Ref }) => Effect.Effect readonly compact: (sessionID: SessionID) => Effect.Effect readonly wait: (sessionID: SessionID) => Effect.Effect } @@ -120,9 +129,9 @@ export const layer = Layer.effect( agent: row.agent ?? undefined, model: row.model ? { - id: ModelID.make(row.model.id), - providerID: ProviderID.make(row.model.providerID), - variant: row.model.variant, + id: Modelv2.ID.make(row.model.id), + providerID: Modelv2.ProviderID.make(row.model.providerID), + variant: Modelv2.VariantID.make(row.model.variant ?? "default"), } : undefined, time: { @@ -134,6 +143,14 @@ export const layer = Layer.effect( } const result: Interface = { + create: Effect.fn("V2Session.create")(function* (_input) { + return {} as any + }), + get: Effect.fn("V2Session.get")(function* (sessionID) { + const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, sessionID)).get()) + if (!row) return yield* new NotFoundError({ sessionID }) + return fromRow(row) + }), list: Effect.fn("V2Session.list")(function* (input) { const direction = input.cursor?.direction ?? "next" let order = input.order ?? "desc" @@ -262,11 +279,30 @@ export const layer = Layer.effect( EventV2.run(SessionEvent.ModelSwitched.Sync, { sessionID: input.sessionID, timestamp: DateTime.makeUnsafe(Date.now()), - id: input.id, - providerID: input.providerID, - variant: input.variant, + model: input.model, }) }), + subagent: Effect.fn("V2Session.subagent")(function* (input) { + const parent = yield* result.get(input.parentID) + const session = yield* result.create({ + agent: input.agent, + model: input.model, + parentID: input.parentID, + workspaceID: parent.workspaceID, + }) + yield* result.prompt({ + prompt: input.prompt, + sessionID: session.id, + }) + yield* Effect.gen(function* () { + yield* result.wait(session.id) + const messages = yield* result.messages({ sessionID: session.id, order: "desc" }) + const assistant = messages.find((msg) => msg.type === "assistant") + if (!assistant) return + const text = assistant.content.findLast((part) => part.type === "text") + if (!text) return + }).pipe(Effect.forkChild()) + }), compact: Effect.fn("V2Session.compact")(function* (_sessionID) {}), wait: Effect.fn("V2Session.wait")(function* (_sessionID) {}), } diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index c9a0b53bb..34cecd80d 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -19,6 +19,7 @@ import { MessageV2 } from "../../src/session/message-v2" import { Database } from "@/storage/db" import { SessionMessageTable, SessionTable } from "@/session/session.sql" import { SessionMessage } from "../../src/v2/session-message" +import { Modelv2 } from "../../src/v2/model" import * as DateTime from "effect/DateTime" import * as Log from "@opencode-ai/core/util/log" import { eq } from "drizzle-orm" @@ -214,7 +215,11 @@ describe("session HttpApi", () => { id: SessionMessage.ID.create(), type: "assistant", agent: "build", - model: { id: "model", providerID: "provider" }, + model: { + id: Modelv2.ID.make("model"), + providerID: Modelv2.ProviderID.make("provider"), + variant: Modelv2.VariantID.make("default"), + }, time: { created: DateTime.makeUnsafe(1) }, content: [], }) diff --git a/packages/opencode/test/v2/session-message-updater.test.ts b/packages/opencode/test/v2/session-message-updater.test.ts index 128177167..44ac031ed 100644 --- a/packages/opencode/test/v2/session-message-updater.test.ts +++ b/packages/opencode/test/v2/session-message-updater.test.ts @@ -2,6 +2,7 @@ import { expect, test } from "bun:test" import * as DateTime from "effect/DateTime" import { SessionID } from "../../src/session/schema" import { EventV2 } from "../../src/v2/event" +import { Modelv2 } from "../../src/v2/model" import { SessionEvent } from "../../src/v2/session-event" import { SessionMessageUpdater } from "../../src/v2/session-message-updater" @@ -16,7 +17,11 @@ test("step snapshots carry over to assistant messages", () => { sessionID, timestamp: DateTime.makeUnsafe(1), agent: "build", - model: { id: "model", providerID: "provider" }, + model: { + id: Modelv2.ID.make("model"), + providerID: Modelv2.ProviderID.make("provider"), + variant: Modelv2.VariantID.make("default"), + }, snapshot: "before", }, } satisfies SessionEvent.Event) @@ -56,7 +61,11 @@ test("text ended populates assistant text content", () => { sessionID, timestamp: DateTime.makeUnsafe(1), agent: "build", - model: { id: "model", providerID: "provider" }, + model: { + id: Modelv2.ID.make("model"), + providerID: Modelv2.ProviderID.make("provider"), + variant: Modelv2.VariantID.make("default"), + }, }, } satisfies SessionEvent.Event) @@ -96,7 +105,11 @@ test("tool completion stores completed timestamp", () => { sessionID, timestamp: DateTime.makeUnsafe(1), agent: "build", - model: { id: "model", providerID: "provider" }, + model: { + id: Modelv2.ID.make("model"), + providerID: Modelv2.ProviderID.make("provider"), + variant: Modelv2.VariantID.make("default"), + }, }, } satisfies SessionEvent.Event) diff --git a/specs/v2/session-concepts-gap.md b/specs/v2/session-concepts-gap.md deleted file mode 100644 index 20d84c8f4..000000000 --- a/specs/v2/session-concepts-gap.md +++ /dev/null @@ -1,131 +0,0 @@ -# Session V2 Concept Gaps - -Compared with `packages/opencode/src/session/message-v2.ts` and `packages/opencode/src/session/processor.ts`, `packages/opencode/src/v2` currently captures the rough event stream for prompts, assistant steps, text, reasoning, tools, retries, and compaction, but it does not yet capture several persisted-message and processor concepts. - -## Message Metadata - -- User messages are missing selected `agent`, `model`, `system`, enabled `tools`, output `format`, and summary metadata. -- Assistant messages are missing `parentID`, `agent`, `providerID`, `modelID`, `variant`, `path.cwd`, `path.root`, deprecated `mode`, `summary`, `structured`, `finish`, and typed `error`. - -## Output Format - -- Text output format. -- JSON-schema output format. -- Structured-output retry count. -- Structured assistant result payload. -- Structured-output error classification. - -## Errors - -- Aborted error. -- Provider auth error. -- API error with status, retryability, headers, body, and metadata. -- Context-overflow error. -- Output-length error. -- Unknown error. -- V2 mostly reduces assistant errors to strings, except retry errors. - -## Part Identity - -- V1 has stable `MessageID`, `PartID`, `sessionID`, and `messageID` on every part. -- V2 assistant content does not preserve stable per-content IDs. -- Stable content IDs matter for deltas, updates, removals, sync events, and UI reconciliation. - -## Part Timing And Metadata - -- V1 text, reasoning, and tool states carry timing and provider metadata. -- V2 assistant text and reasoning content only store text. -- V2 events include metadata, but `SessionEntry` currently drops most provider metadata. - -## Snapshots And Patches - -- Snapshot parts. -- Patch parts. -- Step-start snapshot references. -- Step-finish snapshot references. -- Processor behavior that tracks a snapshot before the stream and emits patches after step finish or cleanup. - -## Step Boundaries - -- V1 stores `step-start` and `step-finish` as first-class parts. -- V2 has `step.started` and `step.ended` events, but the assistant entry only stores aggregate cost and tokens. -- V2 does not preserve step boundary parts, finish reason, or snapshot details in the entry model. - -## Compaction - -- V1 compaction parts have `auto`, `overflow`, and `tail_start_id`. -- V2 compacted events have `auto` and optional `overflow`, but no retained-tail marker. -- V1 also has history filtering semantics around completed summary messages and retained tails. - -## Files And Sources - -- V1 file parts have `mime`, `filename`, `url`, and typed source information. -- V1 source variants include file, symbol, and resource sources. -- Symbol sources include LSP range, name, and kind. -- Resource sources include client name and URI. -- V2 file attachments have `uri`, `mime`, `name`, `description`, and a generic text source, but lose source type, LSP metadata, and resource metadata. - -## Agents And Subtasks - -- Agent parts. -- Subtask parts. -- Subtask prompt, description, agent, model, and command. -- V2 has agent attachments on prompts, but no assistant/session content equivalent for subtask execution. - -## Text Flags - -- Synthetic text flag. -- Ignored text flag. -- V2 has a separate synthetic entry, but no ignored text concept. - -## Tool Calls - -- V1 pending tool state stores parsed input and raw input text separately. -- V2 pending tool state stores a string input but does not preserve a separate raw field. -- V1 completed tool state has `time.start`, `time.end`, and optional `time.compacted`. -- V2 tool time has `created`, `ran`, `completed`, and `pruned`, but the stepper currently does not set `completed` or `pruned`. -- V1 error tool state has `time.start` and `time.end`. -- V1 supports interrupted tool errors with `metadata.interrupted` and preserved partial output. -- V1 tracks provider execution and provider call metadata. -- V2 events include provider info, but `SessionEntryStepper` drops it from entries. -- V1 has tool-output compaction and truncation behavior via `time.compacted`. - -## Media Handling - -- V1 models tool attachments as file parts and has provider-specific handling for media in tool results. -- V1 can strip media, inject synthetic user messages for unsupported providers, and uses a synthetic attachment prompt. -- V2 has attachments but not these model-message conversion semantics. - -## Retries - -- V1 stores retries as independently addressable retry parts. -- V2 stores retries as an assistant aggregate. -- V2 captures some retry information, but not the independent part identity/update model. - -## Processor Control Flow - -- Session status transitions: busy, retry, and idle. -- Retry policy integration. -- Context-overflow-driven compaction. -- Abort and interrupt handling. -- Permission-denied blocking. -- Doom-loop detection. -- Plugin hook for `experimental.text.complete`. -- Background summary generation after steps. -- Cleanup semantics for open text, reasoning, and tool calls. - -## Sync And Bus Events - -- Message updated. -- Message removed. -- Message part updated. -- Message part delta. -- Message part removed. -- V2 has domain events, but not the sync/bus event model for persisted message and part updates/removals. - -## History Retrieval - -- Cursor encoding and decoding. -- Paged message retrieval. -- Reverse streaming through history. -- Compaction-aware history filtering. diff --git a/specs/v2/todo.md b/specs/v2/todo.md index 3a4b9cf24..77c650e55 100644 --- a/specs/v2/todo.md +++ b/specs/v2/todo.md @@ -20,7 +20,7 @@ model. It can stop doing all the The new agent loop needs to trigger compaction properly -## Plugin API design - ??? +## Plugin API design - James? We need to figure out how we want server plugins to work and what hooks are useful. @@ -49,7 +49,7 @@ I have a basic model service that allows for models to be registered dynamically Providers should register as plugins and autoload based on whatever logic they want / config. They should register models into model database -## Event - Kit/James +## Event - Kit I have this v2/event.ts but it needs to be self contained instead of using the old bus system From 75d141b574b94e304c5222daecd4aa68bb9df1e8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Mon, 4 May 2026 22:36:06 -0400 Subject: [PATCH 13/18] fix(session): cancel subtask child sessions (#25798) --- packages/opencode/src/session/prompt.ts | 4 +- packages/opencode/src/tool/task.ts | 27 +- packages/opencode/test/session/prompt.test.ts | 37 ++ packages/opencode/test/tool/task.test.ts | 518 ++++++++++-------- 4 files changed, 339 insertions(+), 247 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index e1fa81abf..8286ecf8e 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,6 +1,5 @@ import path from "path" import os from "os" -import z from "zod" import * as EffectZod from "@/util/effect-zod" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" @@ -121,9 +120,8 @@ export const layer = Layer.effect( return yield* EffectBridge.make() }) const ops = Effect.fn("SessionPrompt.ops")(function* () { - const run = yield* runner() return { - cancel: (sessionID: SessionID) => run.fork(cancel(sessionID)), + cancel: (sessionID: SessionID) => cancel(sessionID), resolvePromptParts: (template: string) => resolvePromptParts(template), prompt: (input: PromptInput) => prompt(input), } satisfies TaskPromptOps diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index e58ea9b12..22e4e5671 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -6,10 +6,11 @@ import { MessageV2 } from "../session/message-v2" import { Agent } from "../agent/agent" import type { SessionPrompt } from "../session/prompt" import { Config } from "@/config/config" -import { Effect, Schema } from "effect" +import { Effect, Exit, Schema } from "effect" +import { EffectBridge } from "@/effect/bridge" export interface TaskPromptOps { - cancel(sessionID: SessionID): void + cancel(sessionID: SessionID): Effect.Effect resolvePromptParts(template: string): Effect.Effect prompt(input: SessionPrompt.PromptInput): Effect.Effect } @@ -118,16 +119,18 @@ export const TaskTool = Tool.define( const ops = ctx.extra?.promptOps as TaskPromptOps if (!ops) return yield* Effect.fail(new Error("TaskTool requires promptOps in ctx.extra")) + const runCancel = yield* EffectBridge.make() const messageID = MessageID.ascending() + const cancel = ops.cancel(nextSession.id) - function cancel() { - ops.cancel(nextSession.id) + function onAbort() { + runCancel.fork(cancel) } return yield* Effect.acquireUseRelease( Effect.sync(() => { - ctx.abort.addEventListener("abort", cancel) + ctx.abort.addEventListener("abort", onAbort) }), () => Effect.gen(function* () { @@ -163,10 +166,16 @@ export const TaskTool = Tool.define( ].join("\n"), } }), - () => - Effect.sync(() => { - ctx.abort.removeEventListener("abort", cancel) - }), + (_, exit) => + Effect.gen(function* () { + if (Exit.hasInterrupts(exit)) yield* cancel + }).pipe( + Effect.ensuring( + Effect.sync(() => { + ctx.abort.removeEventListener("abort", onAbort) + }), + ), + ), ) }) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index a602c0c8d..c5170f346 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -858,6 +858,43 @@ it.live( 30_000, ) +it.live( + "cancel propagates from slash command subtask to child session", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const status = yield* SessionStatus.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* llm.hang + const msg = yield* user(chat.id, "hello") + yield* addSubtask(chat.id, msg.id) + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + + const msgs = yield* MessageV2.filterCompactedEffect(chat.id) + const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") + const tool = taskMsg ? toolPart(taskMsg.parts) : undefined + const sessionID = tool?.state.status === "running" ? tool.state.metadata?.sessionId : undefined + expect(typeof sessionID).toBe("string") + if (typeof sessionID !== "string") throw new Error("missing child session id") + const childID = SessionID.make(sessionID) + expect((yield* status.get(childID)).type).toBe("busy") + + yield* prompt.cancel(chat.id) + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + + expect((yield* status.get(chat.id)).type).toBe("idle") + expect((yield* status.get(childID)).type).toBe("idle") + }), + { git: true, config: providerCfg }, + ), + 10_000, +) + it.live( "cancel with queued callers resolves all cleanly", () => diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index a8d62bb68..f75fcf84b 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -1,18 +1,17 @@ import { afterEach, describe, expect } from "bun:test" -import { Effect, Layer } from "effect" +import { Effect, Exit, Fiber, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { Config } from "@/config/config" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Instance } from "../../src/project/instance" import { Session } from "@/session/session" import { MessageV2 } from "../../src/session/message-v2" import type { SessionPrompt } from "../../src/session/prompt" -import { MessageID, PartID } from "../../src/session/schema" +import { MessageID, PartID, SessionID } from "../../src/session/schema" import { ModelID, ProviderID } from "../../src/provider/schema" import { TaskTool, type TaskPromptOps } from "../../src/tool/task" import { Truncate } from "@/tool/truncate" import { ToolRegistry } from "@/tool/registry" -import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" +import { disposeAllInstances } from "../fixture/fixture" import { testEffect } from "../lib/effect" afterEach(async () => { @@ -35,6 +34,14 @@ const it = testEffect( ), ) +function defer() { + let resolve!: (value: T | PromiseLike) => void + const promise = new Promise((done) => { + resolve = done + }) + return { promise, resolve } +} + const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { const session = yield* Session.Service const chat = yield* session.create({ title }) @@ -66,7 +73,7 @@ const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") { function stubOps(opts?: { onPrompt?: (input: SessionPrompt.PromptInput) => void; text?: string }): TaskPromptOps { return { - cancel() {}, + cancel: () => Effect.void, resolvePromptParts: (template) => Effect.succeed([{ type: "text" as const, text: template }]), prompt: (input) => Effect.sync(() => { @@ -107,102 +114,270 @@ function reply(input: SessionPrompt.PromptInput, text: string): MessageV2.WithPa } describe("tool.task", () => { - it.live("description sorts subagents by name and is stable across calls", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const agent = yield* Agent.Service - const build = yield* agent.get("build") - const registry = yield* ToolRegistry.Service - const get = Effect.fnUntraced(function* () { - const tools = yield* registry.tools({ ...ref, agent: build }) - return tools.find((tool) => tool.id === TaskTool.id)?.description ?? "" - }) - const first = yield* get() - const second = yield* get() + it.instance( + "description sorts subagents by name and is stable across calls", + () => + Effect.gen(function* () { + const agent = yield* Agent.Service + const build = yield* agent.get("build") + const registry = yield* ToolRegistry.Service + const get = Effect.fnUntraced(function* () { + const tools = yield* registry.tools({ ...ref, agent: build }) + return tools.find((tool) => tool.id === TaskTool.id)?.description ?? "" + }) + const first = yield* get() + const second = yield* get() - expect(first).toBe(second) + expect(first).toBe(second) - const alpha = first.indexOf("- alpha: Alpha agent") - const explore = first.indexOf("- explore:") - const general = first.indexOf("- general:") - const zebra = first.indexOf("- zebra: Zebra agent") + const alpha = first.indexOf("- alpha: Alpha agent") + const explore = first.indexOf("- explore:") + const general = first.indexOf("- general:") + const zebra = first.indexOf("- zebra: Zebra agent") - expect(alpha).toBeGreaterThan(-1) - expect(explore).toBeGreaterThan(alpha) - expect(general).toBeGreaterThan(explore) - expect(zebra).toBeGreaterThan(general) - }), - { - config: { - agent: { - zebra: { - description: "Zebra agent", - mode: "subagent", - }, - alpha: { - description: "Alpha agent", - mode: "subagent", - }, + expect(alpha).toBeGreaterThan(-1) + expect(explore).toBeGreaterThan(alpha) + expect(general).toBeGreaterThan(explore) + expect(zebra).toBeGreaterThan(general) + }), + { + config: { + agent: { + zebra: { + description: "Zebra agent", + mode: "subagent", + }, + alpha: { + description: "Alpha agent", + mode: "subagent", }, }, }, - ), + }, ) - it.live("description hides denied subagents for the caller", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const agent = yield* Agent.Service - const build = yield* agent.get("build") - const registry = yield* ToolRegistry.Service - const description = - (yield* registry.tools({ ...ref, agent: build })).find((tool) => tool.id === TaskTool.id)?.description ?? "" + it.instance( + "description hides denied subagents for the caller", + () => + Effect.gen(function* () { + const agent = yield* Agent.Service + const build = yield* agent.get("build") + const registry = yield* ToolRegistry.Service + const description = + (yield* registry.tools({ ...ref, agent: build })).find((tool) => tool.id === TaskTool.id)?.description ?? "" - expect(description).toContain("- alpha: Alpha agent") - expect(description).not.toContain("- zebra: Zebra agent") - }), - { - config: { - permission: { - task: { - "*": "allow", - zebra: "deny", - }, + expect(description).toContain("- alpha: Alpha agent") + expect(description).not.toContain("- zebra: Zebra agent") + }), + { + config: { + permission: { + task: { + "*": "allow", + zebra: "deny", }, - agent: { - zebra: { - description: "Zebra agent", - mode: "subagent", - }, - alpha: { - description: "Alpha agent", - mode: "subagent", - }, + }, + agent: { + zebra: { + description: "Zebra agent", + mode: "subagent", + }, + alpha: { + description: "Alpha agent", + mode: "subagent", }, }, }, - ), + }, ) - it.live("execute resumes an existing task session from task_id", () => - provideTmpdirInstance(() => + it.instance("execute resumes an existing task session from task_id", () => + Effect.gen(function* () { + const sessions = yield* Session.Service + const { chat, assistant } = yield* seed() + const child = yield* sessions.create({ parentID: chat.id, title: "Existing child" }) + const tool = yield* TaskTool + const def = yield* tool.init() + let seen: SessionPrompt.PromptInput | undefined + const promptOps = stubOps({ text: "resumed", onPrompt: (input) => (seen = input) }) + + const result = yield* def.execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + task_id: child.id, + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + + const kids = yield* sessions.children(chat.id) + expect(kids).toHaveLength(1) + expect(kids[0]?.id).toBe(child.id) + expect(result.metadata.sessionId).toBe(child.id) + expect(result.output).toContain(`task_id: ${child.id}`) + expect(seen?.sessionID).toBe(child.id) + }), + ) + + it.instance("execute asks by default and skips checks when bypassed", () => + Effect.gen(function* () { + const { chat, assistant } = yield* seed() + const tool = yield* TaskTool + const def = yield* tool.init() + const calls: unknown[] = [] + const promptOps = stubOps() + + const exec = (extra?: Record) => + def.execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps, ...extra }, + messages: [], + metadata: () => Effect.void, + ask: (input) => + Effect.sync(() => { + calls.push(input) + }), + }, + ) + + yield* exec() + yield* exec({ bypassAgentCheck: true }) + + expect(calls).toHaveLength(1) + expect(calls[0]).toEqual({ + permission: "task", + patterns: ["general"], + always: ["*"], + metadata: { + description: "inspect bug", + subagent_type: "general", + }, + }) + }), + ) + + it.instance("execute cancels child session when abort signal fires", () => + Effect.gen(function* () { + const { chat, assistant } = yield* seed() + const tool = yield* TaskTool + const def = yield* tool.init() + const ready = defer() + const cancelled = defer() + const abort = new AbortController() + const promptOps: TaskPromptOps = { + cancel: (sessionID) => + Effect.sync(() => { + cancelled.resolve(sessionID) + }), + resolvePromptParts: (template) => Effect.succeed([{ type: "text" as const, text: template }]), + prompt: (input) => + Effect.promise(() => { + ready.resolve(input) + return cancelled.promise + }).pipe(Effect.as(reply(input, "cancelled"))), + } + + const fiber = yield* def + .execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: abort.signal, + extra: { promptOps }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + .pipe(Effect.forkChild) + + const input = yield* Effect.promise(() => ready.promise) + abort.abort() + expect(yield* Effect.promise(() => cancelled.promise)).toBe(input.sessionID) + + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + }), + ) + + it.instance("execute creates a child when task_id does not exist", () => + Effect.gen(function* () { + const sessions = yield* Session.Service + const { chat, assistant } = yield* seed() + const tool = yield* TaskTool + const def = yield* tool.init() + let seen: SessionPrompt.PromptInput | undefined + const promptOps = stubOps({ text: "created", onPrompt: (input) => (seen = input) }) + + const result = yield* def.execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + task_id: "ses_missing", + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + + const kids = yield* sessions.children(chat.id) + expect(kids).toHaveLength(1) + expect(kids[0]?.id).toBe(result.metadata.sessionId) + expect(result.metadata.sessionId).not.toBe("ses_missing") + expect(result.output).toContain(`task_id: ${result.metadata.sessionId}`) + expect(seen?.sessionID).toBe(result.metadata.sessionId) + }), + ) + + it.instance( + "execute shapes child permissions for task, todowrite, and primary tools", + () => Effect.gen(function* () { const sessions = yield* Session.Service const { chat, assistant } = yield* seed() - const child = yield* sessions.create({ parentID: chat.id, title: "Existing child" }) const tool = yield* TaskTool const def = yield* tool.init() let seen: SessionPrompt.PromptInput | undefined - const promptOps = stubOps({ text: "resumed", onPrompt: (input) => (seen = input) }) + const promptOps = stubOps({ onPrompt: (input) => (seen = input) }) const result = yield* def.execute( { description: "inspect bug", prompt: "look into the cache key path", - subagent_type: "general", - task_id: child.id, + subagent_type: "reviewer", }, { sessionID: chat.id, @@ -216,172 +391,45 @@ describe("tool.task", () => { }, ) - const kids = yield* sessions.children(chat.id) - expect(kids).toHaveLength(1) - expect(kids[0]?.id).toBe(child.id) - expect(result.metadata.sessionId).toBe(child.id) - expect(result.output).toContain(`task_id: ${child.id}`) - expect(seen?.sessionID).toBe(child.id) - }), - ), - ) - - it.live("execute asks by default and skips checks when bypassed", () => - provideTmpdirInstance(() => - Effect.gen(function* () { - const { chat, assistant } = yield* seed() - const tool = yield* TaskTool - const def = yield* tool.init() - const calls: unknown[] = [] - const promptOps = stubOps() - - const exec = (extra?: Record) => - def.execute( - { - description: "inspect bug", - prompt: "look into the cache key path", - subagent_type: "general", - }, - { - sessionID: chat.id, - messageID: assistant.id, - agent: "build", - abort: new AbortController().signal, - extra: { promptOps, ...extra }, - messages: [], - metadata: () => Effect.void, - ask: (input) => - Effect.sync(() => { - calls.push(input) - }), - }, - ) - - yield* exec() - yield* exec({ bypassAgentCheck: true }) - - expect(calls).toHaveLength(1) - expect(calls[0]).toEqual({ - permission: "task", - patterns: ["general"], - always: ["*"], - metadata: { - description: "inspect bug", - subagent_type: "general", + const child = yield* sessions.get(result.metadata.sessionId) + expect(child.parentID).toBe(chat.id) + expect(child.permission).toEqual([ + { + permission: "todowrite", + pattern: "*", + action: "deny", }, + { + permission: "bash", + pattern: "*", + action: "allow", + }, + { + permission: "read", + pattern: "*", + action: "allow", + }, + ]) + expect(seen?.tools).toEqual({ + todowrite: false, + bash: false, + read: false, }) }), - ), - ) - - it.live("execute creates a child when task_id does not exist", () => - provideTmpdirInstance(() => - Effect.gen(function* () { - const sessions = yield* Session.Service - const { chat, assistant } = yield* seed() - const tool = yield* TaskTool - const def = yield* tool.init() - let seen: SessionPrompt.PromptInput | undefined - const promptOps = stubOps({ text: "created", onPrompt: (input) => (seen = input) }) - - const result = yield* def.execute( - { - description: "inspect bug", - prompt: "look into the cache key path", - subagent_type: "general", - task_id: "ses_missing", - }, - { - sessionID: chat.id, - messageID: assistant.id, - agent: "build", - abort: new AbortController().signal, - extra: { promptOps }, - messages: [], - metadata: () => Effect.void, - ask: () => Effect.void, - }, - ) - - const kids = yield* sessions.children(chat.id) - expect(kids).toHaveLength(1) - expect(kids[0]?.id).toBe(result.metadata.sessionId) - expect(result.metadata.sessionId).not.toBe("ses_missing") - expect(result.output).toContain(`task_id: ${result.metadata.sessionId}`) - expect(seen?.sessionID).toBe(result.metadata.sessionId) - }), - ), - ) - - it.live("execute shapes child permissions for task, todowrite, and primary tools", () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const sessions = yield* Session.Service - const { chat, assistant } = yield* seed() - const tool = yield* TaskTool - const def = yield* tool.init() - let seen: SessionPrompt.PromptInput | undefined - const promptOps = stubOps({ onPrompt: (input) => (seen = input) }) - - const result = yield* def.execute( - { - description: "inspect bug", - prompt: "look into the cache key path", - subagent_type: "reviewer", + { + config: { + agent: { + reviewer: { + mode: "subagent", + permission: { + task: "allow", }, - { - sessionID: chat.id, - messageID: assistant.id, - agent: "build", - abort: new AbortController().signal, - extra: { promptOps }, - messages: [], - metadata: () => Effect.void, - ask: () => Effect.void, - }, - ) - - const child = yield* sessions.get(result.metadata.sessionId) - expect(child.parentID).toBe(chat.id) - expect(child.permission).toEqual([ - { - permission: "todowrite", - pattern: "*", - action: "deny", - }, - { - permission: "bash", - pattern: "*", - action: "allow", - }, - { - permission: "read", - pattern: "*", - action: "allow", - }, - ]) - expect(seen?.tools).toEqual({ - todowrite: false, - bash: false, - read: false, - }) - }), - { - config: { - agent: { - reviewer: { - mode: "subagent", - permission: { - task: "allow", - }, - }, - }, - experimental: { - primary_tools: ["bash", "read"], }, }, + experimental: { + primary_tools: ["bash", "read"], + }, }, - ), + }, ) }) From 2d0a757eb2dbeabad64af02a9fb3602d4ccefd5b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 5 May 2026 02:37:07 +0000 Subject: [PATCH 14/18] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 61 +++++----- packages/sdk/openapi.json | 146 +++++++++--------------- 2 files changed, 83 insertions(+), 124 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index c0255754d..7734ca53e 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1875,9 +1875,11 @@ export type SyncEventSessionNextModelSwitched = { data: { timestamp: number sessionID: string - id: string - providerID: string - variant?: string + model: { + id: string + providerID: string + variant: string + } } } @@ -1948,7 +1950,7 @@ export type SyncEventSessionNextStepStarted = { model: { id: string providerID: string - variant?: string + variant: string } snapshot?: string } @@ -1987,10 +1989,7 @@ export type SyncEventSessionNextStepFailed = { data: { timestamp: number sessionID: string - error: { - type: string - message: string - } + error: SessionErrorUnknown } } @@ -2188,10 +2187,7 @@ export type SyncEventSessionNextToolFailed = { timestamp: number sessionID: string callID: string - error: { - type: string - message: string - } + error: SessionErrorUnknown provider: { executed: boolean metadata?: { @@ -2616,9 +2612,11 @@ export type EventSessionNextModelSwitched = { properties: { timestamp: number sessionID: string - id: string - providerID: string - variant?: string + model: { + id: string + providerID: string + variant: string + } } } @@ -2693,7 +2691,7 @@ export type EventSessionNextStepStarted = { model: { id: string providerID: string - variant?: string + variant: string } snapshot?: string } @@ -2720,16 +2718,18 @@ export type EventSessionNextStepEnded = { } } +export type SessionErrorUnknown = { + type: "unknown" + message: string +} + export type EventSessionNextStepFailed = { id: string type: "session.next.step.failed" properties: { timestamp: number sessionID: string - error: { - type: string - message: string - } + error: SessionErrorUnknown } } @@ -2900,10 +2900,7 @@ export type EventSessionNextToolFailed = { timestamp: number sessionID: string callID: string - error: { - type: string - message: string - } + error: SessionErrorUnknown provider: { executed: boolean metadata?: { @@ -2994,7 +2991,7 @@ export type SessionInfo = { model?: { id: string providerID: string - variant?: string + variant: string } time: { created: number @@ -3030,7 +3027,7 @@ export type SessionMessageModelSwitched = { model: { id: string providerID: string - variant?: string + variant: string } } @@ -3124,10 +3121,7 @@ export type SessionMessageToolStateError = { structured: { [key: string]: unknown } - error: { - type: string - message: string - } + error: SessionErrorUnknown } export type SessionMessageAssistantTool = { @@ -3167,7 +3161,7 @@ export type SessionMessageAssistant = { model: { id: string providerID: string - variant?: string + variant: string } content: Array snapshot?: { @@ -3185,10 +3179,7 @@ export type SessionMessageAssistant = { write: number } } - error?: { - type: string - message: string - } + error?: SessionErrorUnknown } export type SessionMessageCompaction = { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index db8889f1a..fea9dd5a9 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -13998,17 +13998,24 @@ "sessionID": { "type": "string" }, - "id": { - "type": "string" - }, - "providerID": { - "type": "string" - }, - "variant": { - "type": "string" + "model": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["id", "providerID", "variant"], + "additionalProperties": false } }, - "required": ["timestamp", "sessionID", "id", "providerID"], + "required": ["timestamp", "sessionID", "model"], "additionalProperties": false } }, @@ -14231,7 +14238,7 @@ "type": "string" } }, - "required": ["id", "providerID"], + "required": ["id", "providerID", "variant"], "additionalProperties": false }, "snapshot": { @@ -14357,17 +14364,7 @@ "type": "string" }, "error": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false + "$ref": "#/components/schemas/SessionErrorUnknown" } }, "required": ["timestamp", "sessionID", "error"], @@ -14979,17 +14976,7 @@ "type": "string" }, "error": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false + "$ref": "#/components/schemas/SessionErrorUnknown" }, "provider": { "type": "object", @@ -16267,17 +16254,24 @@ "sessionID": { "type": "string" }, - "id": { - "type": "string" - }, - "providerID": { - "type": "string" - }, - "variant": { - "type": "string" + "model": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "providerID": { + "type": "string" + }, + "variant": { + "type": "string" + } + }, + "required": ["id", "providerID", "variant"], + "additionalProperties": false } }, - "required": ["timestamp", "sessionID", "id", "providerID"], + "required": ["timestamp", "sessionID", "model"], "additionalProperties": false } }, @@ -16496,7 +16490,7 @@ "type": "string" } }, - "required": ["id", "providerID"], + "required": ["id", "providerID", "variant"], "additionalProperties": false }, "snapshot": { @@ -16580,6 +16574,20 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "SessionErrorUnknown": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["unknown"] + }, + "message": { + "type": "string" + } + }, + "required": ["type", "message"], + "additionalProperties": false + }, "EventSessionNextStepFailed": { "type": "object", "properties": { @@ -16600,17 +16608,7 @@ "type": "string" }, "error": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false + "$ref": "#/components/schemas/SessionErrorUnknown" } }, "required": ["timestamp", "sessionID", "error"], @@ -17113,17 +17111,7 @@ "type": "string" }, "error": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false + "$ref": "#/components/schemas/SessionErrorUnknown" }, "provider": { "type": "object", @@ -17376,7 +17364,7 @@ "type": "string" } }, - "required": ["id", "providerID"], + "required": ["id", "providerID", "variant"], "additionalProperties": false }, "time": { @@ -17472,7 +17460,7 @@ "type": "string" } }, - "required": ["id", "providerID"], + "required": ["id", "providerID", "variant"], "additionalProperties": false } }, @@ -17731,17 +17719,7 @@ "type": "object" }, "error": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false + "$ref": "#/components/schemas/SessionErrorUnknown" } }, "required": ["status", "input", "content", "structured", "error"], @@ -17854,7 +17832,7 @@ "type": "string" } }, - "required": ["id", "providerID"], + "required": ["id", "providerID", "variant"], "additionalProperties": false }, "content": { @@ -17921,17 +17899,7 @@ "additionalProperties": false }, "error": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": ["type", "message"], - "additionalProperties": false + "$ref": "#/components/schemas/SessionErrorUnknown" } }, "required": ["id", "time", "type", "agent", "model", "content"], From 07f1c8c0ac3d08e32c46a73c71786717b5472879 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 5 May 2026 14:26:35 +1000 Subject: [PATCH 15/18] fix(desktop): stabilize Windows titlebar zoom (#25813) --- packages/app/src/components/titlebar.tsx | 21 ++++++++++++++++--- packages/desktop-electron/src/main/ipc.ts | 9 ++++++-- packages/desktop-electron/src/main/windows.ts | 14 ++++++++++--- .../src/renderer/webview-zoom.ts | 15 +++++++++---- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 409fcbeff..eafea591a 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -35,6 +35,9 @@ type TauriApi = { const tauriApi = () => (window as unknown as { __TAURI__?: TauriApi }).__TAURI__ const currentDesktopWindow = () => tauriApi()?.window?.getCurrentWindow?.() const currentThemeWindow = () => tauriApi()?.webviewWindow?.getCurrentWebviewWindow?.() +const titlebarHeight = 40 +const minTitlebarZoom = 0.25 +const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each. export function Titlebar() { const layout = useLayout() @@ -51,7 +54,14 @@ export function Titlebar() { const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows") const web = createMemo(() => platform.platform === "web") const zoom = () => platform.webviewZoom?.() ?? 1 - const minHeight = () => (mac() ? `${40 / zoom()}px` : undefined) + const titlebarZoom = () => (windows() ? Math.max(zoom(), minTitlebarZoom) : zoom()) + const counterZoom = () => (windows() && titlebarZoom() < 1 ? 1 / titlebarZoom() : 1) + const minHeight = () => { + if (mac()) return `${titlebarHeight / zoom()}px` + if (windows()) return `${titlebarHeight / Math.min(titlebarZoom(), 1)}px` + return undefined + } + const windowsControlsWidth = () => `${windowsControlsBaseWidth / Math.max(titlebarZoom(), 1)}px` const [history, setHistory] = createStore({ stack: [] as string[], @@ -165,12 +175,16 @@ export function Titlebar() { return (
+
- {!tauriApi() &&
} + {!tauriApi() &&
}
+
) } diff --git a/packages/desktop-electron/src/main/ipc.ts b/packages/desktop-electron/src/main/ipc.ts index 8dbca8eea..241361373 100644 --- a/packages/desktop-electron/src/main/ipc.ts +++ b/packages/desktop-electron/src/main/ipc.ts @@ -11,7 +11,7 @@ import type { WslConfig, } from "../preload/types" import { getStore } from "./store" -import { setTitlebar } from "./windows" +import { setTitlebar, updateTitlebar } from "./windows" const pickerFilters = (ext?: string[]) => { if (!ext || ext.length === 0) return undefined @@ -183,7 +183,12 @@ export function registerIpcHandlers(deps: Deps) { }) ipcMain.handle("get-zoom-factor", (event: IpcMainInvokeEvent) => event.sender.getZoomFactor()) - ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => event.sender.setZoomFactor(factor)) + ipcMain.handle("set-zoom-factor", (event: IpcMainInvokeEvent, factor: number) => { + event.sender.setZoomFactor(factor) + const win = BrowserWindow.fromWebContents(event.sender) + if (!win) return + updateTitlebar(win) + }) ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => { const win = BrowserWindow.fromWebContents(event.sender) if (!win) return diff --git a/packages/desktop-electron/src/main/windows.ts b/packages/desktop-electron/src/main/windows.ts index 337e1ca0b..387e793b0 100644 --- a/packages/desktop-electron/src/main/windows.ts +++ b/packages/desktop-electron/src/main/windows.ts @@ -21,6 +21,8 @@ protocol.registerSchemesAsPrivileged([ ]) let backgroundColor: string | undefined +const titlebarThemes = new WeakMap>() +const titlebarHeight = 40 export function setBackgroundColor(color: string) { backgroundColor = color @@ -43,18 +45,23 @@ function tone() { return nativeTheme.shouldUseDarkColors ? "dark" : "light" } -function overlay(theme: Partial = {}) { +function overlay(theme: Partial = {}, zoom = 1) { const mode = theme.mode ?? tone() return { color: "#00000000", symbolColor: mode === "dark" ? "white" : "black", - height: 40, + height: Math.max(titlebarHeight, Math.round(titlebarHeight * zoom)), } } export function setTitlebar(win: BrowserWindow, theme: Partial = {}) { + titlebarThemes.set(win, theme) + updateTitlebar(win) +} + +export function updateTitlebar(win: BrowserWindow) { if (process.platform !== "win32") return - win.setTitleBarOverlay(overlay(theme)) + win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor())) } export function setDockIcon() { @@ -188,6 +195,7 @@ function wireZoom(win: BrowserWindow) { win.webContents.setZoomFactor(1) win.webContents.on("zoom-changed", () => { win.webContents.setZoomFactor(1) + updateTitlebar(win) }) } diff --git a/packages/desktop-electron/src/renderer/webview-zoom.ts b/packages/desktop-electron/src/renderer/webview-zoom.ts index 6e13266f4..967ff54eb 100644 --- a/packages/desktop-electron/src/renderer/webview-zoom.ts +++ b/packages/desktop-electron/src/renderer/webview-zoom.ts @@ -12,6 +12,7 @@ const OS_NAME = (() => { })() const [webviewZoom, setWebviewZoom] = createSignal(1) +let requestedZoom = 1 const MAX_ZOOM_LEVEL = 10 const MIN_ZOOM_LEVEL = 0.2 @@ -19,8 +20,14 @@ const MIN_ZOOM_LEVEL = 0.2 const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL) const applyZoom = (next: number) => { - setWebviewZoom(next) - void window.api.setZoomFactor(next) + requestedZoom = next + void window.api.setZoomFactor(next).then(() => { + if (requestedZoom !== next) return + setWebviewZoom(next) + }).catch(() => { + if (requestedZoom !== next) return + requestedZoom = webviewZoom() + }) } window.addEventListener("keydown", (event) => { @@ -28,12 +35,12 @@ window.addEventListener("keydown", (event) => { if (event.key === "-") { event.preventDefault() - applyZoom(clamp(webviewZoom() - 0.2)) + applyZoom(clamp(requestedZoom - 0.2)) return } if (event.key === "=" || event.key === "+") { event.preventDefault() - applyZoom(clamp(webviewZoom() + 0.2)) + applyZoom(clamp(requestedZoom + 0.2)) return } if (event.key === "0") { From 6f7d63e9ceaacc5debbfcba18bf8391a90e59e8f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 5 May 2026 04:27:38 +0000 Subject: [PATCH 16/18] chore: generate --- packages/app/src/components/titlebar.tsx | 266 +++++++++--------- .../src/renderer/webview-zoom.ts | 17 +- 2 files changed, 143 insertions(+), 140 deletions(-) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index eafea591a..2917b7adb 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -185,151 +185,151 @@ export function Titlebar() { class="grid h-full min-h-full w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center" style={{ zoom: counterZoom() }} > -
- -
-
- -
- - -
- -
-
-
- - - - -
-
-
+
+
+
-
-
- - {!tauriApi() &&
} -
- -
+
+
+ + {!tauriApi() &&
} +
+ +
) diff --git a/packages/desktop-electron/src/renderer/webview-zoom.ts b/packages/desktop-electron/src/renderer/webview-zoom.ts index 967ff54eb..cb4b5a448 100644 --- a/packages/desktop-electron/src/renderer/webview-zoom.ts +++ b/packages/desktop-electron/src/renderer/webview-zoom.ts @@ -21,13 +21,16 @@ const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_Z const applyZoom = (next: number) => { requestedZoom = next - void window.api.setZoomFactor(next).then(() => { - if (requestedZoom !== next) return - setWebviewZoom(next) - }).catch(() => { - if (requestedZoom !== next) return - requestedZoom = webviewZoom() - }) + void window.api + .setZoomFactor(next) + .then(() => { + if (requestedZoom !== next) return + setWebviewZoom(next) + }) + .catch(() => { + if (requestedZoom !== next) return + requestedZoom = webviewZoom() + }) } window.addEventListener("keydown", (event) => { From b4147c8d08b2e14554337536f54c6965006b29ca Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 5 May 2026 13:43:36 +0800 Subject: [PATCH 17/18] refactor(desktop): consolidate desktop-electron into desktop package (#25822) --- .github/workflows/publish.yml | 20 +- bun.lock | 95 +- package.json | 2 +- packages/desktop-electron/.gitignore | 28 - packages/desktop-electron/AGENTS.md | 4 - packages/desktop-electron/README.md | 32 - packages/desktop-electron/package.json | 68 - .../desktop-electron/scripts/copy-bundles.ts | 12 - packages/desktop-electron/scripts/predev.ts | 5 - packages/desktop-electron/scripts/prepare.ts | 9 - packages/desktop-electron/scripts/utils.ts | 77 - packages/desktop-electron/sst-env.d.ts | 10 - packages/desktop-electron/tsconfig.json | 23 - packages/desktop/.gitignore | 4 + packages/desktop/AGENTS.md | 4 +- packages/desktop/README.md | 24 +- .../electron-builder.config.ts | 0 .../electron.vite.config.ts | 0 .../icons/README.md | 0 .../icons/beta/128x128.png | Bin .../icons/beta/128x128@2x.png | Bin .../icons/beta/32x32.png | Bin .../icons/beta/64x64.png | Bin .../icons/beta/Square107x107Logo.png | Bin .../icons/beta/Square142x142Logo.png | Bin .../icons/beta/Square150x150Logo.png | Bin .../icons/beta/Square284x284Logo.png | Bin .../icons/beta/Square30x30Logo.png | Bin .../icons/beta/Square310x310Logo.png | Bin .../icons/beta/Square44x44Logo.png | Bin .../icons/beta/Square71x71Logo.png | Bin .../icons/beta/Square89x89Logo.png | Bin .../icons/beta/StoreLogo.png | Bin .../android/mipmap-anydpi-v26/ic_launcher.xml | 0 .../beta/android/mipmap-hdpi/ic_launcher.png | Bin .../mipmap-hdpi/ic_launcher_foreground.png | Bin .../android/mipmap-hdpi/ic_launcher_round.png | Bin .../beta/android/mipmap-mdpi/ic_launcher.png | Bin .../mipmap-mdpi/ic_launcher_foreground.png | Bin .../android/mipmap-mdpi/ic_launcher_round.png | Bin .../beta/android/mipmap-xhdpi/ic_launcher.png | Bin .../mipmap-xhdpi/ic_launcher_foreground.png | Bin .../mipmap-xhdpi/ic_launcher_round.png | Bin .../android/mipmap-xxhdpi/ic_launcher.png | Bin .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxhdpi/ic_launcher_round.png | Bin .../android/mipmap-xxxhdpi/ic_launcher.png | Bin .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxxhdpi/ic_launcher_round.png | Bin .../android/values/ic_launcher_background.xml | 0 .../icons/beta/dock.png | Bin .../icons/beta/icon.icns | Bin .../icons/beta/icon.ico | Bin .../icons/beta/icon.png | Bin .../icons/beta/ios/AppIcon-20x20@1x.png | Bin .../icons/beta/ios/AppIcon-20x20@2x-1.png | Bin .../icons/beta/ios/AppIcon-20x20@2x.png | Bin .../icons/beta/ios/AppIcon-20x20@3x.png | Bin .../icons/beta/ios/AppIcon-29x29@1x.png | Bin .../icons/beta/ios/AppIcon-29x29@2x-1.png | Bin .../icons/beta/ios/AppIcon-29x29@2x.png | Bin .../icons/beta/ios/AppIcon-29x29@3x.png | Bin .../icons/beta/ios/AppIcon-40x40@1x.png | Bin .../icons/beta/ios/AppIcon-40x40@2x-1.png | Bin .../icons/beta/ios/AppIcon-40x40@2x.png | Bin .../icons/beta/ios/AppIcon-40x40@3x.png | Bin .../icons/beta/ios/AppIcon-512@2x.png | Bin .../icons/beta/ios/AppIcon-60x60@2x.png | Bin .../icons/beta/ios/AppIcon-60x60@3x.png | Bin .../icons/beta/ios/AppIcon-76x76@1x.png | Bin .../icons/beta/ios/AppIcon-76x76@2x.png | Bin .../icons/beta/ios/AppIcon-83.5x83.5@2x.png | Bin .../icons/dev/128x128.png | Bin .../icons/dev/128x128@2x.png | Bin .../icons/dev/32x32.png | Bin .../icons/dev/64x64.png | Bin .../icons/dev/Square107x107Logo.png | Bin .../icons/dev/Square142x142Logo.png | Bin .../icons/dev/Square150x150Logo.png | Bin .../icons/dev/Square284x284Logo.png | Bin .../icons/dev/Square30x30Logo.png | Bin .../icons/dev/Square310x310Logo.png | Bin .../icons/dev/Square44x44Logo.png | Bin .../icons/dev/Square71x71Logo.png | Bin .../icons/dev/Square89x89Logo.png | Bin .../icons/dev/StoreLogo.png | Bin .../android/mipmap-anydpi-v26/ic_launcher.xml | 0 .../dev/android/mipmap-hdpi/ic_launcher.png | Bin .../mipmap-hdpi/ic_launcher_foreground.png | Bin .../android/mipmap-hdpi/ic_launcher_round.png | Bin .../dev/android/mipmap-mdpi/ic_launcher.png | Bin .../mipmap-mdpi/ic_launcher_foreground.png | Bin .../android/mipmap-mdpi/ic_launcher_round.png | Bin .../dev/android/mipmap-xhdpi/ic_launcher.png | Bin .../mipmap-xhdpi/ic_launcher_foreground.png | Bin .../mipmap-xhdpi/ic_launcher_round.png | Bin .../dev/android/mipmap-xxhdpi/ic_launcher.png | Bin .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxhdpi/ic_launcher_round.png | Bin .../android/mipmap-xxxhdpi/ic_launcher.png | Bin .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxxhdpi/ic_launcher_round.png | Bin .../android/values/ic_launcher_background.xml | 0 .../icons/dev/dock.png | Bin .../icons/dev/icon.icns | Bin .../icons/dev/icon.ico | Bin .../icons/dev/icon.png | Bin .../icons/dev/ios/AppIcon-20x20@1x.png | Bin .../icons/dev/ios/AppIcon-20x20@2x-1.png | Bin .../icons/dev/ios/AppIcon-20x20@2x.png | Bin .../icons/dev/ios/AppIcon-20x20@3x.png | Bin .../icons/dev/ios/AppIcon-29x29@1x.png | Bin .../icons/dev/ios/AppIcon-29x29@2x-1.png | Bin .../icons/dev/ios/AppIcon-29x29@2x.png | Bin .../icons/dev/ios/AppIcon-29x29@3x.png | Bin .../icons/dev/ios/AppIcon-40x40@1x.png | Bin .../icons/dev/ios/AppIcon-40x40@2x-1.png | Bin .../icons/dev/ios/AppIcon-40x40@2x.png | Bin .../icons/dev/ios/AppIcon-40x40@3x.png | Bin .../icons/dev/ios/AppIcon-512@2x.png | Bin .../icons/dev/ios/AppIcon-60x60@2x.png | Bin .../icons/dev/ios/AppIcon-60x60@3x.png | Bin .../icons/dev/ios/AppIcon-76x76@1x.png | Bin .../icons/dev/ios/AppIcon-76x76@2x.png | Bin .../icons/dev/ios/AppIcon-83.5x83.5@2x.png | Bin .../icons/prod/128x128.png | Bin .../icons/prod/128x128@2x.png | Bin .../icons/prod/32x32.png | Bin .../icons/prod/64x64.png | Bin .../icons/prod/Square107x107Logo.png | Bin .../icons/prod/Square142x142Logo.png | Bin .../icons/prod/Square150x150Logo.png | Bin .../icons/prod/Square284x284Logo.png | Bin .../icons/prod/Square30x30Logo.png | Bin .../icons/prod/Square310x310Logo.png | Bin .../icons/prod/Square44x44Logo.png | Bin .../icons/prod/Square71x71Logo.png | Bin .../icons/prod/Square89x89Logo.png | Bin .../icons/prod/StoreLogo.png | Bin .../android/mipmap-anydpi-v26/ic_launcher.xml | 0 .../prod/android/mipmap-hdpi/ic_launcher.png | Bin .../mipmap-hdpi/ic_launcher_foreground.png | Bin .../android/mipmap-hdpi/ic_launcher_round.png | Bin .../prod/android/mipmap-mdpi/ic_launcher.png | Bin .../mipmap-mdpi/ic_launcher_foreground.png | Bin .../android/mipmap-mdpi/ic_launcher_round.png | Bin .../prod/android/mipmap-xhdpi/ic_launcher.png | Bin .../mipmap-xhdpi/ic_launcher_foreground.png | Bin .../mipmap-xhdpi/ic_launcher_round.png | Bin .../android/mipmap-xxhdpi/ic_launcher.png | Bin .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxhdpi/ic_launcher_round.png | Bin .../android/mipmap-xxxhdpi/ic_launcher.png | Bin .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin .../mipmap-xxxhdpi/ic_launcher_round.png | Bin .../android/values/ic_launcher_background.xml | 0 .../icons/prod/dock.png | Bin .../icons/prod/icon.icns | Bin .../icons/prod/icon.ico | Bin .../icons/prod/icon.png | Bin .../icons/prod/ios/AppIcon-20x20@1x.png | Bin .../icons/prod/ios/AppIcon-20x20@2x-1.png | Bin .../icons/prod/ios/AppIcon-20x20@2x.png | Bin .../icons/prod/ios/AppIcon-20x20@3x.png | Bin .../icons/prod/ios/AppIcon-29x29@1x.png | Bin .../icons/prod/ios/AppIcon-29x29@2x-1.png | Bin .../icons/prod/ios/AppIcon-29x29@2x.png | Bin .../icons/prod/ios/AppIcon-29x29@3x.png | Bin .../icons/prod/ios/AppIcon-40x40@1x.png | Bin .../icons/prod/ios/AppIcon-40x40@2x-1.png | Bin .../icons/prod/ios/AppIcon-40x40@2x.png | Bin .../icons/prod/ios/AppIcon-40x40@3x.png | Bin .../icons/prod/ios/AppIcon-512@2x.png | Bin .../icons/prod/ios/AppIcon-60x60@2x.png | Bin .../icons/prod/ios/AppIcon-60x60@3x.png | Bin .../icons/prod/ios/AppIcon-76x76@1x.png | Bin .../icons/prod/ios/AppIcon-76x76@2x.png | Bin .../icons/prod/ios/AppIcon-83.5x83.5@2x.png | Bin packages/desktop/index.html | 24 - packages/desktop/package.json | 74 +- .../resources/entitlements.plist | 0 packages/desktop/scripts/copy-bundles.ts | 6 +- .../scripts/copy-icons.ts | 0 .../scripts/finalize-latest-yml.ts | 0 .../scripts/prebuild.ts | 0 packages/desktop/scripts/predev.ts | 14 +- packages/desktop/scripts/prepare.ts | 15 +- packages/desktop/scripts/utils.ts | 28 +- packages/desktop/src-tauri/.gitignore | 9 - packages/desktop/src-tauri/Cargo.lock | 7394 ----------------- packages/desktop/src-tauri/Cargo.toml | 75 - .../desktop/src-tauri/assets/nsis-header.bmp | Bin 25818 -> 0 bytes .../desktop/src-tauri/assets/nsis-sidebar.bmp | Bin 154542 -> 0 bytes packages/desktop/src-tauri/build.rs | 3 - .../src-tauri/capabilities/default.json | 52 - packages/desktop/src-tauri/entitlements.plist | 18 - packages/desktop/src-tauri/icons/README.md | 11 - .../desktop/src-tauri/icons/beta/128x128.png | Bin 10186 -> 0 bytes .../src-tauri/icons/beta/128x128@2x.png | Bin 36252 -> 0 bytes .../desktop/src-tauri/icons/beta/32x32.png | Bin 1309 -> 0 bytes .../desktop/src-tauri/icons/beta/64x64.png | Bin 3587 -> 0 bytes .../icons/beta/Square107x107Logo.png | Bin 7562 -> 0 bytes .../icons/beta/Square142x142Logo.png | Bin 12279 -> 0 bytes .../icons/beta/Square150x150Logo.png | Bin 13445 -> 0 bytes .../icons/beta/Square284x284Logo.png | Bin 45201 -> 0 bytes .../src-tauri/icons/beta/Square30x30Logo.png | Bin 1281 -> 0 bytes .../icons/beta/Square310x310Logo.png | Bin 54725 -> 0 bytes .../src-tauri/icons/beta/Square44x44Logo.png | Bin 2167 -> 0 bytes .../src-tauri/icons/beta/Square71x71Logo.png | Bin 4121 -> 0 bytes .../src-tauri/icons/beta/Square89x89Logo.png | Bin 5782 -> 0 bytes .../src-tauri/icons/beta/StoreLogo.png | Bin 2559 -> 0 bytes .../android/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../beta/android/mipmap-hdpi/ic_launcher.png | Bin 2077 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 15269 -> 0 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 1887 -> 0 bytes .../beta/android/mipmap-mdpi/ic_launcher.png | Bin 2083 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 7845 -> 0 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 1792 -> 0 bytes .../beta/android/mipmap-xhdpi/ic_launcher.png | Bin 5778 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 25523 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 5026 -> 0 bytes .../android/mipmap-xxhdpi/ic_launcher.png | Bin 10758 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 60763 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 9312 -> 0 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 17122 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 116520 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 14941 -> 0 bytes .../android/values/ic_launcher_background.xml | 4 - .../desktop/src-tauri/icons/beta/icon.icns | Bin 882048 -> 0 bytes .../desktop/src-tauri/icons/beta/icon.ico | Bin 49612 -> 0 bytes .../desktop/src-tauri/icons/beta/icon.png | Bin 172485 -> 0 bytes .../icons/beta/ios/AppIcon-20x20@1x.png | Bin 687 -> 0 bytes .../icons/beta/ios/AppIcon-20x20@2x-1.png | Bin 1660 -> 0 bytes .../icons/beta/ios/AppIcon-20x20@2x.png | Bin 1660 -> 0 bytes .../icons/beta/ios/AppIcon-20x20@3x.png | Bin 2950 -> 0 bytes .../icons/beta/ios/AppIcon-29x29@1x.png | Bin 1072 -> 0 bytes .../icons/beta/ios/AppIcon-29x29@2x-1.png | Bin 2834 -> 0 bytes .../icons/beta/ios/AppIcon-29x29@2x.png | Bin 2834 -> 0 bytes .../icons/beta/ios/AppIcon-29x29@3x.png | Bin 5048 -> 0 bytes .../icons/beta/ios/AppIcon-40x40@1x.png | Bin 1660 -> 0 bytes .../icons/beta/ios/AppIcon-40x40@2x-1.png | Bin 4396 -> 0 bytes .../icons/beta/ios/AppIcon-40x40@2x.png | Bin 4396 -> 0 bytes .../icons/beta/ios/AppIcon-40x40@3x.png | Bin 8452 -> 0 bytes .../icons/beta/ios/AppIcon-512@2x.png | Bin 596205 -> 0 bytes .../icons/beta/ios/AppIcon-60x60@2x.png | Bin 8452 -> 0 bytes .../icons/beta/ios/AppIcon-60x60@3x.png | Bin 16916 -> 0 bytes .../icons/beta/ios/AppIcon-76x76@1x.png | Bin 4193 -> 0 bytes .../icons/beta/ios/AppIcon-76x76@2x.png | Bin 12523 -> 0 bytes .../icons/beta/ios/AppIcon-83.5x83.5@2x.png | Bin 14760 -> 0 bytes .../desktop/src-tauri/icons/dev/128x128.png | Bin 16568 -> 0 bytes .../src-tauri/icons/dev/128x128@2x.png | Bin 59884 -> 0 bytes .../desktop/src-tauri/icons/dev/32x32.png | Bin 1973 -> 0 bytes .../desktop/src-tauri/icons/dev/64x64.png | Bin 5469 -> 0 bytes .../src-tauri/icons/dev/Square107x107Logo.png | Bin 12116 -> 0 bytes .../src-tauri/icons/dev/Square142x142Logo.png | Bin 19936 -> 0 bytes .../src-tauri/icons/dev/Square150x150Logo.png | Bin 21988 -> 0 bytes .../src-tauri/icons/dev/Square284x284Logo.png | Bin 74022 -> 0 bytes .../src-tauri/icons/dev/Square30x30Logo.png | Bin 1786 -> 0 bytes .../src-tauri/icons/dev/Square310x310Logo.png | Bin 89075 -> 0 bytes .../src-tauri/icons/dev/Square44x44Logo.png | Bin 3211 -> 0 bytes .../src-tauri/icons/dev/Square71x71Logo.png | Bin 6370 -> 0 bytes .../src-tauri/icons/dev/Square89x89Logo.png | Bin 9316 -> 0 bytes .../desktop/src-tauri/icons/dev/StoreLogo.png | Bin 3862 -> 0 bytes .../android/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../dev/android/mipmap-hdpi/ic_launcher.png | Bin 3076 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 24987 -> 0 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 2853 -> 0 bytes .../dev/android/mipmap-mdpi/ic_launcher.png | Bin 3016 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 12682 -> 0 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 2702 -> 0 bytes .../dev/android/mipmap-xhdpi/ic_launcher.png | Bin 8701 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 42285 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 7640 -> 0 bytes .../dev/android/mipmap-xxhdpi/ic_launcher.png | Bin 16970 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 97586 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 14939 -> 0 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 27316 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 180625 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 24066 -> 0 bytes .../android/values/ic_launcher_background.xml | 4 - .../desktop/src-tauri/icons/dev/icon.icns | Bin 1187792 -> 0 bytes packages/desktop/src-tauri/icons/dev/icon.ico | Bin 73182 -> 0 bytes packages/desktop/src-tauri/icons/dev/icon.png | Bin 264014 -> 0 bytes .../icons/dev/ios/AppIcon-20x20@1x.png | Bin 955 -> 0 bytes .../icons/dev/ios/AppIcon-20x20@2x-1.png | Bin 2695 -> 0 bytes .../icons/dev/ios/AppIcon-20x20@2x.png | Bin 2695 -> 0 bytes .../icons/dev/ios/AppIcon-20x20@3x.png | Bin 4932 -> 0 bytes .../icons/dev/ios/AppIcon-29x29@1x.png | Bin 1640 -> 0 bytes .../icons/dev/ios/AppIcon-29x29@2x-1.png | Bin 4684 -> 0 bytes .../icons/dev/ios/AppIcon-29x29@2x.png | Bin 4684 -> 0 bytes .../icons/dev/ios/AppIcon-29x29@3x.png | Bin 8781 -> 0 bytes .../icons/dev/ios/AppIcon-40x40@1x.png | Bin 2695 -> 0 bytes .../icons/dev/ios/AppIcon-40x40@2x-1.png | Bin 7529 -> 0 bytes .../icons/dev/ios/AppIcon-40x40@2x.png | Bin 7529 -> 0 bytes .../icons/dev/ios/AppIcon-40x40@3x.png | Bin 14557 -> 0 bytes .../icons/dev/ios/AppIcon-512@2x.png | Bin 980713 -> 0 bytes .../icons/dev/ios/AppIcon-60x60@2x.png | Bin 14557 -> 0 bytes .../icons/dev/ios/AppIcon-60x60@3x.png | Bin 29995 -> 0 bytes .../icons/dev/ios/AppIcon-76x76@1x.png | Bin 7093 -> 0 bytes .../icons/dev/ios/AppIcon-76x76@2x.png | Bin 22066 -> 0 bytes .../icons/dev/ios/AppIcon-83.5x83.5@2x.png | Bin 25898 -> 0 bytes .../desktop/src-tauri/icons/prod/128x128.png | Bin 9013 -> 0 bytes .../src-tauri/icons/prod/128x128@2x.png | Bin 36840 -> 0 bytes .../desktop/src-tauri/icons/prod/32x32.png | Bin 1255 -> 0 bytes .../desktop/src-tauri/icons/prod/64x64.png | Bin 2971 -> 0 bytes .../icons/prod/Square107x107Logo.png | Bin 6441 -> 0 bytes .../icons/prod/Square142x142Logo.png | Bin 10850 -> 0 bytes .../icons/prod/Square150x150Logo.png | Bin 12036 -> 0 bytes .../icons/prod/Square284x284Logo.png | Bin 47137 -> 0 bytes .../src-tauri/icons/prod/Square30x30Logo.png | Bin 1109 -> 0 bytes .../icons/prod/Square310x310Logo.png | Bin 58165 -> 0 bytes .../src-tauri/icons/prod/Square44x44Logo.png | Bin 1827 -> 0 bytes .../src-tauri/icons/prod/Square71x71Logo.png | Bin 3405 -> 0 bytes .../src-tauri/icons/prod/Square89x89Logo.png | Bin 4760 -> 0 bytes .../src-tauri/icons/prod/StoreLogo.png | Bin 2186 -> 0 bytes .../android/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../prod/android/mipmap-hdpi/ic_launcher.png | Bin 1886 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 13918 -> 0 bytes .../android/mipmap-hdpi/ic_launcher_round.png | Bin 1811 -> 0 bytes .../prod/android/mipmap-mdpi/ic_launcher.png | Bin 1873 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 6540 -> 0 bytes .../android/mipmap-mdpi/ic_launcher_round.png | Bin 1751 -> 0 bytes .../prod/android/mipmap-xhdpi/ic_launcher.png | Bin 4726 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 25393 -> 0 bytes .../mipmap-xhdpi/ic_launcher_round.png | Bin 4101 -> 0 bytes .../android/mipmap-xxhdpi/ic_launcher.png | Bin 9156 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 64829 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_round.png | Bin 8270 -> 0 bytes .../android/mipmap-xxxhdpi/ic_launcher.png | Bin 15359 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 127895 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_round.png | Bin 14064 -> 0 bytes .../android/values/ic_launcher_background.xml | 4 - .../desktop/src-tauri/icons/prod/icon.icns | Bin 1010901 -> 0 bytes .../desktop/src-tauri/icons/prod/icon.ico | Bin 47600 -> 0 bytes .../desktop/src-tauri/icons/prod/icon.png | Bin 190179 -> 0 bytes .../icons/prod/ios/AppIcon-20x20@1x.png | Bin 728 -> 0 bytes .../icons/prod/ios/AppIcon-20x20@2x-1.png | Bin 1607 -> 0 bytes .../icons/prod/ios/AppIcon-20x20@2x.png | Bin 1607 -> 0 bytes .../icons/prod/ios/AppIcon-20x20@3x.png | Bin 2648 -> 0 bytes .../icons/prod/ios/AppIcon-29x29@1x.png | Bin 1094 -> 0 bytes .../icons/prod/ios/AppIcon-29x29@2x-1.png | Bin 2542 -> 0 bytes .../icons/prod/ios/AppIcon-29x29@2x.png | Bin 2542 -> 0 bytes .../icons/prod/ios/AppIcon-29x29@3x.png | Bin 4709 -> 0 bytes .../icons/prod/ios/AppIcon-40x40@1x.png | Bin 1607 -> 0 bytes .../icons/prod/ios/AppIcon-40x40@2x-1.png | Bin 4058 -> 0 bytes .../icons/prod/ios/AppIcon-40x40@2x.png | Bin 4058 -> 0 bytes .../icons/prod/ios/AppIcon-40x40@3x.png | Bin 7828 -> 0 bytes .../icons/prod/ios/AppIcon-512@2x.png | Bin 681769 -> 0 bytes .../icons/prod/ios/AppIcon-60x60@2x.png | Bin 7828 -> 0 bytes .../icons/prod/ios/AppIcon-60x60@3x.png | Bin 17106 -> 0 bytes .../icons/prod/ios/AppIcon-76x76@1x.png | Bin 3730 -> 0 bytes .../icons/prod/ios/AppIcon-76x76@2x.png | Bin 12166 -> 0 bytes .../icons/prod/ios/AppIcon-83.5x83.5@2x.png | Bin 14705 -> 0 bytes .../src-tauri/release/appstream.metainfo.xml | 130 - packages/desktop/src-tauri/src/cli.rs | 742 -- packages/desktop/src-tauri/src/constants.rs | 10 - packages/desktop/src-tauri/src/lib.rs | 601 -- .../desktop/src-tauri/src/linux_display.rs | 53 - .../desktop/src-tauri/src/linux_windowing.rs | 475 -- packages/desktop/src-tauri/src/logging.rs | 76 - packages/desktop/src-tauri/src/main.rs | 78 - packages/desktop/src-tauri/src/markdown.rs | 63 - packages/desktop/src-tauri/src/os/mod.rs | 2 - packages/desktop/src-tauri/src/os/windows.rs | 463 -- packages/desktop/src-tauri/src/server.rs | 170 - .../src-tauri/src/window_customizer.rs | 46 - packages/desktop/src-tauri/src/windows.rs | 174 - .../desktop/src-tauri/tauri.beta.conf.json | 37 - packages/desktop/src-tauri/tauri.conf.json | 67 - .../desktop/src-tauri/tauri.prod.conf.json | 42 - packages/desktop/src/bindings.ts | 67 - packages/desktop/src/cli.ts | 43 - packages/desktop/src/entry.tsx | 5 - packages/desktop/src/env.d.ts | 9 - packages/desktop/src/i18n/ar.ts | 59 - packages/desktop/src/i18n/br.ts | 61 - packages/desktop/src/i18n/bs.ts | 62 - packages/desktop/src/i18n/da.ts | 61 - packages/desktop/src/i18n/de.ts | 62 - packages/desktop/src/i18n/en.ts | 61 - packages/desktop/src/i18n/es.ts | 61 - packages/desktop/src/i18n/fr.ts | 62 - packages/desktop/src/i18n/index.ts | 192 - packages/desktop/src/i18n/ja.ts | 62 - packages/desktop/src/i18n/ko.ts | 60 - packages/desktop/src/i18n/no.ts | 61 - packages/desktop/src/i18n/pl.ts | 62 - packages/desktop/src/i18n/ru.ts | 61 - packages/desktop/src/i18n/zh.ts | 59 - packages/desktop/src/i18n/zht.ts | 59 - packages/desktop/src/index.tsx | 505 -- packages/desktop/src/loading.tsx | 90 - .../src/main/apps.ts | 0 .../src/main/constants.ts | 0 .../src/main/env.d.ts | 0 .../src/main/index.ts | 0 .../src/main/ipc.ts | 0 .../src/main/logging.ts | 0 .../src/main/markdown.ts | 0 .../src/main/menu.ts | 0 .../src/main/migrate.ts | 0 .../src/main/server.ts | 0 .../src/main/shell-env.test.ts | 0 .../src/main/shell-env.ts | 0 .../src/main/store.ts | 2 +- .../src/main/windows.ts | 0 packages/desktop/src/menu.ts | 190 - .../src/preload/index.ts | 0 .../src/preload/types.ts | 0 .../src/renderer/cli.ts | 0 .../src/renderer/env.d.ts | 0 .../src/renderer/html.test.ts | 0 .../src/renderer/i18n/ar.ts | 0 .../src/renderer/i18n/br.ts | 0 .../src/renderer/i18n/bs.ts | 0 .../src/renderer/i18n/da.ts | 0 .../src/renderer/i18n/de.ts | 0 .../src/renderer/i18n/en.ts | 0 .../src/renderer/i18n/es.ts | 0 .../src/renderer/i18n/fr.ts | 0 .../src/renderer/i18n/index.ts | 0 .../src/renderer/i18n/ja.ts | 0 .../src/renderer/i18n/ko.ts | 0 .../src/renderer/i18n/no.ts | 0 .../src/renderer/i18n/pl.ts | 0 .../src/renderer/i18n/ru.ts | 0 .../src/renderer/i18n/zh.ts | 0 .../src/renderer/i18n/zht.ts | 0 .../src/renderer/index.html | 0 .../src/renderer/index.tsx | 4 +- .../src/renderer/loading.html | 0 .../src/renderer/loading.tsx | 0 .../src/renderer/styles.css | 0 .../src/renderer/updater.ts | 0 .../src/renderer/webview-zoom.ts | 0 packages/desktop/src/styles.css | 7 - packages/desktop/src/updater.ts | 51 - packages/desktop/src/webview-zoom.ts | 37 - packages/desktop/tsconfig.json | 5 +- packages/desktop/vite.config.ts | 38 - script/publish.ts | 2 +- 441 files changed, 115 insertions(+), 13441 deletions(-) delete mode 100644 packages/desktop-electron/.gitignore delete mode 100644 packages/desktop-electron/AGENTS.md delete mode 100644 packages/desktop-electron/README.md delete mode 100644 packages/desktop-electron/package.json delete mode 100644 packages/desktop-electron/scripts/copy-bundles.ts delete mode 100644 packages/desktop-electron/scripts/predev.ts delete mode 100755 packages/desktop-electron/scripts/prepare.ts delete mode 100644 packages/desktop-electron/scripts/utils.ts delete mode 100644 packages/desktop-electron/sst-env.d.ts delete mode 100644 packages/desktop-electron/tsconfig.json rename packages/{desktop-electron => desktop}/electron-builder.config.ts (100%) rename packages/{desktop-electron => desktop}/electron.vite.config.ts (100%) rename packages/{desktop-electron => desktop}/icons/README.md (100%) rename packages/{desktop-electron => desktop}/icons/beta/128x128.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/128x128@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/32x32.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/64x64.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square107x107Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square142x142Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square150x150Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square284x284Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square30x30Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square310x310Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square44x44Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square71x71Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/Square89x89Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/StoreLogo.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-hdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-hdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-mdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-mdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/android/values/ic_launcher_background.xml (100%) rename packages/{desktop-electron => desktop}/icons/beta/dock.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/icon.icns (100%) rename packages/{desktop-electron => desktop}/icons/beta/icon.ico (100%) rename packages/{desktop-electron => desktop}/icons/beta/icon.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-20x20@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-20x20@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-20x20@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-20x20@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-29x29@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-29x29@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-29x29@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-29x29@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-40x40@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-40x40@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-40x40@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-40x40@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-512@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-60x60@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-60x60@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-76x76@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-76x76@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/beta/ios/AppIcon-83.5x83.5@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/128x128.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/128x128@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/32x32.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/64x64.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square107x107Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square142x142Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square150x150Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square284x284Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square30x30Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square310x310Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square44x44Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square71x71Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/Square89x89Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/StoreLogo.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-hdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-hdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-mdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-mdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/android/values/ic_launcher_background.xml (100%) rename packages/{desktop-electron => desktop}/icons/dev/dock.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/icon.icns (100%) rename packages/{desktop-electron => desktop}/icons/dev/icon.ico (100%) rename packages/{desktop-electron => desktop}/icons/dev/icon.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-20x20@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-20x20@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-20x20@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-20x20@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-29x29@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-29x29@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-29x29@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-29x29@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-40x40@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-40x40@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-40x40@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-40x40@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-512@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-60x60@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-60x60@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-76x76@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-76x76@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/dev/ios/AppIcon-83.5x83.5@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/128x128.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/128x128@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/32x32.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/64x64.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square107x107Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square142x142Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square150x150Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square284x284Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square30x30Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square310x310Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square44x44Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square71x71Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/Square89x89Logo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/StoreLogo.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-hdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-hdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-mdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-mdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/android/values/ic_launcher_background.xml (100%) rename packages/{desktop-electron => desktop}/icons/prod/dock.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/icon.icns (100%) rename packages/{desktop-electron => desktop}/icons/prod/icon.ico (100%) rename packages/{desktop-electron => desktop}/icons/prod/icon.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-20x20@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-20x20@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-20x20@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-20x20@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-29x29@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-29x29@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-29x29@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-29x29@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-40x40@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-40x40@2x-1.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-40x40@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-40x40@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-512@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-60x60@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-60x60@3x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-76x76@1x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-76x76@2x.png (100%) rename packages/{desktop-electron => desktop}/icons/prod/ios/AppIcon-83.5x83.5@2x.png (100%) delete mode 100644 packages/desktop/index.html rename packages/{desktop-electron => desktop}/resources/entitlements.plist (100%) rename packages/{desktop-electron => desktop}/scripts/copy-icons.ts (100%) rename packages/{desktop-electron => desktop}/scripts/finalize-latest-yml.ts (100%) rename packages/{desktop-electron => desktop}/scripts/prebuild.ts (100%) delete mode 100644 packages/desktop/src-tauri/.gitignore delete mode 100644 packages/desktop/src-tauri/Cargo.lock delete mode 100644 packages/desktop/src-tauri/Cargo.toml delete mode 100644 packages/desktop/src-tauri/assets/nsis-header.bmp delete mode 100644 packages/desktop/src-tauri/assets/nsis-sidebar.bmp delete mode 100644 packages/desktop/src-tauri/build.rs delete mode 100644 packages/desktop/src-tauri/capabilities/default.json delete mode 100644 packages/desktop/src-tauri/entitlements.plist delete mode 100644 packages/desktop/src-tauri/icons/README.md delete mode 100644 packages/desktop/src-tauri/icons/beta/128x128.png delete mode 100644 packages/desktop/src-tauri/icons/beta/128x128@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/32x32.png delete mode 100644 packages/desktop/src-tauri/icons/beta/64x64.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square107x107Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square142x142Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square150x150Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square284x284Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square30x30Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square310x310Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square44x44Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square71x71Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/Square89x89Logo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/StoreLogo.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-hdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-hdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-mdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-mdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xxhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/beta/android/values/ic_launcher_background.xml delete mode 100644 packages/desktop/src-tauri/icons/beta/icon.icns delete mode 100644 packages/desktop/src-tauri/icons/beta/icon.ico delete mode 100644 packages/desktop/src-tauri/icons/beta/icon.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@1x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-20x20@3x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@1x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-29x29@3x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@1x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-40x40@3x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-512@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-60x60@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-60x60@3x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-76x76@1x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-76x76@2x.png delete mode 100644 packages/desktop/src-tauri/icons/beta/ios/AppIcon-83.5x83.5@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/128x128.png delete mode 100644 packages/desktop/src-tauri/icons/dev/128x128@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/32x32.png delete mode 100644 packages/desktop/src-tauri/icons/dev/64x64.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square107x107Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square142x142Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square150x150Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square284x284Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square30x30Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square310x310Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square44x44Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square71x71Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/Square89x89Logo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/StoreLogo.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-hdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-mdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/dev/android/values/ic_launcher_background.xml delete mode 100644 packages/desktop/src-tauri/icons/dev/icon.icns delete mode 100644 packages/desktop/src-tauri/icons/dev/icon.ico delete mode 100644 packages/desktop/src-tauri/icons/dev/icon.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@1x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-20x20@3x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@1x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-29x29@3x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@1x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-40x40@3x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-512@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-60x60@3x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@1x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-76x76@2x.png delete mode 100644 packages/desktop/src-tauri/icons/dev/ios/AppIcon-83.5x83.5@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/128x128.png delete mode 100644 packages/desktop/src-tauri/icons/prod/128x128@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/32x32.png delete mode 100644 packages/desktop/src-tauri/icons/prod/64x64.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square107x107Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square142x142Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square150x150Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square284x284Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square30x30Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square310x310Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square44x44Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square71x71Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/Square89x89Logo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/StoreLogo.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-hdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-mdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 packages/desktop/src-tauri/icons/prod/android/values/ic_launcher_background.xml delete mode 100644 packages/desktop/src-tauri/icons/prod/icon.icns delete mode 100644 packages/desktop/src-tauri/icons/prod/icon.ico delete mode 100644 packages/desktop/src-tauri/icons/prod/icon.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@1x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-20x20@3x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@1x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-29x29@3x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@1x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x-1.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-40x40@3x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-512@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-60x60@3x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@1x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-76x76@2x.png delete mode 100644 packages/desktop/src-tauri/icons/prod/ios/AppIcon-83.5x83.5@2x.png delete mode 100644 packages/desktop/src-tauri/release/appstream.metainfo.xml delete mode 100644 packages/desktop/src-tauri/src/cli.rs delete mode 100644 packages/desktop/src-tauri/src/constants.rs delete mode 100644 packages/desktop/src-tauri/src/lib.rs delete mode 100644 packages/desktop/src-tauri/src/linux_display.rs delete mode 100644 packages/desktop/src-tauri/src/linux_windowing.rs delete mode 100644 packages/desktop/src-tauri/src/logging.rs delete mode 100644 packages/desktop/src-tauri/src/main.rs delete mode 100644 packages/desktop/src-tauri/src/markdown.rs delete mode 100644 packages/desktop/src-tauri/src/os/mod.rs delete mode 100644 packages/desktop/src-tauri/src/os/windows.rs delete mode 100644 packages/desktop/src-tauri/src/server.rs delete mode 100644 packages/desktop/src-tauri/src/window_customizer.rs delete mode 100644 packages/desktop/src-tauri/src/windows.rs delete mode 100644 packages/desktop/src-tauri/tauri.beta.conf.json delete mode 100644 packages/desktop/src-tauri/tauri.conf.json delete mode 100644 packages/desktop/src-tauri/tauri.prod.conf.json delete mode 100644 packages/desktop/src/bindings.ts delete mode 100644 packages/desktop/src/cli.ts delete mode 100644 packages/desktop/src/entry.tsx delete mode 100644 packages/desktop/src/env.d.ts delete mode 100644 packages/desktop/src/i18n/ar.ts delete mode 100644 packages/desktop/src/i18n/br.ts delete mode 100644 packages/desktop/src/i18n/bs.ts delete mode 100644 packages/desktop/src/i18n/da.ts delete mode 100644 packages/desktop/src/i18n/de.ts delete mode 100644 packages/desktop/src/i18n/en.ts delete mode 100644 packages/desktop/src/i18n/es.ts delete mode 100644 packages/desktop/src/i18n/fr.ts delete mode 100644 packages/desktop/src/i18n/index.ts delete mode 100644 packages/desktop/src/i18n/ja.ts delete mode 100644 packages/desktop/src/i18n/ko.ts delete mode 100644 packages/desktop/src/i18n/no.ts delete mode 100644 packages/desktop/src/i18n/pl.ts delete mode 100644 packages/desktop/src/i18n/ru.ts delete mode 100644 packages/desktop/src/i18n/zh.ts delete mode 100644 packages/desktop/src/i18n/zht.ts delete mode 100644 packages/desktop/src/index.tsx delete mode 100644 packages/desktop/src/loading.tsx rename packages/{desktop-electron => desktop}/src/main/apps.ts (100%) rename packages/{desktop-electron => desktop}/src/main/constants.ts (100%) rename packages/{desktop-electron => desktop}/src/main/env.d.ts (100%) rename packages/{desktop-electron => desktop}/src/main/index.ts (100%) rename packages/{desktop-electron => desktop}/src/main/ipc.ts (100%) rename packages/{desktop-electron => desktop}/src/main/logging.ts (100%) rename packages/{desktop-electron => desktop}/src/main/markdown.ts (100%) rename packages/{desktop-electron => desktop}/src/main/menu.ts (100%) rename packages/{desktop-electron => desktop}/src/main/migrate.ts (100%) rename packages/{desktop-electron => desktop}/src/main/server.ts (100%) rename packages/{desktop-electron => desktop}/src/main/shell-env.test.ts (100%) rename packages/{desktop-electron => desktop}/src/main/shell-env.ts (100%) rename packages/{desktop-electron => desktop}/src/main/store.ts (81%) rename packages/{desktop-electron => desktop}/src/main/windows.ts (100%) delete mode 100644 packages/desktop/src/menu.ts rename packages/{desktop-electron => desktop}/src/preload/index.ts (100%) rename packages/{desktop-electron => desktop}/src/preload/types.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/cli.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/env.d.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/html.test.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/ar.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/br.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/bs.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/da.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/de.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/en.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/es.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/fr.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/index.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/ja.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/ko.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/no.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/pl.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/ru.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/zh.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/i18n/zht.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/index.html (100%) rename packages/{desktop-electron => desktop}/src/renderer/index.tsx (98%) rename packages/{desktop-electron => desktop}/src/renderer/loading.html (100%) rename packages/{desktop-electron => desktop}/src/renderer/loading.tsx (100%) rename packages/{desktop-electron => desktop}/src/renderer/styles.css (100%) rename packages/{desktop-electron => desktop}/src/renderer/updater.ts (100%) rename packages/{desktop-electron => desktop}/src/renderer/webview-zoom.ts (100%) delete mode 100644 packages/desktop/src/styles.css delete mode 100644 packages/desktop/src/updater.ts delete mode 100644 packages/desktop/src/webview-zoom.ts delete mode 100644 packages/desktop/vite.config.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4614226a8..5f7ee96b9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -304,7 +304,7 @@ jobs: - name: Prepare run: bun ./scripts/prepare.ts - working-directory: packages/desktop-electron + working-directory: packages/desktop env: OPENCODE_VERSION: ${{ needs.version.outputs.version }} OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }} @@ -315,7 +315,7 @@ jobs: - name: Build run: bun run build - working-directory: packages/desktop-electron + working-directory: packages/desktop env: OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} @@ -329,7 +329,7 @@ jobs: - name: Package and publish if: needs.version.outputs.release run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts - working-directory: packages/desktop-electron + working-directory: packages/desktop timeout-minutes: 60 env: OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }} @@ -343,14 +343,14 @@ jobs: - name: Package (no publish) if: ${{ !needs.version.outputs.release }} run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts - working-directory: packages/desktop-electron + working-directory: packages/desktop timeout-minutes: 60 env: OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }} - name: Create and upload macOS .app.tar.gz if: runner.os == 'macOS' && needs.version.outputs.release - working-directory: packages/desktop-electron/dist + working-directory: packages/desktop/dist env: GH_TOKEN: ${{ steps.committer.outputs.token }} run: | @@ -377,9 +377,9 @@ jobs: shell: pwsh run: | $files = @() - $files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*.exe" | Select-Object -ExpandProperty FullName - $files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName - $files += Get-ChildItem "${{ github.workspace }}\packages\desktop-electron\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName + $files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*.exe" | Select-Object -ExpandProperty FullName + $files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName + $files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName foreach ($file in $files | Select-Object -Unique) { $sig = Get-AuthenticodeSignature $file @@ -391,13 +391,13 @@ jobs: - uses: actions/upload-artifact@v4 with: name: opencode-desktop-${{ matrix.settings.target }} - path: packages/desktop-electron/dist/* + path: packages/desktop/dist/* - uses: actions/upload-artifact@v4 if: needs.version.outputs.release with: name: latest-yml-${{ matrix.settings.target }} - path: packages/desktop-electron/dist/latest*.yml + path: packages/desktop/dist/latest*.yml publish: needs: diff --git a/bun.lock b/bun.lock index 07415dd79..5067655ae 100644 --- a/bun.lock +++ b/bun.lock @@ -229,41 +229,6 @@ "packages/desktop": { "name": "@opencode-ai/desktop", "version": "1.14.35", - "dependencies": { - "@opencode-ai/app": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@sentry/solid": "catalog:", - "@solid-primitives/i18n": "2.2.1", - "@solid-primitives/storage": "catalog:", - "@solidjs/meta": "catalog:", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-clipboard-manager": "~2", - "@tauri-apps/plugin-deep-link": "~2", - "@tauri-apps/plugin-dialog": "~2", - "@tauri-apps/plugin-http": "~2", - "@tauri-apps/plugin-notification": "~2", - "@tauri-apps/plugin-opener": "^2", - "@tauri-apps/plugin-os": "~2", - "@tauri-apps/plugin-process": "~2", - "@tauri-apps/plugin-shell": "~2", - "@tauri-apps/plugin-store": "~2", - "@tauri-apps/plugin-updater": "~2", - "@tauri-apps/plugin-window-state": "~2", - "solid-js": "catalog:", - }, - "devDependencies": { - "@actions/artifact": "4.0.0", - "@sentry/vite-plugin": "catalog:", - "@tauri-apps/cli": "^2", - "@types/bun": "catalog:", - "@typescript/native-preview": "catalog:", - "typescript": "~5.6.2", - "vite": "catalog:", - }, - }, - "packages/desktop-electron": { - "name": "@opencode-ai/desktop-electron", - "version": "1.14.35", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -1570,8 +1535,6 @@ "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"], - "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"], - "@opencode-ai/enterprise": ["@opencode-ai/enterprise@workspace:packages/enterprise"], "@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"], @@ -2270,54 +2233,8 @@ "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], - "@tauri-apps/cli": ["@tauri-apps/cli@2.10.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="], - - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ=="], - - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw=="], - - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w=="], - - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA=="], - - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg=="], - - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.1", "", { "os": "linux", "cpu": "none" }, "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw=="], - - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw=="], - - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ=="], - - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg=="], - - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw=="], - - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="], - - "@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ=="], - - "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="], - - "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw=="], - - "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-oxd7oypzQeu8kAfFCrw534Kq7Cw+NzozcnCY21O4rz3A+veJiIiuSCMIprgGcZOcLAXFP9GmDhKUbhuKWcunRw=="], - - "@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="], - - "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="], - - "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="], - - "@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="], - - "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="], - "@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="], - "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.1", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA=="], - - "@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="], - "@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="], "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], @@ -5628,14 +5545,10 @@ "@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], + "@opencode-ai/desktop/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + "@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], - "@opencode-ai/desktop-electron/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="], - - "@opencode-ai/desktop-electron/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], - - "@opencode-ai/desktop-electron/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], - "@opencode-ai/ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], "@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="], @@ -6618,8 +6531,6 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "@opencode-ai/desktop-electron/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], - "@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], "@opencode-ai/web/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], @@ -7068,8 +6979,6 @@ "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@opencode-ai/desktop-electron/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], - "@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "@sentry/bundler-plugin-core/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], diff --git a/package.json b/package.json index de3dd31f4..335a8b3b1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "packageManager": "bun@1.3.13", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", - "dev:desktop": "bun --cwd packages/desktop-electron dev", + "dev:desktop": "bun --cwd packages/desktop dev", "dev:web": "bun --cwd packages/app dev", "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", "dev:storybook": "bun --cwd packages/storybook storybook", diff --git a/packages/desktop-electron/.gitignore b/packages/desktop-electron/.gitignore deleted file mode 100644 index ac9d8db96..000000000 --- a/packages/desktop-electron/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? -out/ - -resources/opencode-cli* -resources/icons diff --git a/packages/desktop-electron/AGENTS.md b/packages/desktop-electron/AGENTS.md deleted file mode 100644 index 7805ea835..000000000 --- a/packages/desktop-electron/AGENTS.md +++ /dev/null @@ -1,4 +0,0 @@ -# Desktop package notes - -- Renderer process should only call `window.api` from `src/preload`. -- Main process should register IPC handlers in `src/main/ipc.ts`. diff --git a/packages/desktop-electron/README.md b/packages/desktop-electron/README.md deleted file mode 100644 index ebaf48822..000000000 --- a/packages/desktop-electron/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# OpenCode Desktop - -Native OpenCode desktop app, built with Tauri v2. - -## Development - -From the repo root: - -```bash -bun install -bun run --cwd packages/desktop tauri dev -``` - -This starts the Vite dev server on http://localhost:1420 and opens the native window. - -If you only want the web dev server (no native shell): - -```bash -bun run --cwd packages/desktop dev -``` - -## Build - -To create a production `dist/` and build the native app bundle: - -```bash -bun run --cwd packages/desktop tauri build -``` - -## Prerequisites - -Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions. diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json deleted file mode 100644 index ba981e637..000000000 --- a/packages/desktop-electron/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@opencode-ai/desktop-electron", - "private": true, - "version": "1.14.35", - "type": "module", - "license": "MIT", - "homepage": "https://opencode.ai", - "author": { - "name": "OpenCode", - "email": "hello@opencode.ai" - }, - "scripts": { - "typecheck": "tsgo -b", - "predev": "bun ./scripts/predev.ts", - "dev": "electron-vite dev", - "prebuild": "bun ./scripts/prebuild.ts", - "build": "electron-vite build", - "preview": "electron-vite preview", - "package": "electron-builder --config electron-builder.config.ts", - "package:mac": "electron-builder --mac --config electron-builder.config.ts", - "package:win": "electron-builder --win --config electron-builder.config.ts", - "package:linux": "electron-builder --linux --config electron-builder.config.ts", - "native:build": "bun install --cwd native" - }, - "main": "./out/main/index.js", - "dependencies": { - "effect": "catalog:", - "electron-context-menu": "4.1.2", - "electron-log": "^5", - "electron-store": "^10", - "electron-updater": "^6", - "electron-window-state": "^5.0.3", - "drizzle-orm": "catalog:", - "marked": "^15" - }, - "devDependencies": { - "@actions/artifact": "4.0.0", - "@lydell/node-pty": "catalog:", - "@opencode-ai/app": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@sentry/solid": "catalog:", - "@sentry/vite-plugin": "catalog:", - "@solid-primitives/i18n": "2.2.1", - "@solid-primitives/storage": "catalog:", - "@solidjs/meta": "catalog:", - "@solidjs/router": "0.15.4", - "@types/bun": "catalog:", - "@types/node": "catalog:", - "@typescript/native-preview": "catalog:", - "@valibot/to-json-schema": "1.6.0", - "electron": "41.2.1", - "electron-builder": "^26", - "electron-vite": "^5", - "solid-js": "catalog:", - "sury": "11.0.0-alpha.4", - "typescript": "~5.6.2", - "vite": "catalog:", - "zod-openapi": "5.4.6" - }, - "optionalDependencies": { - "@lydell/node-pty-darwin-arm64": "1.2.0-beta.10", - "@lydell/node-pty-darwin-x64": "1.2.0-beta.10", - "@lydell/node-pty-linux-arm64": "1.2.0-beta.10", - "@lydell/node-pty-linux-x64": "1.2.0-beta.10", - "@lydell/node-pty-win32-arm64": "1.2.0-beta.10", - "@lydell/node-pty-win32-x64": "1.2.0-beta.10" - } -} diff --git a/packages/desktop-electron/scripts/copy-bundles.ts b/packages/desktop-electron/scripts/copy-bundles.ts deleted file mode 100644 index 6ef3335eb..000000000 --- a/packages/desktop-electron/scripts/copy-bundles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { $ } from "bun" -import * as path from "node:path" - -import { RUST_TARGET } from "./utils" - -if (!RUST_TARGET) throw new Error("RUST_TARGET not defined") - -const BUNDLE_DIR = "dist" -const BUNDLES_OUT_DIR = path.join(process.cwd(), "dist/bundles") - -await $`mkdir -p ${BUNDLES_OUT_DIR}` -await $`cp -r ${BUNDLE_DIR}/* ${BUNDLES_OUT_DIR}` diff --git a/packages/desktop-electron/scripts/predev.ts b/packages/desktop-electron/scripts/predev.ts deleted file mode 100644 index 37c31d7ee..000000000 --- a/packages/desktop-electron/scripts/predev.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { $ } from "bun" - -await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}` - -await $`cd ../opencode && bun script/build-node.ts` diff --git a/packages/desktop-electron/scripts/prepare.ts b/packages/desktop-electron/scripts/prepare.ts deleted file mode 100755 index 0dfd5a35c..000000000 --- a/packages/desktop-electron/scripts/prepare.ts +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bun -import { Script } from "@opencode-ai/script" - -await import("./prebuild") - -const pkg = await Bun.file("./package.json").json() -pkg.version = Script.version -await Bun.write("./package.json", JSON.stringify(pkg, null, 2) + "\n") -console.log(`Updated package.json version to ${Script.version}`) diff --git a/packages/desktop-electron/scripts/utils.ts b/packages/desktop-electron/scripts/utils.ts deleted file mode 100644 index 19b96b0a1..000000000 --- a/packages/desktop-electron/scripts/utils.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { $ } from "bun" - -export type Channel = "dev" | "beta" | "prod" - -export function resolveChannel(): Channel { - const raw = Bun.env.OPENCODE_CHANNEL - if (raw === "dev" || raw === "beta" || raw === "prod") return raw - return "dev" -} - -export const SIDECAR_BINARIES: Array<{ rustTarget: string; ocBinary: string; assetExt: string }> = [ - { - rustTarget: "aarch64-apple-darwin", - ocBinary: "opencode-darwin-arm64", - assetExt: "zip", - }, - { - rustTarget: "x86_64-apple-darwin", - ocBinary: "opencode-darwin-x64-baseline", - assetExt: "zip", - }, - { - rustTarget: "aarch64-pc-windows-msvc", - ocBinary: "opencode-windows-arm64", - assetExt: "zip", - }, - { - rustTarget: "x86_64-pc-windows-msvc", - ocBinary: "opencode-windows-x64-baseline", - assetExt: "zip", - }, - { - rustTarget: "x86_64-unknown-linux-gnu", - ocBinary: "opencode-linux-x64-baseline", - assetExt: "tar.gz", - }, - { - rustTarget: "aarch64-unknown-linux-gnu", - ocBinary: "opencode-linux-arm64", - assetExt: "tar.gz", - }, -] - -export const RUST_TARGET = Bun.env.RUST_TARGET - -function nativeTarget() { - const { platform, arch } = process - if (platform === "darwin") return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin" - if (platform === "win32") return arch === "arm64" ? "aarch64-pc-windows-msvc" : "x86_64-pc-windows-msvc" - if (platform === "linux") return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu" - throw new Error(`Unsupported platform: ${platform}/${arch}`) -} - -export function getCurrentSidecar(target = RUST_TARGET ?? nativeTarget()) { - const binaryConfig = SIDECAR_BINARIES.find((b) => b.rustTarget === target) - if (!binaryConfig) throw new Error(`Sidecar configuration not available for Rust target '${target}'`) - - return binaryConfig -} - -export async function copyBinaryToSidecarFolder(source: string) { - const dir = `resources` - await $`mkdir -p ${dir}` - const dest = windowsify(`${dir}/opencode-cli`) - await $`cp ${source} ${dest}` - if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true") { - await $`pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File ../../script/sign-windows.ps1 ${dest}` - } - if (process.platform === "darwin") await $`codesign --force --sign - ${dest}` - - console.log(`Copied ${source} to ${dest}`) -} - -export function windowsify(path: string) { - if (path.endsWith(".exe")) return path - return `${path}${process.platform === "win32" ? ".exe" : ""}` -} diff --git a/packages/desktop-electron/sst-env.d.ts b/packages/desktop-electron/sst-env.d.ts deleted file mode 100644 index 64441936d..000000000 --- a/packages/desktop-electron/sst-env.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* This file is auto-generated by SST. Do not edit. */ -/* tslint:disable */ -/* eslint-disable */ -/* deno-fmt-ignore-file */ -/* biome-ignore-all lint: auto-generated */ - -/// - -import "sst" -export {} \ No newline at end of file diff --git a/packages/desktop-electron/tsconfig.json b/packages/desktop-electron/tsconfig.json deleted file mode 100644 index 9637fe03d..000000000 --- a/packages/desktop-electron/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", - "allowJs": true, - "resolveJsonModule": true, - "strict": true, - "isolatedModules": true, - "noEmit": true, - "emitDeclarationOnly": false, - "outDir": "node_modules/.ts-dist", - "types": ["vite/client", "node", "electron"] - }, - "references": [{ "path": "../app" }], - "include": ["src", "package.json"], - "exclude": ["src/**/*.test.ts"] -} diff --git a/packages/desktop/.gitignore b/packages/desktop/.gitignore index a547bf36d..ac9d8db96 100644 --- a/packages/desktop/.gitignore +++ b/packages/desktop/.gitignore @@ -22,3 +22,7 @@ dist-ssr *.njsproj *.sln *.sw? +out/ + +resources/opencode-cli* +resources/icons diff --git a/packages/desktop/AGENTS.md b/packages/desktop/AGENTS.md index 3839db1a9..7805ea835 100644 --- a/packages/desktop/AGENTS.md +++ b/packages/desktop/AGENTS.md @@ -1,4 +1,4 @@ # Desktop package notes -- Never call `invoke` manually in this package. -- Use the generated bindings in `packages/desktop/src/bindings.ts` for core commands/events. +- Renderer process should only call `window.api` from `src/preload`. +- Main process should register IPC handlers in `src/main/ipc.ts`. diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 358b7d24d..ebaf48822 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -2,10 +2,6 @@ Native OpenCode desktop app, built with Tauri v2. -## Prerequisites - -Building the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions. - ## Development From the repo root: @@ -15,18 +11,22 @@ bun install bun run --cwd packages/desktop tauri dev ``` +This starts the Vite dev server on http://localhost:1420 and opens the native window. + +If you only want the web dev server (no native shell): + +```bash +bun run --cwd packages/desktop dev +``` + ## Build +To create a production `dist/` and build the native app bundle: + ```bash bun run --cwd packages/desktop tauri build ``` -## Troubleshooting +## Prerequisites -### Rust compiler not found - -If you see errors about Rust not being found, install it via [rustup](https://rustup.rs/): - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` +Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions. diff --git a/packages/desktop-electron/electron-builder.config.ts b/packages/desktop/electron-builder.config.ts similarity index 100% rename from packages/desktop-electron/electron-builder.config.ts rename to packages/desktop/electron-builder.config.ts diff --git a/packages/desktop-electron/electron.vite.config.ts b/packages/desktop/electron.vite.config.ts similarity index 100% rename from packages/desktop-electron/electron.vite.config.ts rename to packages/desktop/electron.vite.config.ts diff --git a/packages/desktop-electron/icons/README.md b/packages/desktop/icons/README.md similarity index 100% rename from packages/desktop-electron/icons/README.md rename to packages/desktop/icons/README.md diff --git a/packages/desktop-electron/icons/beta/128x128.png b/packages/desktop/icons/beta/128x128.png similarity index 100% rename from packages/desktop-electron/icons/beta/128x128.png rename to packages/desktop/icons/beta/128x128.png diff --git a/packages/desktop-electron/icons/beta/128x128@2x.png b/packages/desktop/icons/beta/128x128@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/128x128@2x.png rename to packages/desktop/icons/beta/128x128@2x.png diff --git a/packages/desktop-electron/icons/beta/32x32.png b/packages/desktop/icons/beta/32x32.png similarity index 100% rename from packages/desktop-electron/icons/beta/32x32.png rename to packages/desktop/icons/beta/32x32.png diff --git a/packages/desktop-electron/icons/beta/64x64.png b/packages/desktop/icons/beta/64x64.png similarity index 100% rename from packages/desktop-electron/icons/beta/64x64.png rename to packages/desktop/icons/beta/64x64.png diff --git a/packages/desktop-electron/icons/beta/Square107x107Logo.png b/packages/desktop/icons/beta/Square107x107Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square107x107Logo.png rename to packages/desktop/icons/beta/Square107x107Logo.png diff --git a/packages/desktop-electron/icons/beta/Square142x142Logo.png b/packages/desktop/icons/beta/Square142x142Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square142x142Logo.png rename to packages/desktop/icons/beta/Square142x142Logo.png diff --git a/packages/desktop-electron/icons/beta/Square150x150Logo.png b/packages/desktop/icons/beta/Square150x150Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square150x150Logo.png rename to packages/desktop/icons/beta/Square150x150Logo.png diff --git a/packages/desktop-electron/icons/beta/Square284x284Logo.png b/packages/desktop/icons/beta/Square284x284Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square284x284Logo.png rename to packages/desktop/icons/beta/Square284x284Logo.png diff --git a/packages/desktop-electron/icons/beta/Square30x30Logo.png b/packages/desktop/icons/beta/Square30x30Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square30x30Logo.png rename to packages/desktop/icons/beta/Square30x30Logo.png diff --git a/packages/desktop-electron/icons/beta/Square310x310Logo.png b/packages/desktop/icons/beta/Square310x310Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square310x310Logo.png rename to packages/desktop/icons/beta/Square310x310Logo.png diff --git a/packages/desktop-electron/icons/beta/Square44x44Logo.png b/packages/desktop/icons/beta/Square44x44Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square44x44Logo.png rename to packages/desktop/icons/beta/Square44x44Logo.png diff --git a/packages/desktop-electron/icons/beta/Square71x71Logo.png b/packages/desktop/icons/beta/Square71x71Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square71x71Logo.png rename to packages/desktop/icons/beta/Square71x71Logo.png diff --git a/packages/desktop-electron/icons/beta/Square89x89Logo.png b/packages/desktop/icons/beta/Square89x89Logo.png similarity index 100% rename from packages/desktop-electron/icons/beta/Square89x89Logo.png rename to packages/desktop/icons/beta/Square89x89Logo.png diff --git a/packages/desktop-electron/icons/beta/StoreLogo.png b/packages/desktop/icons/beta/StoreLogo.png similarity index 100% rename from packages/desktop-electron/icons/beta/StoreLogo.png rename to packages/desktop/icons/beta/StoreLogo.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml rename to packages/desktop/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/icons/beta/android/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png rename to packages/desktop/icons/beta/android/mipmap-hdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png rename to packages/desktop/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/icons/beta/android/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png rename to packages/desktop/icons/beta/android/mipmap-hdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/icons/beta/android/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png rename to packages/desktop/icons/beta/android/mipmap-mdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png rename to packages/desktop/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/icons/beta/android/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png rename to packages/desktop/icons/beta/android/mipmap-mdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/icons/beta/android/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png rename to packages/desktop/icons/beta/android/mipmap-xhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png rename to packages/desktop/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/icons/beta/android/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png rename to packages/desktop/icons/beta/android/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png rename to packages/desktop/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png rename to packages/desktop/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png rename to packages/desktop/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml b/packages/desktop/icons/beta/android/values/ic_launcher_background.xml similarity index 100% rename from packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml rename to packages/desktop/icons/beta/android/values/ic_launcher_background.xml diff --git a/packages/desktop-electron/icons/beta/dock.png b/packages/desktop/icons/beta/dock.png similarity index 100% rename from packages/desktop-electron/icons/beta/dock.png rename to packages/desktop/icons/beta/dock.png diff --git a/packages/desktop-electron/icons/beta/icon.icns b/packages/desktop/icons/beta/icon.icns similarity index 100% rename from packages/desktop-electron/icons/beta/icon.icns rename to packages/desktop/icons/beta/icon.icns diff --git a/packages/desktop-electron/icons/beta/icon.ico b/packages/desktop/icons/beta/icon.ico similarity index 100% rename from packages/desktop-electron/icons/beta/icon.ico rename to packages/desktop/icons/beta/icon.ico diff --git a/packages/desktop-electron/icons/beta/icon.png b/packages/desktop/icons/beta/icon.png similarity index 100% rename from packages/desktop-electron/icons/beta/icon.png rename to packages/desktop/icons/beta/icon.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@1x.png b/packages/desktop/icons/beta/ios/AppIcon-20x20@1x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-20x20@1x.png rename to packages/desktop/icons/beta/ios/AppIcon-20x20@1x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x-1.png b/packages/desktop/icons/beta/ios/AppIcon-20x20@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x-1.png rename to packages/desktop/icons/beta/ios/AppIcon-20x20@2x-1.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x.png b/packages/desktop/icons/beta/ios/AppIcon-20x20@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-20x20@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-20x20@2x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-20x20@3x.png b/packages/desktop/icons/beta/ios/AppIcon-20x20@3x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-20x20@3x.png rename to packages/desktop/icons/beta/ios/AppIcon-20x20@3x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@1x.png b/packages/desktop/icons/beta/ios/AppIcon-29x29@1x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-29x29@1x.png rename to packages/desktop/icons/beta/ios/AppIcon-29x29@1x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x-1.png b/packages/desktop/icons/beta/ios/AppIcon-29x29@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x-1.png rename to packages/desktop/icons/beta/ios/AppIcon-29x29@2x-1.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x.png b/packages/desktop/icons/beta/ios/AppIcon-29x29@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-29x29@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-29x29@2x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-29x29@3x.png b/packages/desktop/icons/beta/ios/AppIcon-29x29@3x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-29x29@3x.png rename to packages/desktop/icons/beta/ios/AppIcon-29x29@3x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@1x.png b/packages/desktop/icons/beta/ios/AppIcon-40x40@1x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-40x40@1x.png rename to packages/desktop/icons/beta/ios/AppIcon-40x40@1x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x-1.png b/packages/desktop/icons/beta/ios/AppIcon-40x40@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x-1.png rename to packages/desktop/icons/beta/ios/AppIcon-40x40@2x-1.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x.png b/packages/desktop/icons/beta/ios/AppIcon-40x40@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-40x40@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-40x40@2x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-40x40@3x.png b/packages/desktop/icons/beta/ios/AppIcon-40x40@3x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-40x40@3x.png rename to packages/desktop/icons/beta/ios/AppIcon-40x40@3x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-512@2x.png b/packages/desktop/icons/beta/ios/AppIcon-512@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-512@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-512@2x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@2x.png b/packages/desktop/icons/beta/ios/AppIcon-60x60@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-60x60@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-60x60@2x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-60x60@3x.png b/packages/desktop/icons/beta/ios/AppIcon-60x60@3x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-60x60@3x.png rename to packages/desktop/icons/beta/ios/AppIcon-60x60@3x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@1x.png b/packages/desktop/icons/beta/ios/AppIcon-76x76@1x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-76x76@1x.png rename to packages/desktop/icons/beta/ios/AppIcon-76x76@1x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-76x76@2x.png b/packages/desktop/icons/beta/ios/AppIcon-76x76@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-76x76@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-76x76@2x.png diff --git a/packages/desktop-electron/icons/beta/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/icons/beta/ios/AppIcon-83.5x83.5@2x.png similarity index 100% rename from packages/desktop-electron/icons/beta/ios/AppIcon-83.5x83.5@2x.png rename to packages/desktop/icons/beta/ios/AppIcon-83.5x83.5@2x.png diff --git a/packages/desktop-electron/icons/dev/128x128.png b/packages/desktop/icons/dev/128x128.png similarity index 100% rename from packages/desktop-electron/icons/dev/128x128.png rename to packages/desktop/icons/dev/128x128.png diff --git a/packages/desktop-electron/icons/dev/128x128@2x.png b/packages/desktop/icons/dev/128x128@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/128x128@2x.png rename to packages/desktop/icons/dev/128x128@2x.png diff --git a/packages/desktop-electron/icons/dev/32x32.png b/packages/desktop/icons/dev/32x32.png similarity index 100% rename from packages/desktop-electron/icons/dev/32x32.png rename to packages/desktop/icons/dev/32x32.png diff --git a/packages/desktop-electron/icons/dev/64x64.png b/packages/desktop/icons/dev/64x64.png similarity index 100% rename from packages/desktop-electron/icons/dev/64x64.png rename to packages/desktop/icons/dev/64x64.png diff --git a/packages/desktop-electron/icons/dev/Square107x107Logo.png b/packages/desktop/icons/dev/Square107x107Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square107x107Logo.png rename to packages/desktop/icons/dev/Square107x107Logo.png diff --git a/packages/desktop-electron/icons/dev/Square142x142Logo.png b/packages/desktop/icons/dev/Square142x142Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square142x142Logo.png rename to packages/desktop/icons/dev/Square142x142Logo.png diff --git a/packages/desktop-electron/icons/dev/Square150x150Logo.png b/packages/desktop/icons/dev/Square150x150Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square150x150Logo.png rename to packages/desktop/icons/dev/Square150x150Logo.png diff --git a/packages/desktop-electron/icons/dev/Square284x284Logo.png b/packages/desktop/icons/dev/Square284x284Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square284x284Logo.png rename to packages/desktop/icons/dev/Square284x284Logo.png diff --git a/packages/desktop-electron/icons/dev/Square30x30Logo.png b/packages/desktop/icons/dev/Square30x30Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square30x30Logo.png rename to packages/desktop/icons/dev/Square30x30Logo.png diff --git a/packages/desktop-electron/icons/dev/Square310x310Logo.png b/packages/desktop/icons/dev/Square310x310Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square310x310Logo.png rename to packages/desktop/icons/dev/Square310x310Logo.png diff --git a/packages/desktop-electron/icons/dev/Square44x44Logo.png b/packages/desktop/icons/dev/Square44x44Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square44x44Logo.png rename to packages/desktop/icons/dev/Square44x44Logo.png diff --git a/packages/desktop-electron/icons/dev/Square71x71Logo.png b/packages/desktop/icons/dev/Square71x71Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square71x71Logo.png rename to packages/desktop/icons/dev/Square71x71Logo.png diff --git a/packages/desktop-electron/icons/dev/Square89x89Logo.png b/packages/desktop/icons/dev/Square89x89Logo.png similarity index 100% rename from packages/desktop-electron/icons/dev/Square89x89Logo.png rename to packages/desktop/icons/dev/Square89x89Logo.png diff --git a/packages/desktop-electron/icons/dev/StoreLogo.png b/packages/desktop/icons/dev/StoreLogo.png similarity index 100% rename from packages/desktop-electron/icons/dev/StoreLogo.png rename to packages/desktop/icons/dev/StoreLogo.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml rename to packages/desktop/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/icons/dev/android/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png rename to packages/desktop/icons/dev/android/mipmap-hdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png rename to packages/desktop/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/icons/dev/android/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png rename to packages/desktop/icons/dev/android/mipmap-hdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/icons/dev/android/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png rename to packages/desktop/icons/dev/android/mipmap-mdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png rename to packages/desktop/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/icons/dev/android/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png rename to packages/desktop/icons/dev/android/mipmap-mdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/icons/dev/android/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png rename to packages/desktop/icons/dev/android/mipmap-xhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png rename to packages/desktop/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/icons/dev/android/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png rename to packages/desktop/icons/dev/android/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png rename to packages/desktop/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png rename to packages/desktop/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png rename to packages/desktop/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml b/packages/desktop/icons/dev/android/values/ic_launcher_background.xml similarity index 100% rename from packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml rename to packages/desktop/icons/dev/android/values/ic_launcher_background.xml diff --git a/packages/desktop-electron/icons/dev/dock.png b/packages/desktop/icons/dev/dock.png similarity index 100% rename from packages/desktop-electron/icons/dev/dock.png rename to packages/desktop/icons/dev/dock.png diff --git a/packages/desktop-electron/icons/dev/icon.icns b/packages/desktop/icons/dev/icon.icns similarity index 100% rename from packages/desktop-electron/icons/dev/icon.icns rename to packages/desktop/icons/dev/icon.icns diff --git a/packages/desktop-electron/icons/dev/icon.ico b/packages/desktop/icons/dev/icon.ico similarity index 100% rename from packages/desktop-electron/icons/dev/icon.ico rename to packages/desktop/icons/dev/icon.ico diff --git a/packages/desktop-electron/icons/dev/icon.png b/packages/desktop/icons/dev/icon.png similarity index 100% rename from packages/desktop-electron/icons/dev/icon.png rename to packages/desktop/icons/dev/icon.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@1x.png b/packages/desktop/icons/dev/ios/AppIcon-20x20@1x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-20x20@1x.png rename to packages/desktop/icons/dev/ios/AppIcon-20x20@1x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x-1.png b/packages/desktop/icons/dev/ios/AppIcon-20x20@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x-1.png rename to packages/desktop/icons/dev/ios/AppIcon-20x20@2x-1.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x.png b/packages/desktop/icons/dev/ios/AppIcon-20x20@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-20x20@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-20x20@2x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-20x20@3x.png b/packages/desktop/icons/dev/ios/AppIcon-20x20@3x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-20x20@3x.png rename to packages/desktop/icons/dev/ios/AppIcon-20x20@3x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@1x.png b/packages/desktop/icons/dev/ios/AppIcon-29x29@1x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-29x29@1x.png rename to packages/desktop/icons/dev/ios/AppIcon-29x29@1x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x-1.png b/packages/desktop/icons/dev/ios/AppIcon-29x29@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x-1.png rename to packages/desktop/icons/dev/ios/AppIcon-29x29@2x-1.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x.png b/packages/desktop/icons/dev/ios/AppIcon-29x29@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-29x29@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-29x29@2x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-29x29@3x.png b/packages/desktop/icons/dev/ios/AppIcon-29x29@3x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-29x29@3x.png rename to packages/desktop/icons/dev/ios/AppIcon-29x29@3x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@1x.png b/packages/desktop/icons/dev/ios/AppIcon-40x40@1x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-40x40@1x.png rename to packages/desktop/icons/dev/ios/AppIcon-40x40@1x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x-1.png b/packages/desktop/icons/dev/ios/AppIcon-40x40@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x-1.png rename to packages/desktop/icons/dev/ios/AppIcon-40x40@2x-1.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x.png b/packages/desktop/icons/dev/ios/AppIcon-40x40@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-40x40@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-40x40@2x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-40x40@3x.png b/packages/desktop/icons/dev/ios/AppIcon-40x40@3x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-40x40@3x.png rename to packages/desktop/icons/dev/ios/AppIcon-40x40@3x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-512@2x.png b/packages/desktop/icons/dev/ios/AppIcon-512@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-512@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-512@2x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@2x.png b/packages/desktop/icons/dev/ios/AppIcon-60x60@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-60x60@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-60x60@2x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-60x60@3x.png b/packages/desktop/icons/dev/ios/AppIcon-60x60@3x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-60x60@3x.png rename to packages/desktop/icons/dev/ios/AppIcon-60x60@3x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@1x.png b/packages/desktop/icons/dev/ios/AppIcon-76x76@1x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-76x76@1x.png rename to packages/desktop/icons/dev/ios/AppIcon-76x76@1x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-76x76@2x.png b/packages/desktop/icons/dev/ios/AppIcon-76x76@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-76x76@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-76x76@2x.png diff --git a/packages/desktop-electron/icons/dev/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/icons/dev/ios/AppIcon-83.5x83.5@2x.png similarity index 100% rename from packages/desktop-electron/icons/dev/ios/AppIcon-83.5x83.5@2x.png rename to packages/desktop/icons/dev/ios/AppIcon-83.5x83.5@2x.png diff --git a/packages/desktop-electron/icons/prod/128x128.png b/packages/desktop/icons/prod/128x128.png similarity index 100% rename from packages/desktop-electron/icons/prod/128x128.png rename to packages/desktop/icons/prod/128x128.png diff --git a/packages/desktop-electron/icons/prod/128x128@2x.png b/packages/desktop/icons/prod/128x128@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/128x128@2x.png rename to packages/desktop/icons/prod/128x128@2x.png diff --git a/packages/desktop-electron/icons/prod/32x32.png b/packages/desktop/icons/prod/32x32.png similarity index 100% rename from packages/desktop-electron/icons/prod/32x32.png rename to packages/desktop/icons/prod/32x32.png diff --git a/packages/desktop-electron/icons/prod/64x64.png b/packages/desktop/icons/prod/64x64.png similarity index 100% rename from packages/desktop-electron/icons/prod/64x64.png rename to packages/desktop/icons/prod/64x64.png diff --git a/packages/desktop-electron/icons/prod/Square107x107Logo.png b/packages/desktop/icons/prod/Square107x107Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square107x107Logo.png rename to packages/desktop/icons/prod/Square107x107Logo.png diff --git a/packages/desktop-electron/icons/prod/Square142x142Logo.png b/packages/desktop/icons/prod/Square142x142Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square142x142Logo.png rename to packages/desktop/icons/prod/Square142x142Logo.png diff --git a/packages/desktop-electron/icons/prod/Square150x150Logo.png b/packages/desktop/icons/prod/Square150x150Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square150x150Logo.png rename to packages/desktop/icons/prod/Square150x150Logo.png diff --git a/packages/desktop-electron/icons/prod/Square284x284Logo.png b/packages/desktop/icons/prod/Square284x284Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square284x284Logo.png rename to packages/desktop/icons/prod/Square284x284Logo.png diff --git a/packages/desktop-electron/icons/prod/Square30x30Logo.png b/packages/desktop/icons/prod/Square30x30Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square30x30Logo.png rename to packages/desktop/icons/prod/Square30x30Logo.png diff --git a/packages/desktop-electron/icons/prod/Square310x310Logo.png b/packages/desktop/icons/prod/Square310x310Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square310x310Logo.png rename to packages/desktop/icons/prod/Square310x310Logo.png diff --git a/packages/desktop-electron/icons/prod/Square44x44Logo.png b/packages/desktop/icons/prod/Square44x44Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square44x44Logo.png rename to packages/desktop/icons/prod/Square44x44Logo.png diff --git a/packages/desktop-electron/icons/prod/Square71x71Logo.png b/packages/desktop/icons/prod/Square71x71Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square71x71Logo.png rename to packages/desktop/icons/prod/Square71x71Logo.png diff --git a/packages/desktop-electron/icons/prod/Square89x89Logo.png b/packages/desktop/icons/prod/Square89x89Logo.png similarity index 100% rename from packages/desktop-electron/icons/prod/Square89x89Logo.png rename to packages/desktop/icons/prod/Square89x89Logo.png diff --git a/packages/desktop-electron/icons/prod/StoreLogo.png b/packages/desktop/icons/prod/StoreLogo.png similarity index 100% rename from packages/desktop-electron/icons/prod/StoreLogo.png rename to packages/desktop/icons/prod/StoreLogo.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml b/packages/desktop/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml rename to packages/desktop/icons/prod/android/mipmap-anydpi-v26/ic_launcher.xml diff --git a/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher.png b/packages/desktop/icons/prod/android/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher.png rename to packages/desktop/icons/prod/android/mipmap-hdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/desktop/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png rename to packages/desktop/icons/prod/android/mipmap-hdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_round.png b/packages/desktop/icons/prod/android/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-hdpi/ic_launcher_round.png rename to packages/desktop/icons/prod/android/mipmap-hdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher.png b/packages/desktop/icons/prod/android/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher.png rename to packages/desktop/icons/prod/android/mipmap-mdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/desktop/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png rename to packages/desktop/icons/prod/android/mipmap-mdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_round.png b/packages/desktop/icons/prod/android/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-mdpi/ic_launcher_round.png rename to packages/desktop/icons/prod/android/mipmap-mdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher.png b/packages/desktop/icons/prod/android/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher.png rename to packages/desktop/icons/prod/android/mipmap-xhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/desktop/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/prod/android/mipmap-xhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png b/packages/desktop/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png rename to packages/desktop/icons/prod/android/mipmap-xhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher.png b/packages/desktop/icons/prod/android/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher.png rename to packages/desktop/icons/prod/android/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/desktop/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/prod/android/mipmap-xxhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/desktop/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png rename to packages/desktop/icons/prod/android/mipmap-xxhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png b/packages/desktop/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png rename to packages/desktop/icons/prod/android/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/desktop/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png rename to packages/desktop/icons/prod/android/mipmap-xxxhdpi/ic_launcher_foreground.png diff --git a/packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/desktop/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from packages/desktop-electron/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png rename to packages/desktop/icons/prod/android/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/packages/desktop-electron/icons/prod/android/values/ic_launcher_background.xml b/packages/desktop/icons/prod/android/values/ic_launcher_background.xml similarity index 100% rename from packages/desktop-electron/icons/prod/android/values/ic_launcher_background.xml rename to packages/desktop/icons/prod/android/values/ic_launcher_background.xml diff --git a/packages/desktop-electron/icons/prod/dock.png b/packages/desktop/icons/prod/dock.png similarity index 100% rename from packages/desktop-electron/icons/prod/dock.png rename to packages/desktop/icons/prod/dock.png diff --git a/packages/desktop-electron/icons/prod/icon.icns b/packages/desktop/icons/prod/icon.icns similarity index 100% rename from packages/desktop-electron/icons/prod/icon.icns rename to packages/desktop/icons/prod/icon.icns diff --git a/packages/desktop-electron/icons/prod/icon.ico b/packages/desktop/icons/prod/icon.ico similarity index 100% rename from packages/desktop-electron/icons/prod/icon.ico rename to packages/desktop/icons/prod/icon.ico diff --git a/packages/desktop-electron/icons/prod/icon.png b/packages/desktop/icons/prod/icon.png similarity index 100% rename from packages/desktop-electron/icons/prod/icon.png rename to packages/desktop/icons/prod/icon.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@1x.png b/packages/desktop/icons/prod/ios/AppIcon-20x20@1x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-20x20@1x.png rename to packages/desktop/icons/prod/ios/AppIcon-20x20@1x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x-1.png b/packages/desktop/icons/prod/ios/AppIcon-20x20@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x-1.png rename to packages/desktop/icons/prod/ios/AppIcon-20x20@2x-1.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x.png b/packages/desktop/icons/prod/ios/AppIcon-20x20@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-20x20@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-20x20@2x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-20x20@3x.png b/packages/desktop/icons/prod/ios/AppIcon-20x20@3x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-20x20@3x.png rename to packages/desktop/icons/prod/ios/AppIcon-20x20@3x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@1x.png b/packages/desktop/icons/prod/ios/AppIcon-29x29@1x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-29x29@1x.png rename to packages/desktop/icons/prod/ios/AppIcon-29x29@1x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x-1.png b/packages/desktop/icons/prod/ios/AppIcon-29x29@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x-1.png rename to packages/desktop/icons/prod/ios/AppIcon-29x29@2x-1.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x.png b/packages/desktop/icons/prod/ios/AppIcon-29x29@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-29x29@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-29x29@2x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-29x29@3x.png b/packages/desktop/icons/prod/ios/AppIcon-29x29@3x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-29x29@3x.png rename to packages/desktop/icons/prod/ios/AppIcon-29x29@3x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@1x.png b/packages/desktop/icons/prod/ios/AppIcon-40x40@1x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-40x40@1x.png rename to packages/desktop/icons/prod/ios/AppIcon-40x40@1x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x-1.png b/packages/desktop/icons/prod/ios/AppIcon-40x40@2x-1.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x-1.png rename to packages/desktop/icons/prod/ios/AppIcon-40x40@2x-1.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x.png b/packages/desktop/icons/prod/ios/AppIcon-40x40@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-40x40@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-40x40@2x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-40x40@3x.png b/packages/desktop/icons/prod/ios/AppIcon-40x40@3x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-40x40@3x.png rename to packages/desktop/icons/prod/ios/AppIcon-40x40@3x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-512@2x.png b/packages/desktop/icons/prod/ios/AppIcon-512@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-512@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-512@2x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@2x.png b/packages/desktop/icons/prod/ios/AppIcon-60x60@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-60x60@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-60x60@2x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-60x60@3x.png b/packages/desktop/icons/prod/ios/AppIcon-60x60@3x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-60x60@3x.png rename to packages/desktop/icons/prod/ios/AppIcon-60x60@3x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@1x.png b/packages/desktop/icons/prod/ios/AppIcon-76x76@1x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-76x76@1x.png rename to packages/desktop/icons/prod/ios/AppIcon-76x76@1x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-76x76@2x.png b/packages/desktop/icons/prod/ios/AppIcon-76x76@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-76x76@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-76x76@2x.png diff --git a/packages/desktop-electron/icons/prod/ios/AppIcon-83.5x83.5@2x.png b/packages/desktop/icons/prod/ios/AppIcon-83.5x83.5@2x.png similarity index 100% rename from packages/desktop-electron/icons/prod/ios/AppIcon-83.5x83.5@2x.png rename to packages/desktop/icons/prod/ios/AppIcon-83.5x83.5@2x.png diff --git a/packages/desktop/index.html b/packages/desktop/index.html deleted file mode 100644 index ce2775a70..000000000 --- a/packages/desktop/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - OpenCode - - - - - - - - - - - - - -
-