diff --git a/.github/workflows/docs-locale-sync.yml b/.github/workflows/docs-locale-sync.yml index fff2ec4292..9689eee6d2 100644 --- a/.github/workflows/docs-locale-sync.yml +++ b/.github/workflows/docs-locale-sync.yml @@ -9,7 +9,8 @@ on: jobs: sync-locales: - if: github.actor != 'opencode-agent[bot]' + if: false + #if: github.actor != 'opencode-agent[bot]' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write @@ -34,7 +35,7 @@ jobs: - name: Compute changed English docs id: changes run: | - FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'packages/web/src/content/docs/*.mdx' || true) + FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- ':(glob)packages/web/src/content/docs/*.mdx' || true) if [ -z "$FILES" ]; then echo "has_changes=false" >> "$GITHUB_OUTPUT" echo "No English docs changed in push range" diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml index 9d94682f11..6b5b3929ad 100644 --- a/.github/workflows/nix-hashes.yml +++ b/.github/workflows/nix-hashes.yml @@ -17,6 +17,10 @@ on: - "patches/**" - ".github/workflows/nix-hashes.yml" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # Native runners required: bun install cross-compilation flags (--os/--cpu) # do not produce byte-identical node_modules as native installs. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c58be30ab..f184d1ddb3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,6 +100,9 @@ jobs: run: bun --cwd packages/app test:e2e:local env: CI: true + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + OPENCODE_E2E_MODEL: opencode/claude-haiku-4-5 + OPENCODE_E2E_REQUIRE_PAID: "true" timeout-minutes: 30 - name: Upload Playwright artifacts diff --git a/.gitignore b/.gitignore index c287d91ac1..52a5a04596 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ target # Local dev files opencode-dev +UPCOMING_CHANGELOG.md logs/ *.bun-build tsconfig.tsbuildinfo diff --git a/.opencode/command/changelog.md b/.opencode/command/changelog.md index 271e7eba18..4cd30a704a 100644 --- a/.opencode/command/changelog.md +++ b/.opencode/command/changelog.md @@ -1,23 +1,46 @@ --- -model: opencode/kimi-k2.5 +model: opencode/gpt-5.4 --- -create UPCOMING_CHANGELOG.md +Create `UPCOMING_CHANGELOG.md` from the structured changelog input below. +If `UPCOMING_CHANGELOG.md` already exists, ignore its current contents completely. +Do not preserve, merge, or reuse text from the existing file. -it should have sections +The input already contains the exact commit range since the last non-draft release. +The commits are already filtered to the release-relevant packages and grouped into +the release sections. Do not fetch GitHub releases, PRs, or build your own commit list. +The input may also include a `## Community Contributors Input` section. -``` -## TUI +Before writing any entry you keep, inspect the real diff with +`git show --stat --format='' ` or `git show --format='' ` so you can +understand the actual code changes and not just the commit message (they may be misleading). +Do not use `git log` or author metadata when deciding attribution. -## Desktop +Rules: -## Core +- Write the final file with sections in this order: + `## Core`, `## TUI`, `## Desktop`, `## SDK`, `## Extensions` +- Only include sections that have at least one notable entry +- Keep one bullet per commit you keep +- Skip commits that are entirely internal, CI, tests, refactors, or otherwise not user-facing +- Start each bullet with a capital letter +- Prefer what changed for users over what code changed internally +- Do not copy raw commit prefixes like `fix:` or `feat:` or trailing PR numbers like `(#123)` +- Community attribution is deterministic: only preserve an existing `(@username)` suffix from the changelog input +- If an input bullet has no `(@username)` suffix, do not add one +- Never add a new `(@username)` suffix from `git show`, commit authors, names, or email addresses +- If no notable entries remain and there is no contributor block, write exactly `No notable changes.` +- If no notable entries remain but there is a contributor block, omit all release sections and return only the contributor block +- If the input contains `## Community Contributors Input`, append the block below that heading to the end of the final file verbatim +- Do not add, remove, rewrite, or reorder contributor names or commit titles in that block +- Do not derive the thank-you section from the main summary bullets +- Do not include the heading `## Community Contributors Input` in the final file +- Focus on writing the least words to get your point across - users will skim read the changelog, so we should be precise -## Misc -``` +**Importantly, the changelog is for users (who are at least slightly technical), they may use the TUI, Desktop, SDK, Plugins and so forth. Be thorough in understanding flow on effects may not be immediately apparent. e.g. a package upgrade looks internal but may patch a bug. Or a refactor may also stabilise some race condition that fixes bugs for users. The PR title/body + commit message will give you the authors context, usually containing the outcome not just technical detail** -fetch the latest github release for this repository to determine the last release version. + -find each PR that was merged since the last release +!`bun script/raw-changelog.ts $ARGUMENTS` -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/.opencode/plugins/tui-smoke.tsx b/.opencode/plugins/tui-smoke.tsx index deb3c3e3e4..febfc3e371 100644 --- a/.opencode/plugins/tui-smoke.tsx +++ b/.opencode/plugins/tui-smoke.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource @opentui/solid */ -import { useKeyboard, useTerminalDimensions } from "@opentui/solid" +import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid" import { RGBA, VignetteEffect } from "@opentui/core" import type { TuiKeybindSet, @@ -615,7 +615,7 @@ const Modal = (props: { ) } -const home = (input: Cfg): TuiSlotPlugin => ({ +const home = (api: TuiPluginApi, input: Cfg) => ({ slots: { home_logo(ctx) { const map = ctx.theme.current @@ -649,6 +649,36 @@ const home = (input: Cfg): TuiSlotPlugin => ({ ) }, + home_prompt(ctx, value) { + const skin = look(ctx.theme.current) + type Prompt = (props: { + workspaceID?: string + hint?: JSX.Element + placeholders?: { + normal?: string[] + shell?: string[] + } + }) => JSX.Element + if (!("Prompt" in api.ui)) return null + const view = api.ui.Prompt + if (typeof view !== "function") return null + const Prompt = view as Prompt + const normal = [ + `[SMOKE] route check for ${input.label}`, + "[SMOKE] confirm home_prompt slot override", + "[SMOKE] verify api.ui.Prompt rendering", + ] + const shell = ["printf '[SMOKE] home prompt\n'", "git status --short", "bun --version"] + const Hint = ( + + + smoke home prompt + + + ) + + return + }, home_bottom(ctx) { const skin = look(ctx.theme.current) const text = "extra content in the unified home bottom slot" @@ -706,8 +736,8 @@ const block = (input: Cfg, order: number, title: string, text: string): TuiSlotP }, }) -const slot = (input: Cfg): TuiSlotPlugin[] => [ - home(input), +const slot = (api: TuiPluginApi, input: Cfg): TuiSlotPlugin[] => [ + home(api, input), block(input, 50, "Smoke above", "renders above internal sidebar blocks"), block(input, 250, "Smoke between", "renders between internal sidebar blocks"), block(input, 650, "Smoke below", "renders below internal sidebar blocks"), @@ -848,7 +878,7 @@ const tui: TuiPlugin = async (api, options, meta) => { ]) reg(api, value, keys) - for (const item of slot(value)) { + for (const item of slot(api, value)) { api.slots.register(item) } } diff --git a/bun.lock b/bun.lock index ad047015c7..e53461ff49 100644 --- a/bun.lock +++ b/bun.lock @@ -44,7 +44,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -97,7 +97,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -131,7 +131,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -158,7 +158,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -182,7 +182,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -206,7 +206,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -239,7 +239,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -270,7 +270,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -299,7 +299,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -367,7 +367,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.3.3", + "version": "1.3.10", "bin": { "opencode": "./bin/opencode", }, @@ -393,7 +393,7 @@ "@ai-sdk/provider-utils": "4.0.21", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.74", + "@ai-sdk/xai": "3.0.75", "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/platform-node": "catalog:", @@ -408,8 +408,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "2.3.3", - "@opentui/core": "0.1.91", - "@opentui/solid": "0.1.91", + "@opentui/core": "0.1.93", + "@opentui/solid": "0.1.93", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -439,7 +439,7 @@ "mime-types": "3.0.2", "minimatch": "10.0.3", "open": "10.1.2", - "opencode-gitlab-auth": "2.0.0", + "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", "opentui-spinner": "0.0.6", "partial-json": "0.1.7", @@ -449,6 +449,7 @@ "solid-js": "catalog:", "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", + "tree-sitter-powershell": "0.25.10", "turndown": "7.2.0", "ulid": "catalog:", "vscode-jsonrpc": "8.2.1", @@ -494,22 +495,22 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", }, "devDependencies": { - "@opentui/core": "0.1.91", - "@opentui/solid": "0.1.91", + "@opentui/core": "0.1.93", + "@opentui/solid": "0.1.93", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.1.91", - "@opentui/solid": ">=0.1.91", + "@opentui/core": ">=0.1.93", + "@opentui/solid": ">=0.1.93", }, "optionalPeers": [ "@opentui/core", @@ -528,7 +529,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.3.3", + "version": "1.3.10", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -539,7 +540,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -574,7 +575,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -621,7 +622,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "zod": "catalog:", }, @@ -632,7 +633,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.3.3", + "version": "1.3.10", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -665,8 +666,9 @@ }, }, "trustedDependencies": [ - "electron", "esbuild", + "tree-sitter-powershell", + "electron", "web-tree-sitter", "tree-sitter-bash", ], @@ -789,7 +791,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.74", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HDDLsT+QrzE3c2QZLRV/HKAwMtXDb0PMDdk1PYUXLJ3r9Qv76zGKGyvJLX7Pu6c8TOHD1mwLrOVYrsTpC/eTMw=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.75", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-V8UKK4fNpI9cnrtsZBvUp9O9J6Y9fTKBRoSLyEaNGPirACewixmLDbXsSgAeownPVWiWpK34bFysd+XouI5Ywg=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -1765,21 +1767,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.91", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.91", "@opentui/core-darwin-x64": "0.1.91", "@opentui/core-linux-arm64": "0.1.91", "@opentui/core-linux-x64": "0.1.91", "@opentui/core-win32-arm64": "0.1.91", "@opentui/core-win32-x64": "0.1.91", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-xkuBDChHix3lHESQZTWXnPi0c8aANtg0567te3Am2O9EB3V1afKYdOYRV7RrzC+VBNmkymD8dUN+jzLkEUnAEw=="], + "@opentui/core": ["@opentui/core@0.1.93", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.93", "@opentui/core-darwin-x64": "0.1.93", "@opentui/core-linux-arm64": "0.1.93", "@opentui/core-linux-x64": "0.1.93", "@opentui/core-win32-arm64": "0.1.93", "@opentui/core-win32-x64": "0.1.93", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-HlTM16ZiBKN0mPBNMHSILkSrbzNku6Pg/ovIpVVkEPqLeWeSC2bfZS4Uhc0Ej1sckVVVoU9HKBJanfHvpP+pMg=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.91", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WlIMa832vyjHCJsteWtSDsTAOrOPw/LQjYXVPISwwKo5Puyyl9vWNsF+69eYEyFEh15u8JNNrOPK98nlXq8SOA=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.93", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4I2mwhXLqRNUv7tu88hA6cBGaGpLZXkAa8W0VqBiGDV+Tx337x4T+vbQ7G57OwKXT787oTrEOF9rOOrGLov6qw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.91", "", { "os": "darwin", "cpu": "x64" }, "sha512-nFZgQrdGtEzf5GXg4YxtDzxHvSwAig2G4Qf6ySN6sU9f9eaB1NJNhOVYLNJHBVEs5qOamBee+nXYEtG6zInIFQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.93", "", { "os": "darwin", "cpu": "x64" }, "sha512-jvYMgcg47a5qLhSv1DnQiafEWBQ1UukGutmsYV1TvNuhWtuDXYLVy2AhKIHPzbB9JNrV0IpjbxUC8QnJaP3n8g=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.91", "", { "os": "linux", "cpu": "arm64" }, "sha512-vXAcHZaS3QzEXYyvM9KoE0juSOMPPPdNrV5Fo4HAbI5BXGCkMNQJoN0j0EzoO9xwfsO+EulRSHCLVTNkvI4n8Q=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.93", "", { "os": "linux", "cpu": "arm64" }, "sha512-bvFqRcPftmg14iYmMc3d63XC9rhe4yF7pJRApH6klLBKp27WX/LU0iSO4mvyX7qhy65gcmyy4Sj9dl5jNJ+vlA=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.91", "", { "os": "linux", "cpu": "x64" }, "sha512-rAJ9sOvvI9eoWHjVj6TLPDRqYPYISmfCm2TDxi67BO27+E7naJANHIIxMC7yhPAmwBof7plioL2lwl2UFXAoXw=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.93", "", { "os": "linux", "cpu": "x64" }, "sha512-/wJXhwtNxdcpshrRl1KouyGE54ODAHxRQgBHtnlM/F4bB8cjzOlq2Yc+5cv5DxRz4Q0nQZFCPefwpg2U6ZwNdA=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.91", "", { "os": "win32", "cpu": "arm64" }, "sha512-teLe7uHvPnD/lOwTwZp2lUFfeT27dk6ZSLWk8hrhsAJ/Y0MyoaCUHAsg3nZ/p+I3pie5aZUR1f0vrJfaZ8ukJw=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.93", "", { "os": "win32", "cpu": "arm64" }, "sha512-g3PQobfM2yFPSzkBKRKFp8FgTG4ulWyJcU+GYXjyYmxQIT+ZbOU7UfR//ImRq3/FxUAfUC/MhC6WwjqccjEqBw=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.91", "", { "os": "win32", "cpu": "x64" }, "sha512-Odx9S1NYp3I2jgy5aj5k3/wb3M+yChEK7k8UUxxFt4R37V1/um8n6Cxw4nfid6T2C45KDGJ/0BYe6lGugJlnSg=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.93", "", { "os": "win32", "cpu": "x64" }, "sha512-Spllte2W7q+WfB1zVHgHilVJNp+jpp77PkkxTWyMQNvT7vJNt9LABMNjGTGiJBBMkAuKvO0GgFNKxrda7tFKrQ=="], - "@opentui/solid": ["@opentui/solid@0.1.91", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.91", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-phqiOcmTgNy7aG7s3P6zyatrBc1f6DkuLDJmGqy6R9QuoS4Mn9MKdNQe6Ick03xRAZuaS6ZdG3kueNxIlUMTCA=="], + "@opentui/solid": ["@opentui/solid@0.1.93", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.93", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-Qx+4qoLSjnRGoo/YY4sZJMyXj09Y5kaAMpVO+65Ax58MMj4TjABN4bOOiRT2KV7sKOMTjxiAgXAIaBuqBBJ0Qg=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], @@ -4495,7 +4497,7 @@ "opencode": ["opencode@workspace:packages/opencode"], - "opencode-gitlab-auth": ["opencode-gitlab-auth@2.0.0", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-jmZOOvYIurRScQCtdBqIW5HbP1JbmIiq7UtI7NGgn2vjke46g9d4NVPBg5/ZmFFVIBwZcgyFgJ7b8kGEOR9ujA=="], + "opencode-gitlab-auth": ["opencode-gitlab-auth@2.0.1", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-1EMZHdbADLMVaTVLQ6C/V8uVMDr6MP++osj2lmOecowtn46AafP/w6ADkV4AN/ddjA1rob5cWpMuf/iME6DI6A=="], "opencode-poe-auth": ["opencode-poe-auth@0.0.1", "", { "dependencies": { "open": "^10.0.0", "poe-oauth": "*" }, "peerDependencies": { "@opencode-ai/plugin": "*" } }, "sha512-cXqTlS6AXHzo1oBdosnxbT47ZJEZ9WXn050X8Re6wZ1vaNnTpB/l2fMQt90evT7RBK0fB8UjXQUDMKyd7bbiqg=="], @@ -5283,6 +5285,8 @@ "tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="], + "tree-sitter-powershell": ["tree-sitter-powershell@0.25.10", "", { "dependencies": { "node-addon-api": "^7.1.0", "node-gyp-build": "^4.8.0" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-bEt8QoySpGFnU3aa8WedQyNMaN6aTwy/WUbvIVt0JSKF+BbJoSHNHu+wCbhj7xLMsfB0AuffmiJm+B8gzva8Lg=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], @@ -6309,6 +6313,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.74", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HDDLsT+QrzE3c2QZLRV/HKAwMtXDb0PMDdk1PYUXLJ3r9Qv76zGKGyvJLX7Pu6c8TOHD1mwLrOVYrsTpC/eTMw=="], + "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], diff --git a/nix/hashes.json b/nix/hashes.json index fcb011df4e..ec1fc2f600 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-5VHEo9GCBP+MeLMoWqSuJLAX/qwGLdFjZe20yatgogM=", - "aarch64-linux": "sha256-hn+V2UpoCj1ddKcq1ySGOMRVvsd3T8sqgpd6nHYfUoA=", - "aarch64-darwin": "sha256-Qctkv6AHDrl+qdB1L+DeqLREeWm6BQqtVCK4tibIMCc=", - "x86_64-darwin": "sha256-YHHnow2dqtKOsjQvbyKk6HQmTo8cAv8frgOfD5aS3h8=" + "x86_64-linux": "sha256-UuVbB5lTRB4bIcaKMc8CLSbQW7m9EjXgxYvxp/uO7Co=", + "aarch64-linux": "sha256-8D7ReLRVb7NDd5PQTVxFhRLmlLbfjK007XgIhhpNKoE=", + "aarch64-darwin": "sha256-M+z7C/eXfVqwDiGiiwKo/LT/m4dvCjL1Pblsr1kxoyI=", + "x86_64-darwin": "sha256-RzZS6GMwYVDPK0W+K/mlebixNMs2+JRkMG9n8OFhd0c=" } } diff --git a/nix/node_modules.nix b/nix/node_modules.nix index 6c188c07cf..e10e85d2fe 100644 --- a/nix/node_modules.nix +++ b/nix/node_modules.nix @@ -20,7 +20,7 @@ let in stdenvNoCC.mkDerivation { pname = "opencode-node_modules"; - version = "${packageJson.version}-${rev}"; + version = "${packageJson.version}+${lib.replaceString "-" "." rev}"; src = lib.fileset.toSource { root = ../.; @@ -54,6 +54,7 @@ stdenvNoCC.mkDerivation { --filter '!./' \ --filter './packages/opencode' \ --filter './packages/desktop' \ + --filter './packages/app' \ --frozen-lockfile \ --ignore-scripts \ --no-progress diff --git a/nix/opencode.nix b/nix/opencode.nix index b7d6f95947..b629d0b554 100644 --- a/nix/opencode.nix +++ b/nix/opencode.nix @@ -3,6 +3,7 @@ stdenvNoCC, callPackage, bun, + nodejs, sysctl, makeBinaryWrapper, models-dev, @@ -19,6 +20,7 @@ stdenvNoCC.mkDerivation (finalAttrs: { nativeBuildInputs = [ bun + nodejs # for patchShebangs node_modules installShellFiles makeBinaryWrapper models-dev @@ -29,6 +31,8 @@ stdenvNoCC.mkDerivation (finalAttrs: { runHook preConfigure cp -R ${finalAttrs.node_modules}/. . + patchShebangs node_modules + patchShebangs packages/*/node_modules runHook postConfigure ''; diff --git a/package.json b/package.json index 3b2feedf31..2bb1a95391 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "protobufjs", "tree-sitter", "tree-sitter-bash", + "tree-sitter-powershell", "web-tree-sitter", "electron" ], diff --git a/packages/app/e2e/fixtures.ts b/packages/app/e2e/fixtures.ts index 7232df6877..ca06858a45 100644 --- a/packages/app/e2e/fixtures.ts +++ b/packages/app/e2e/fixtures.ts @@ -15,6 +15,16 @@ import { createSdk, dirSlug, getWorktree, sessionPath } from "./utils" export const settingsKey = "settings.v3" +const seedModel = (() => { + const [providerID = "opencode", modelID = "big-pickle"] = ( + process.env.OPENCODE_E2E_MODEL ?? "opencode/big-pickle" + ).split("/") + return { + providerID: providerID || "opencode", + modelID: modelID || "big-pickle", + } +})() + type TestFixtures = { sdk: ReturnType gotoSession: (sessionID?: string) => Promise @@ -125,7 +135,7 @@ export const test = base.extend({ async function seedStorage(page: Page, input: { directory: string; extra?: string[] }) { await seedProjects(page, input) - await page.addInitScript(() => { + await page.addInitScript((model: { providerID: string; modelID: string }) => { const win = window as E2EWindow win.__opencode_e2e = { ...win.__opencode_e2e, @@ -143,12 +153,12 @@ async function seedStorage(page: Page, input: { directory: string; extra?: strin localStorage.setItem( "opencode.global.dat:model", JSON.stringify({ - recent: [{ providerID: "opencode", modelID: "big-pickle" }], + recent: [model], user: [], variant: {}, }), ) - }) + }, seedModel) } export { expect } diff --git a/packages/app/e2e/session/session-review.spec.ts b/packages/app/e2e/session/session-review.spec.ts index 07071239d9..3137bd5559 100644 --- a/packages/app/e2e/session/session-review.spec.ts +++ b/packages/app/e2e/session/session-review.spec.ts @@ -234,6 +234,7 @@ async function fileOverflow(page: Parameters[0]["page"]) { } test("review applies inline comment clicks without horizontal overflow", async ({ page, withProject }) => { + test.skip(true, "Flaky in CI for now.") test.setTimeout(180_000) const tag = `review-comment-${Date.now()}` @@ -283,6 +284,7 @@ test("review applies inline comment clicks without horizontal overflow", async ( }) test("review file comments submit on click without clipping actions", async ({ page, withProject }) => { + test.skip(true, "Flaky in CI for now.") test.setTimeout(180_000) const tag = `review-file-comment-${Date.now()}` diff --git a/packages/app/package.json b/packages/app/package.json index 25fa509911..99b3b8ef47 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.3.3", + "version": "1.3.10", "description": "", "type": "module", "exports": { diff --git a/packages/app/script/e2e-local.ts b/packages/app/script/e2e-local.ts index 4841c4f221..70442d0d76 100644 --- a/packages/app/script/e2e-local.ts +++ b/packages/app/script/e2e-local.ts @@ -71,7 +71,7 @@ const serverEnv = { OPENCODE_E2E_PROJECT_DIR: repoDir, OPENCODE_E2E_SESSION_TITLE: "E2E Session", OPENCODE_E2E_MESSAGE: "Seeded for UI e2e", - OPENCODE_E2E_MODEL: "opencode/gpt-5-nano", + OPENCODE_E2E_MODEL: process.env.OPENCODE_E2E_MODEL ?? "opencode/gpt-5-nano", OPENCODE_CLIENT: "app", OPENCODE_STRICT_CONFIG_DEPS: "true", } satisfies Record diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 1cc7c578d3..c8f72b8d2f 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -624,17 +624,18 @@ export const PromptInput: Component = (props) => { if (!cmd) return promptProbe.select(cmd.id) closePopover() + const images = imageAttachments() if (cmd.type === "custom") { const text = `/${cmd.trigger} ` setEditorText(text) - prompt.set([{ type: "text", content: text, start: 0, end: text.length }], text.length) + prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length) focusEditorEnd() return } clearEditor() - prompt.set([{ type: "text", content: "", start: 0, end: 0 }], 0) + prompt.set([...DEFAULT_PROMPT, ...images], 0) command.trigger(cmd.id, "slash") } diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx index 7ba07b15d0..ef1e52d264 100644 --- a/packages/app/src/pages/session/composer/session-question-dock.tsx +++ b/packages/app/src/pages/session/composer/session-question-dock.tsx @@ -11,6 +11,47 @@ import { useSDK } from "@/context/sdk" const cache = new Map() +function Mark(props: { multi: boolean; picked: boolean; onClick?: (event: MouseEvent) => void }) { + return ( + + ) +} + +function Option(props: { + multi: boolean + picked: boolean + label: string + description?: string + disabled: boolean + onClick: VoidFunction +}) { + return ( + + ) +} + export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit: () => void }> = (props) => { const sdk = useSDK() const language = useLanguage() @@ -41,6 +82,9 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit return language.t("session.question.progress", { current: n, total: total() }) }) + const customLabel = () => language.t("ui.messagePart.option.typeOwnAnswer") + const customPlaceholder = () => language.t("ui.question.custom.placeholder") + const last = createMemo(() => store.tab >= total() - 1) const customUpdate = (value: string, selected: boolean = on()) => { @@ -164,6 +208,13 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit const submit = () => void reply(questions().map((_, i) => store.answers[i] ?? [])) + const answered = (i: number) => { + if ((store.answers[i]?.length ?? 0) > 0) return true + return store.customOn[i] === true && (store.custom[i] ?? "").trim().length > 0 + } + + const picked = (answer: string) => store.answers[store.tab]?.includes(answer) ?? false + const pick = (answer: string, custom: boolean = false) => { setStore("answers", store.tab, [answer]) if (custom) setStore("custom", store.tab, answer) @@ -230,6 +281,24 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit customUpdate(input()) } + const resizeInput = (el: HTMLTextAreaElement) => { + el.style.height = "0px" + el.style.height = `${el.scrollHeight}px` + } + + const focusCustom = (el: HTMLTextAreaElement) => { + setTimeout(() => { + el.focus() + resizeInput(el) + }, 0) + } + + const toggleCustomMark = (event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + customToggle() + } + const next = () => { if (sending()) return if (store.editing) commitCustom() @@ -270,10 +339,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit type="button" data-slot="question-progress-segment" data-active={i() === store.tab} - data-answered={ - (store.answers[i()]?.length ?? 0) > 0 || - (store.customOn[i()] === true && (store.custom[i()] ?? "").trim().length > 0) - } + data-answered={answered(i())} disabled={sending()} onClick={() => jump(i())} aria-label={`${language.t("ui.tool.questions")} ${i() + 1}`} @@ -307,43 +373,23 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
- {(opt, i) => { - const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false - return ( - - ) - }} + {(opt, i) => ( + - + - {language.t("ui.messagePart.option.typeOwnAnswer")} - {input() || language.t("ui.question.custom.placeholder")} + {customLabel()} + {input() || customPlaceholder()} } @@ -394,33 +426,13 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit commitCustom() }} > - + - {language.t("ui.messagePart.option.typeOwnAnswer")} + {customLabel()}