Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
c2f7822
chore(app): cleanup (#20062)
adamdotdevin Mar 30, 2026
9f3c2bd
fix: agent value passed to chat.params and chat.headers hooks was not…
rekram1-node Mar 30, 2026
fa95a61
Refactor into plugin loader and do not enforce (#20112)
kommander Mar 30, 2026
c5442d4
refactor(session): effectify SessionPrompt service (#19483)
kitlangton Mar 30, 2026
c8389cf
chore: generate
opencode-agent[bot] Mar 30, 2026
e3e1e9a
fix(Opencode): Bump ai-sdk/xai to 3.0.75 to fix tool calls (#20123)
Jaaneek Mar 30, 2026
054f4be
zen: add safety identifier
fwang Mar 30, 2026
2ed756c
fix(session): restore busy route handling and add regression coverage…
kitlangton Mar 30, 2026
e7ff0f1
zen: qwen3.6 plus
fwang Mar 30, 2026
f2dd774
zen: qwen3.6 plus
fwang Mar 30, 2026
fe120e3
chore: update nix node_modules hashes
opencode-agent[bot] Mar 30, 2026
48e97b4
release: v1.3.8
Mar 30, 2026
e6f6f7a
refactor: replace Filesystem util with AppFileSystem service (#20127)
kitlangton Mar 30, 2026
ea88044
chore: generate
opencode-agent[bot] Mar 30, 2026
39a47c9
wip: zen
fwang Mar 30, 2026
58f6062
wip: zen
fwang Mar 30, 2026
1de0645
fix(plugin): properly resolve entrypoints without leading dot (#20140)
Hona Mar 30, 2026
057848d
release: v1.3.9
Mar 30, 2026
93fad99
smarter changelog (#20138)
Hona Mar 31, 2026
bf77729
fix(theme): darken muted text in catppuccin tui themes (#20161)
kitlangton Mar 31, 2026
a898c2e
refactor(storage): effectify Storage service (#20132)
kitlangton Mar 31, 2026
3df18dc
refactor(provider): effectify Provider service (#20160)
kitlangton Mar 31, 2026
71b2069
chore: generate
opencode-agent[bot] Mar 31, 2026
2cc738f
wip: zen
fwang Mar 31, 2026
befbeda
fix(session): subagents not being clickable (#20263)
thdxr Mar 31, 2026
b1e89c3
release: v1.3.10
Mar 31, 2026
1fcb920
upgrade opentui to 0.1.93 (#19950)
kommander Mar 31, 2026
2e78fde
ensure pinned plugin versions and do not run package scripts on insta…
kommander Mar 31, 2026
85c1692
chore: use paid zen model in e2e
adamdotdevin Mar 31, 2026
25a2b73
warn only and ignore plugins without entrypoints, default config via …
kommander Mar 31, 2026
8c2e2ec
chore: e2e model
adamdotdevin Mar 31, 2026
7342e65
chore: update nix node_modules hashes
opencode-agent[bot] Mar 31, 2026
2daf4b8
feat: add a dedicated system prompt for Kimi models (#20259)
Yuxin-Dong Mar 31, 2026
6e61a46
chore: skip 2 tests
adamdotdevin Mar 31, 2026
2929774
chore: rm harcoded model definition from codex plugin (#20294)
rekram1-node Mar 31, 2026
434d82b
test: update model test fixture (#20182)
rekram1-node Mar 31, 2026
567a911
refactor(session): simplify LLM stream by replacing queue with fromAs…
thdxr Mar 31, 2026
beab4cc
release: v1.3.11
Mar 31, 2026
4dd866d
fix: rm exclusion of ai-sdk/azure in transform.ts, when we migrated t…
rekram1-node Mar 31, 2026
26cc924
feat: enable prompt caching and cache token tracking for google-verte…
major Mar 31, 2026
d6d4446
Update VOUCHED list
github-actions[bot] Mar 31, 2026
1812130
upgrade opentui to 0.1.94 (#20357)
kommander Mar 31, 2026
892bdeb
release: v1.3.12
Mar 31, 2026
53330a5
Update VOUCHED list
github-actions[bot] Mar 31, 2026
0c03a3e
test: migrate prompt tests to HTTP mock LLM server (#20304)
kitlangton Mar 31, 2026
954a6ca
refactor(session): effectify SessionSummary service (#20142)
kitlangton Mar 31, 2026
3fc0367
refactor(session): effectify SessionRevert service (#20143)
kitlangton Mar 31, 2026
9b09a7e
chore: generate
opencode-agent[bot] Mar 31, 2026
4280307
chore: update nix node_modules hashes
opencode-agent[bot] Mar 31, 2026
4b4b783
upgrade opentui to 0.1.95 (#20369)
kommander Mar 31, 2026
6314f09
release: v1.3.13
Apr 1, 2026
181b5f6
refactor(prompt): use Provider service in effect layers (#20167)
kitlangton Apr 1, 2026
7532d99
test: finish HTTP mock processor coverage (#20372)
kitlangton Apr 1, 2026
ca376a4
chore: update nix node_modules hashes
opencode-agent[bot] Apr 1, 2026
c8ecd64
test(app): add mock llm e2e fixture (#20375)
kitlangton Apr 1, 2026
506dd75
electron: port mergeShellEnv logic from tauri (#20192)
Brendonovich Apr 1, 2026
1df5ad4
app: try to hide autofill popups in prompt input (#20197)
Brendonovich Apr 1, 2026
47a6761
fix(session): add keyboard support to question dock (#20439)
nexxeln Apr 1, 2026
a3a6cf1
feat(comments): support file mentions (#20447)
nexxeln Apr 1, 2026
9a1c9ae
test(app): route prompt e2e through mock llm (#20383)
kitlangton Apr 1, 2026
44f8301
perf(review): defer offscreen diff mounts (#20469)
nexxeln Apr 1, 2026
5fd833a
refactor: standardize InstanceState variable name to state (#20267)
kitlangton Apr 1, 2026
d58004a
fall back to first agent if last used agent is not available
thdxr Apr 1, 2026
38d2276
test(e2e): isolate prompt tests with per-worker backend (#20464)
kitlangton Apr 1, 2026
0a125e5
chore: generate
opencode-agent[bot] Apr 1, 2026
897d83c
refactor(init): tighten AGENTS guidance (#20422)
nexxeln Apr 1, 2026
a9c85b7
refactor(shell): use Effect ChildProcess for shell command execution …
kitlangton Apr 1, 2026
2f405da
refactor: use Effect services instead of async facades in provider, a…
kitlangton Apr 1, 2026
ef538c9
chore: generate
opencode-agent[bot] Apr 1, 2026
f9e71ec
chore: update nix node_modules hashes
opencode-agent[bot] Apr 1, 2026
d1e0a46
Update VOUCHED list
github-actions[bot] Apr 1, 2026
c559af5
test(app): migrate more e2e suites to isolated backend (#20505)
kitlangton Apr 1, 2026
c619cae
fix(account): coalesce concurrent console token refreshes (#20503)
kitlangton Apr 1, 2026
f3f728e
test(app): fix isolated backend follow-ups (#20513)
kitlangton Apr 1, 2026
d7481f4
wip: zen
fwang Apr 1, 2026
c9326fc
refactor: replace BunProc with Npm module using @npmcli/arborist (#18…
thdxr Apr 1, 2026
eabf3ca
zen: sync
fwang Apr 1, 2026
880c0a7
fix: normalize filepath in FileTime to prevent Windows path mismatch …
JosXa Apr 1, 2026
cc30bfc
resolve subpath only packages for plugins (#20555)
kommander Apr 1, 2026
fa96cb9
Fix selection expansion by retaining focused input selections during …
Hona Apr 1, 2026
1fcfb69
feat: add new provider plugin hook for resolving models and sync mode…
rekram1-node Apr 1, 2026
f7f41dc
fix(tui): apply scroll configuration uniformly across all scrollboxes…
Hona Apr 1, 2026
802d165
chore(tui): clean up scroll config follow-up (#20561)
Hona Apr 1, 2026
48db7cf
fix(opencode): batch snapshot revert without reordering (#20564)
kitlangton Apr 1, 2026
d9d4f89
fix(test): auto-acknowledge tool-result follow-ups in mock LLM server…
kitlangton Apr 1, 2026
4214ae2
chore: generate
opencode-agent[bot] Apr 1, 2026
26fb6b8
refactor: add Effect-returning versions of MessageV2 functions (#20374)
kitlangton Apr 1, 2026
e4ff1ea
refactor(bash): use Effect ChildProcess for bash tool execution (#20496)
kitlangton Apr 1, 2026
854484b
chore: generate
opencode-agent[bot] Apr 1, 2026
f6fd43e
Refactor plugin/config loading, add theme-only plugin package support…
kommander Apr 1, 2026
92f8e03
fix(test): use effect helper in snapshot race test (#20567)
kitlangton Apr 2, 2026
b1c0748
refactor(revert): yield SessionSummary.Service directly (#20541)
kitlangton Apr 2, 2026
c526caa
fix: show model display name in message footer and transcript (#20539)
kitlangton Apr 2, 2026
8a8f7b3
flock npm.add (#20557)
kommander Apr 2, 2026
00d6841
fix(account): refresh console tokens before expiry (#20558)
kitlangton Apr 2, 2026
0cad775
chore: add User-Agent headers for Cloudflare providers (#20538)
mchenco Apr 2, 2026
e148b31
fix(build): replace require() with dynamic import() in cross-spawn-sp…
kitlangton Apr 2, 2026
789d86f
chore: generate
opencode-agent[bot] Apr 2, 2026
df1c6c9
tui: add consent dialog when sharing for the first time (#20525)
rekram1-node Apr 2, 2026
a09b086
test(app): block real llm calls in e2e prompts (#20579)
kitlangton Apr 2, 2026
0bae38c
refactor(instruction): migrate to Effect service pattern (#20542)
kitlangton Apr 2, 2026
2e8e278
fix(cli): use simple logo in CLI (#20585)
kitlangton Apr 2, 2026
733a3bd
fix(core): prevent agent loop from stopping after tool calls with Ope…
valenvivaldi Apr 2, 2026
5daf2fa
fix(session): compaction agent responds in same language as conversat…
aaron-he-zhu Apr 2, 2026
916afb5
refactor(account): share token freshness helper (#20591)
kitlangton Apr 2, 2026
336d28f
fix(cli): restore colored help logo (#20592)
kitlangton Apr 2, 2026
d66e6dc
feat(opencode): Add Venice AI package as dependency (#20570)
dpuyosa Apr 2, 2026
ebaa99a
chore: generate
opencode-agent[bot] Apr 2, 2026
811c7e2
cli: update usage exceeded error
fwang Apr 2, 2026
a6f524c
chore: update nix node_modules hashes
opencode-agent[bot] Apr 2, 2026
0f48899
fix(node): set OPENCODE_CHANNEL during build (#20616)
Brendonovich Apr 2, 2026
db93891
zen: friendly trial ended message
fwang Apr 2, 2026
d540d36
refactor: simplify solid reactivity across app and web (#20497)
nexxeln Apr 2, 2026
327f625
use solid-primitives/resize-observer across web code (#20613)
Brendonovich Apr 2, 2026
69d047a
cleanup event listeners with solid-primitives/event-listener (#20619)
Brendonovich Apr 2, 2026
ec3ae17
chore: update nix node_modules hashes
opencode-agent[bot] Apr 2, 2026
ca73117
feat(tui): add agent management to /agents dialog
Builderstar Mar 30, 2026
23c8656
refactor: split up models.dev and config model definitions to prevent…
rekram1-node Apr 2, 2026
57a5236
chore: generate
opencode-agent[bot] Apr 2, 2026
291a857
feat: add optional messageID to ShellInput (#20657)
noamzbr Apr 2, 2026
159ede2
chore: generate
opencode-agent[bot] Apr 2, 2026
4105512
Merge branch 'dev' into feat/agent-management-tui
Builderstar Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ adamdotdevin
-agusbasari29 AI PR slop
ariane-emory
-atharvau AI review spamming literally every PR
-borealbytes
-danieljoshuanazareth
-danieljoshuanazareth
edemaine
Expand All @@ -21,8 +22,10 @@ jayair
kitlangton
kommander
-opencode2026
-opencodeengineer bot that spams issues
r44vc0rp
rekram1-node
-robinmordasiewicz
-spider-yamet clawdbot/llm psychosis, spam pinging the team
thdxr
-OpenCodeEngineer bot that spams issues
-toastythebot
18 changes: 10 additions & 8 deletions .opencode/command/changelog.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
---
model: opencode/kimi-k2.5
model: opencode/gpt-5.4
---

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.

Any command arguments are passed directly to `bun script/changelog.ts`.
Use `--from` / `-f` and `--to` / `-t` to preview a specific release range.

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.

Before writing any entry you keep, inspect the real diff with
`git show --stat --format='' <hash>` or `git show --format='' <hash>` so the
summary reflects the actual user-facing change and not just the commit message.
`git show --stat --format='' <hash>` or `git show --format='' <hash>` 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.

Rules:
Expand All @@ -38,7 +35,12 @@ Rules:
- 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

**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**

<changelog_input>

## Changelog Input
!`bun script/raw-changelog.ts $ARGUMENTS`

!`bun script/changelog.ts $ARGUMENTS`
</changelog_input>
1,170 changes: 669 additions & 501 deletions bun.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions nix/hashes.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-0JgEA54d1ZZ0IWUmxCJP2fnQ2cpmLO25G+hafUbHFLw=",
"aarch64-linux": "sha256-oB4Ptc+MH76MEw0DZodmCCz87qOmbzi26751ZM4DYyE=",
"aarch64-darwin": "sha256-712rb7B5gTRz1uTx4cJQSrmq9DoBUe+UxbvawYV4XTE=",
"x86_64-darwin": "sha256-GRCiEBDDEyVx1et04xqdIEQr3ykRMOBJoQy/xddSsCA="
"x86_64-linux": "sha256-SQVfq41OQdGCgWuWqyqIN6aggL0r3Hzn2hJ9BwPJN+I=",
"aarch64-linux": "sha256-4w/1HhxsTzPFTHNf4JlnKle6Boz1gVTEedWG64T8E/M=",
"aarch64-darwin": "sha256-uMd+pU1u1yqP4OP/9461Tyy3zwwv/llr+rlllLjM98A=",
"x86_64-darwin": "sha256-BhIW3FPqKkM2vGfCrxXUvj5tarey33Q7dxCuaj5A+yU="
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"packages/slack"
],
"catalog": {
"@effect/platform-node": "4.0.0-beta.42",
"@effect/platform-node": "4.0.0-beta.43",
"@types/bun": "1.3.11",
"@octokit/rest": "22.0.0",
"@hono/zod-validator": "0.4.2",
Expand All @@ -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.42",
"effect": "4.0.0-beta.43",
"ai": "6.0.138",
"hono": "4.10.7",
"hono-openapi": "1.1.2",
Expand Down
75 changes: 55 additions & 20 deletions packages/app/e2e/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { expect, type Locator, type Page } from "@playwright/test"
import { expect, type Locator, type Page, type Route } from "@playwright/test"
import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
Expand Down Expand Up @@ -43,6 +43,27 @@ export async function defocus(page: Page) {
.catch(() => undefined)
}

export async function withNoReplyPrompt<T>(page: Page, fn: () => Promise<T>) {
const url = "**/session/*/prompt_async"
const route = async (input: Route) => {
const body = input.request().postDataJSON()
await input.continue({
postData: JSON.stringify({ ...body, noReply: true }),
headers: {
...input.request().headers(),
"content-type": "application/json",
},
})
}

await page.route(url, route)
try {
return await fn()
} finally {
await page.unroute(url, route)
}
}

async function terminalID(term: Locator) {
const id = await term.getAttribute(terminalAttr)
if (id) return id
Expand Down Expand Up @@ -312,10 +333,11 @@ export async function openSettings(page: Page) {
return dialog
}

export async function seedProjects(page: Page, input: { directory: string; extra?: string[] }) {
export async function seedProjects(page: Page, input: { directory: string; extra?: string[]; serverUrl?: string }) {
await page.addInitScript(
(args: { directory: string; serverUrl: string; extra: string[] }) => {
const key = "opencode.global.dat:server"
const defaultKey = "opencode.settings.dat:defaultServerUrl"
const raw = localStorage.getItem(key)
const parsed = (() => {
if (!raw) return undefined
Expand All @@ -331,6 +353,7 @@ export async function seedProjects(page: Page, input: { directory: string; extra
const lastProject = store.lastProject && typeof store.lastProject === "object" ? store.lastProject : {}
const projects = store.projects && typeof store.projects === "object" ? store.projects : {}
const nextProjects = { ...(projects as Record<string, unknown>) }
const nextList = list.includes(args.serverUrl) ? list : [args.serverUrl, ...list]

const add = (origin: string, directory: string) => {
const current = nextProjects[origin]
Expand All @@ -356,17 +379,18 @@ export async function seedProjects(page: Page, input: { directory: string; extra
localStorage.setItem(
key,
JSON.stringify({
list,
list: nextList,
projects: nextProjects,
lastProject,
}),
)
localStorage.setItem(defaultKey, args.serverUrl)
},
{ directory: input.directory, serverUrl, extra: input.extra ?? [] },
{ directory: input.directory, serverUrl: input.serverUrl ?? serverUrl, extra: input.extra ?? [] },
)
}

export async function createTestProject() {
export async function createTestProject(input?: { serverUrl?: string }) {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-project-"))
const id = `e2e-${path.basename(root)}`

Expand All @@ -381,7 +405,7 @@ export async function createTestProject() {
stdio: "ignore",
})

return resolveDirectory(root)
return resolveDirectory(root, input?.serverUrl)
}

export async function cleanupTestProject(directory: string) {
Expand Down Expand Up @@ -430,22 +454,22 @@ export async function waitSlug(page: Page, skip: string[] = []) {
return next
}

export async function resolveSlug(slug: string) {
export async function resolveSlug(slug: string, input?: { serverUrl?: string }) {
const directory = base64Decode(slug)
if (!directory) throw new Error(`Failed to decode workspace slug: ${slug}`)
const resolved = await resolveDirectory(directory)
const resolved = await resolveDirectory(directory, input?.serverUrl)
return { directory: resolved, slug: base64Encode(resolved), raw: slug }
}

export async function waitDir(page: Page, directory: string) {
const target = await resolveDirectory(directory)
export async function waitDir(page: Page, directory: string, input?: { serverUrl?: string }) {
const target = await resolveDirectory(directory, input?.serverUrl)
await expect
.poll(
async () => {
await assertHealthy(page, "waitDir")
const slug = slugFromUrl(page.url())
if (!slug) return ""
return resolveSlug(slug)
return resolveSlug(slug, input)
.then((item) => item.directory)
.catch(() => "")
},
Expand All @@ -455,15 +479,15 @@ export async function waitDir(page: Page, directory: string) {
return { directory: target, slug: base64Encode(target) }
}

export async function waitSession(page: Page, input: { directory: string; sessionID?: string }) {
const target = await resolveDirectory(input.directory)
export async function waitSession(page: Page, input: { directory: string; sessionID?: string; serverUrl?: string }) {
const target = await resolveDirectory(input.directory, input.serverUrl)
await expect
.poll(
async () => {
await assertHealthy(page, "waitSession")
const slug = slugFromUrl(page.url())
if (!slug) return false
const resolved = await resolveSlug(slug).catch(() => undefined)
const resolved = await resolveSlug(slug, { serverUrl: input.serverUrl }).catch(() => undefined)
if (!resolved || resolved.directory !== target) return false
const current = sessionIDFromUrl(page.url())
if (input.sessionID && current !== input.sessionID) return false
Expand All @@ -473,7 +497,7 @@ export async function waitSession(page: Page, input: { directory: string; sessio
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 ?? "")
const dir = await resolveDirectory(state.dir, input.serverUrl).catch(() => state.dir ?? "")
if (dir !== target) return false
}

Expand All @@ -489,9 +513,9 @@ export async function waitSession(page: Page, input: { directory: string; sessio
return { directory: target, slug: base64Encode(target) }
}

export async function waitSessionSaved(directory: string, sessionID: string, timeout = 30_000) {
const sdk = createSdk(directory)
const target = await resolveDirectory(directory)
export async function waitSessionSaved(directory: string, sessionID: string, timeout = 30_000, serverUrl?: string) {
const sdk = createSdk(directory, serverUrl)
const target = await resolveDirectory(directory, serverUrl)

await expect
.poll(
Expand All @@ -501,7 +525,7 @@ export async function waitSessionSaved(directory: string, sessionID: string, tim
.then((x) => x.data)
.catch(() => undefined)
if (!data?.directory) return ""
return resolveDirectory(data.directory).catch(() => data.directory)
return resolveDirectory(data.directory, serverUrl).catch(() => data.directory)
},
{ timeout },
)
Expand Down Expand Up @@ -666,8 +690,9 @@ export async function cleanupSession(input: {
sessionID: string
directory?: string
sdk?: ReturnType<typeof createSdk>
serverUrl?: string
}) {
const sdk = input.sdk ?? (input.directory ? createSdk(input.directory) : undefined)
const sdk = input.sdk ?? (input.directory ? createSdk(input.directory, input.serverUrl) : undefined)
if (!sdk) throw new Error("cleanupSession requires sdk or directory")
await waitSessionIdle(sdk, input.sessionID, 5_000).catch(() => undefined)
const current = await status(sdk, input.sessionID).catch(() => undefined)
Expand Down Expand Up @@ -1019,3 +1044,13 @@ export async function openWorkspaceMenu(page: Page, workspaceSlug: string) {
await expect(menu).toBeVisible()
return menu
}

export async function assistantText(sdk: ReturnType<typeof createSdk>, sessionID: string) {
const messages = await sdk.session.messages({ sessionID, limit: 50 }).then((r) => r.data ?? [])
return messages
.filter((m) => m.info.role === "assistant")
.flatMap((m) => m.parts)
.filter((p) => p.type === "text")
.map((p) => p.text)
.join("\n")
}
Loading
Loading