diff --git a/.opencode/command/changelog.md b/.opencode/command/changelog.md index 85dbf9b97bd..271e7eba186 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 a37a5f98ddc..610f3d45587 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 b6f0fed8924..5eaac2de421 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 915e2ef0acb..40ab8ceaf67 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 90af177ed15..efd370d395c 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 80b6c473d6e..32e4ecd8a4e 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 5b2e8a8c6f1..f083bf35979 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 b758a3b3d89..36cbb0fbf19 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