diff --git a/.opencode/command/changelog.md b/.opencode/command/changelog.md index 85dbf9b97b..271e7eba18 100644 --- a/.opencode/command/changelog.md +++ b/.opencode/command/changelog.md @@ -7,15 +7,17 @@ create UPCOMING_CHANGELOG.md it should have sections ``` -# TUI +## TUI -# Desktop +## Desktop -# Core +## Core -# Misc +## Misc ``` -go through each PR merged since the last tag +fetch the latest github release for this repository to determine the last release version. + +find each PR that was merged since the last release for each PR spawn a subagent to summarize what the PR was about. focus on user facing changes. if it was entirely internal or code related you can ignore it. also skip docs updates. each subagent should append its summary to UPCOMING_CHANGELOG.md into the appropriate section. diff --git a/bun.lock b/bun.lock index a37a5f98dd..610f3d4558 100644 --- a/bun.lock +++ b/bun.lock @@ -26,7 +26,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -113,7 +113,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -140,7 +140,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -164,7 +164,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -188,7 +188,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -221,7 +221,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -252,7 +252,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -281,7 +281,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -297,7 +297,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.3.2", + "version": "1.3.3", "bin": { "opencode": "./bin/opencode", }, @@ -422,7 +422,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -446,7 +446,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.3.2", + "version": "1.3.3", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -457,7 +457,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -492,7 +492,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -516,6 +516,7 @@ "motion-dom": "12.34.3", "motion-utils": "12.29.2", "remeda": "catalog:", + "remend": "catalog:", "shiki": "catalog:", "solid-js": "catalog:", "solid-list": "catalog:", @@ -538,7 +539,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "zod": "catalog:", }, @@ -549,7 +550,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.3.2", + "version": "1.3.3", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -599,7 +600,7 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", - "@effect/platform-node": "4.0.0-beta.35", + "@effect/platform-node": "4.0.0-beta.37", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@octokit/rest": "22.0.0", @@ -623,7 +624,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.35", + "effect": "4.0.0-beta.37", "fuzzysort": "3.1.0", "hono": "4.10.7", "hono-openapi": "1.1.2", @@ -631,6 +632,7 @@ "marked": "17.0.1", "marked-shiki": "1.2.1", "remeda": "2.26.0", + "remend": "1.3.0", "shiki": "3.20.0", "solid-js": "1.9.10", "solid-list": "0.3.0", @@ -981,9 +983,9 @@ "@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.35", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.35", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.35", "ioredis": "^5.7.0" } }, "sha512-HPc2xZASl9F9y/xJ01bQgFD6Jf9XP4Fcv/BlVTvG0Yr/uN63lwKZYr/VXor5K5krHfBDeCBD8y7/SICPYZoq3A=="], + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.37", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.37", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.37", "ioredis": "^5.7.0" } }, "sha512-dCfTNYGAT+1K+nu/0jw3FL/0DJXcobZCJs9SD5XJbj1DewWPhR9/AptP6zLGj8vdP8hXem6Aa53nze3HSujW3w=="], - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.35", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.35" } }, "sha512-9bPqNV988itKJ7MQoJuzmR014DB9EZRDOnhJt/+iJlb8qLoR9HnCzNJb9gfBdYhFmVYc8DMsQxG81rdJzpv9tg=="], + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.40", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.40" } }, "sha512-WMRVG7T8ZDALKCOacsx2ZZj3Ccaoq8YGeD9q7ZL4q8RwQv8Nmrl+4+KZl95/zHCqXzgK9oUJOlBfQ7CZr6PQOQ=="], "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], @@ -2797,7 +2799,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@4.0.0-beta.35", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-64j8dgJmoEMeq6Y3WLYcZIRqPZ5E/lqnULCf6QW5te3hQ/sa13UodWLGwBEviEqBoq72U8lArhVX+T7ntzhJGQ=="], + "effect": ["effect@4.0.0-beta.37", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-AVMXXtb6n62W4uvo1EvT7FJ41HfDvQRX8IY2FGPvfP361dtBArKK2JtE5vmFXTsxkW90WUdvJZYpVATGIzr/BA=="], "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], @@ -4147,6 +4149,8 @@ "remeda": ["remeda@2.26.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-lmNNwtaC6Co4m0WTTNoZ/JlpjEqAjPZO0+czC9YVRQUpkbS4x8Hmh+Mn9HPfJfiXqUQ5IXXgSXSOB2pBKAytdA=="], + "remend": ["remend@1.3.0", "", {}, "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw=="], + "request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="], "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], diff --git a/nix/hashes.json b/nix/hashes.json index b6f0fed892..5eaac2de42 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-0VwVhbOtK1r16cVSZcHaI/8fUPc6aYQiUnh7Q3bSHqs=", - "aarch64-linux": "sha256-z5b234MIS0QqDYLopyaT2hd9CAtEbcSo28y0eMfPsBs=", - "aarch64-darwin": "sha256-sn16mtZIhF9OSBrfAHpDCJO6Nt19mdoxvYAOnwWgwDk=", - "x86_64-darwin": "sha256-FaZpwGuWzfypA28ct86xAnW2RuFFUiXjPkr5wVTLN/o=" + "x86_64-linux": "sha256-a2eTu0ISjqPuojkNPnPXzVb/PLlDvw/DXDvmxi9RD5k=", + "aarch64-linux": "sha256-yLaTXRzZ7M/6j2WDP+IL1YCY3+rYY4Qmq3xTDatNzD0=", + "aarch64-darwin": "sha256-uGSVe8S/QvnW+RCI/CxzrlfAAJ1YA+NrhzRE0GTcnvE=", + "x86_64-darwin": "sha256-tplWx2tLg6jWvOBmM41lODJV8pHpkAm4HKWRG7lpkcU=" } } diff --git a/package.json b/package.json index 915e2ef0ac..40ab8ceaf6 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "packages/slack" ], "catalog": { - "@effect/platform-node": "4.0.0-beta.35", + "@effect/platform-node": "4.0.0-beta.37", "@types/bun": "1.3.11", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", @@ -45,7 +45,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.35", + "effect": "4.0.0-beta.37", "ai": "5.0.124", "hono": "4.10.7", "hono-openapi": "1.1.2", @@ -53,6 +53,7 @@ "luxon": "3.6.1", "marked": "17.0.1", "marked-shiki": "1.2.1", + "remend": "1.3.0", "@playwright/test": "1.51.0", "typescript": "5.8.2", "@typescript/native-preview": "7.0.0-dev.20251207.1", diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index 90af177ed1..efd370d395 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -465,10 +465,13 @@ export async function waitSession(page: Page, input: { directory: string; sessio if (!slug) return false const resolved = await resolveSlug(slug).catch(() => undefined) if (!resolved || resolved.directory !== target) return false - if (input.sessionID && sessionIDFromUrl(page.url()) !== input.sessionID) return false + const current = sessionIDFromUrl(page.url()) + if (input.sessionID && current !== input.sessionID) return false + if (!input.sessionID && current) return false const state = await probeSession(page) if (input.sessionID && (!state || state.sessionID !== input.sessionID)) return false + if (!input.sessionID && state?.sessionID) return false if (state?.dir) { const dir = await resolveDirectory(state.dir).catch(() => state.dir ?? "") if (dir !== target) return false diff --git a/packages/app/e2e/selectors.ts b/packages/app/e2e/selectors.ts index 80b6c473d6..32e4ecd8a4 100644 --- a/packages/app/e2e/selectors.ts +++ b/packages/app/e2e/selectors.ts @@ -19,7 +19,8 @@ export const promptVariantSelector = '[data-component="prompt-variant-control"]' export const settingsLanguageSelectSelector = '[data-action="settings-language"]' export const settingsColorSchemeSelector = '[data-action="settings-color-scheme"]' export const settingsThemeSelector = '[data-action="settings-theme"]' -export const settingsFontSelector = '[data-action="settings-font"]' +export const settingsCodeFontSelector = '[data-action="settings-code-font"]' +export const settingsUIFontSelector = '[data-action="settings-ui-font"]' export const settingsNotificationsAgentSelector = '[data-action="settings-notifications-agent"]' export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]' export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]' diff --git a/packages/app/e2e/session/session-composer-dock.spec.ts b/packages/app/e2e/session/session-composer-dock.spec.ts index 5b2e8a8c6f..f083bf3597 100644 --- a/packages/app/e2e/session/session-composer-dock.spec.ts +++ b/packages/app/e2e/session/session-composer-dock.spec.ts @@ -93,7 +93,7 @@ async function todoDock(page: any, sessionID: string) { const write = async (driver: ComposerDriverState | undefined) => { await page.evaluate( - (input) => { + (input: { event: string; sessionID: string; driver: ComposerDriverState | undefined }) => { const win = window as ComposerWindow const composer = win.__opencode_e2e?.composer if (!composer?.enabled) throw new Error("Composer e2e driver is not enabled") @@ -118,7 +118,7 @@ async function todoDock(page: any, sessionID: string) { } const read = () => - page.evaluate((sessionID) => { + page.evaluate((sessionID: string) => { const win = window as ComposerWindow return win.__opencode_e2e?.composer?.sessions?.[sessionID]?.probe ?? null }, sessionID) as Promise @@ -186,6 +186,8 @@ async function withMockPermission( opts: { child?: any } | undefined, fn: (state: { resolved: () => Promise }) => Promise, ) { + const listUrl = /\/permission(?:\?.*)?$/ + const replyUrls = [/\/session\/[^/]+\/permissions\/[^/?]+(?:\?.*)?$/, /\/permission\/[^/]+\/reply(?:\?.*)?$/] let pending = [ { ...request, @@ -204,7 +206,8 @@ async function withMockPermission( const reply = async (route: any) => { const url = new URL(route.request().url()) - const id = url.pathname.split("/").pop() + const parts = url.pathname.split("/").filter(Boolean) + const id = parts.at(-1) === "reply" ? parts.at(-2) : parts.at(-1) pending = pending.filter((item) => item.id !== id) await route.fulfill({ status: 200, @@ -213,8 +216,10 @@ async function withMockPermission( }) } - await page.route("**/permission", list) - await page.route("**/session/*/permissions/*", reply) + await page.route(listUrl, list) + for (const item of replyUrls) { + await page.route(item, reply) + } const sessionList = opts?.child ? async (route: any) => { @@ -242,8 +247,10 @@ async function withMockPermission( try { return await fn(state) } finally { - await page.unroute("**/permission", list) - await page.unroute("**/session/*/permissions/*", reply) + await page.unroute(listUrl, list) + for (const item of replyUrls) { + await page.unroute(item, reply) + } if (sessionList) await page.unroute("**/session?*", sessionList) } } diff --git a/packages/app/e2e/session/session-model-persistence.spec.ts b/packages/app/e2e/session/session-model-persistence.spec.ts index b758a3b3d8..36cbb0fbf1 100644 --- a/packages/app/e2e/session/session-model-persistence.spec.ts +++ b/packages/app/e2e/session/session-model-persistence.spec.ts @@ -28,7 +28,17 @@ type Footer = { type Probe = { dir?: string sessionID?: string - model?: { providerID: string; modelID: string } + agent?: string + model?: { providerID: string; modelID: string; name?: string } + variant?: string | null + pick?: { + agent?: string + model?: { providerID: string; modelID: string } + variant?: string | null + } + variants?: string[] + models?: Array<{ providerID: string; modelID: string; name: string }> + agents?: Array<{ name: string }> } const escape = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") @@ -50,6 +60,86 @@ async function probe(page: Page): Promise { }) } +async function currentModel(page: Page) { + await expect.poll(() => probe(page).then(modelKey), { timeout: 30_000 }).not.toBe(null) + const value = await probe(page).then(modelKey) + if (!value) throw new Error("Failed to resolve current model key") + return value +} + +async function waitControl(page: Page, key: "setAgent" | "setModel" | "setVariant") { + await expect + .poll( + () => + page.evaluate((key) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: Record + } + } + } + return !!win.__opencode_e2e?.model?.controls?.[key] + }, key), + { timeout: 30_000 }, + ) + .toBe(true) +} + +async function pickAgent(page: Page, value: string) { + await waitControl(page, "setAgent") + await page.evaluate((value) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: { + setAgent?: (value: string | undefined) => void + } + } + } + } + const fn = win.__opencode_e2e?.model?.controls?.setAgent + if (!fn) throw new Error("Model e2e agent control is not enabled") + fn(value) + }, value) +} + +async function pickModel(page: Page, value: { providerID: string; modelID: string }) { + await waitControl(page, "setModel") + await page.evaluate((value) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: { + setModel?: (value: { providerID: string; modelID: string } | undefined) => void + } + } + } + } + const fn = win.__opencode_e2e?.model?.controls?.setModel + if (!fn) throw new Error("Model e2e model control is not enabled") + fn(value) + }, value) +} + +async function pickVariant(page: Page, value: string) { + await waitControl(page, "setVariant") + await page.evaluate((value) => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + controls?: { + setVariant?: (value: string | undefined) => void + } + } + } + } + const fn = win.__opencode_e2e?.model?.controls?.setVariant + if (!fn) throw new Error("Model e2e variant control is not enabled") + fn(value) + }, value) +} + async function read(page: Page): Promise