Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
9b57db3
feat: add litellmProxy provider option for explicit LiteLLM compatibi…
seilk Jan 15, 2026
83ed1ad
feat: add Carbonfox theme (#8723)
devatnull Jan 15, 2026
b9b5d42
chore: generate
actions-user Jan 15, 2026
f4086ac
fix:subagent reasoningEffort not being applied (#8646)
johnnyapu15 Jan 15, 2026
9d8d0e9
Revert "fix:subagent reasoningEffort not being applied (#8646)"
rekram1-node Jan 15, 2026
306fc05
fix: project avatar border radius
iamdavidhill Jan 15, 2026
7443b99
feat(console): Fix /black page View Transition Safari issue (#8755)
aaroniker Jan 15, 2026
81983d4
fix(agent): default agent selection in acp and headless mode (#8678)
assagman Jan 15, 2026
8b08d34
fix: stop changing main model/agent from subtasks invocation (#7681)
dbpolito Jan 15, 2026
2abafbc
chore: generate
actions-user Jan 15, 2026
7e619a9
zen: black admin
fwang Jan 15, 2026
12ae808
wip: zen
fwang Jan 15, 2026
a7cae8f
fix: nix desktop workflow (#8747)
ricardo-valero Jan 15, 2026
529eb6e
fix(app): persist workspace order and collapsed state
adamdotdevin Jan 15, 2026
49939c4
feat(app): skeleton loader for sessions
adamdotdevin Jan 15, 2026
657f3d5
feat(app): unified search for commands and files
adamdotdevin Jan 15, 2026
d475107
strip itemIds in more cases
rekram1-node Jan 16, 2026
25cb03d
chore: cleanup
rekram1-node Jan 16, 2026
b572c68
fix(mcp): show auth URL when browser cannot open in remote sessions (…
danlapid Jan 16, 2026
68e6c54
chore: generate
actions-user Jan 16, 2026
d7192d6
tweak: set opencode as user agent for most interefence requests
rekram1-node Jan 16, 2026
d8ef9f8
test: fix transform test
rekram1-node Jan 16, 2026
5092b5f
docs: clarify question tool guidance (#8778)
kitlangton Jan 16, 2026
07e7ebd
fix(tui): add tab navigation in questions (#8777)
kitlangton Jan 16, 2026
12b6210
fix(tui): dim question option prefixes (#8776)
kitlangton Jan 16, 2026
4af9def
fix(tui): correct theme count tip (#8779)
kitlangton Jan 16, 2026
46be47d
stop select dialog event propagation
kommander Jan 16, 2026
1a43e5f
fix: adjust websearch tool to emphasize that it ISNT 2024, give more …
rekram1-node Jan 16, 2026
ac54535
feat: add version to session header and /status dialog (#8802)
AksharP5 Jan 16, 2026
e4a34be
chore: update GitHub stars and commits statistics (#8793)
hyeonjongyang Jan 16, 2026
40b275d
feat(mcp): add OAuth redirect URI configuration for MCP servers (#7379)
christso Jan 16, 2026
0233dd1
chore: generate
actions-user Jan 16, 2026
de2de09
fix: rm user message when dealing w/ image attachments, use proper to…
rekram1-node Jan 16, 2026
f66e6d7
wip: zen
fwang Jan 16, 2026
ce6e9a8
Update node_modules hash (aarch64-linux)
actions-user Jan 16, 2026
cce4f64
Update node_modules hash (x86_64-linux)
actions-user Jan 16, 2026
fffa718
test: fix test now that image fix went in
rekram1-node Jan 16, 2026
0e9664d
Update node_modules hash (x86_64-darwin)
actions-user Jan 16, 2026
524ea95
update gpt models prompt
rekram1-node Jan 16, 2026
c551a4b
fix(app): persist workspace order and collapsed state
adamdotdevin Jan 16, 2026
b1a22e0
fix: avatar radius and current project
iamdavidhill Jan 16, 2026
3ba03a9
fix: search bar size and padding, and shortcut style
iamdavidhill Jan 16, 2026
416f419
fix: add default icon to sessions
iamdavidhill Jan 16, 2026
94ab87f
fix: view all sessions state styles
iamdavidhill Jan 16, 2026
2c54377
fix: updated project/sessions list width
iamdavidhill Jan 16, 2026
7042767
bug: moved createMemo down
iamdavidhill Jan 16, 2026
efaf854
chore: generate
actions-user Jan 16, 2026
f197b8a
ignore: update download stats 2026-01-16
actions-user Jan 16, 2026
e8dad85
fix: responsive menu desktop
iamdavidhill Jan 16, 2026
d5a5e6e
feat(console): /black shader improvements, performance, details (#8871)
aaroniker Jan 16, 2026
4be0ba1
fix: web mobile menu
iamdavidhill Jan 16, 2026
d039904
fix: make hamburger centred with project avatars
iamdavidhill Jan 16, 2026
46f415e
fix: desktop hamburger shift
iamdavidhill Jan 16, 2026
74d584a
fix: session icon and label alignment
iamdavidhill Jan 16, 2026
1fd496a
fix: expand workspaces by default when enabled
iamdavidhill Jan 16, 2026
2190e8c
Revert "fix: expand workspaces by default when enabled"
iamdavidhill Jan 16, 2026
9a71a73
fix: updating panel min size and button max-width
iamdavidhill Jan 16, 2026
21012fa
fix: load more label alignment
iamdavidhill Jan 16, 2026
a49102d
fix: truncate the workspace name on hover
iamdavidhill Jan 16, 2026
bd914a8
Revert "stop select dialog event propagation"
rekram1-node Jan 16, 2026
e0a854f
Revert "fix: rm user message when dealing w/ image attachments, use p…
rekram1-node Jan 16, 2026
d510bd5
Revert "test: fix test now that image fix went in"
rekram1-node Jan 16, 2026
ea8ef37
wip: zen
fwang Jan 16, 2026
88fd6a2
feat(desktop): Terminal Splits (#8767)
dbpolito Jan 16, 2026
d075c09
chore(sdk): update @hey-api/openapi-ts to 0.90.4 (#8921)
coleleavitt Jan 16, 2026
91b8ba2
Revert "chore(sdk): update @hey-api/openapi-ts to 0.90.4" (#8927)
rekram1-node Jan 16, 2026
9a48f8e
Update node_modules hash (aarch64-darwin)
actions-user Jan 16, 2026
40836e9
fix: fix the itemId stripping logic, this time it should fix that id …
rekram1-node Jan 16, 2026
5479928
Reapply "chore(sdk): update @hey-api/openapi-ts to 0.90.4" (#8927)
rekram1-node Jan 16, 2026
ad4bdd9
Update node_modules hash (x86_64-linux)
actions-user Jan 16, 2026
ccc27e2
fix(docs): Broken URL (#8918)
berenar Jan 16, 2026
e1d0b2b
fix: use dynamic import for session event in config.ts to avoid circu…
rekram1-node Jan 16, 2026
22e3240
Update node_modules hash (x86_64-darwin)
actions-user Jan 16, 2026
8fd1b92
fix: ensure that tool attachments arent sent as user messages (#8944)
rekram1-node Jan 16, 2026
0d683ea
upgrade opentui to v0.1.74, fix tmux kitty keyboard regression
kommander Jan 16, 2026
8d4a673
Update node_modules hash (aarch64-linux)
actions-user Jan 16, 2026
438916d
Update node_modules hash (x86_64-darwin)
actions-user Jan 16, 2026
00ec29d
fix(app): scroll jumping when expanding workspaces
adamdotdevin Jan 16, 2026
e92d5b5
fix(app): can't expand workspaces
adamdotdevin Jan 16, 2026
2ccaa10
fix(app): open workspace if navigating to session in workspace
adamdotdevin Jan 16, 2026
0866034
feat(app): edit project and session titles
adamdotdevin Jan 16, 2026
71306cb
Revert "feat(desktop): Terminal Splits (#8767)"
adamdotdevin Jan 16, 2026
bc3616d
release: v1.1.24
Jan 16, 2026
98578d3
fix(bun): reinstall plugins when cache module missing (#8815)
kenryu42 Jan 16, 2026
db0078b
chore: generate
actions-user Jan 16, 2026
360765c
fix(app): center dialog on page instead of session
adamdotdevin Jan 16, 2026
da78b75
fix(app): handle new session correctly
adamdotdevin Jan 16, 2026
8e0ddd1
chore: cleanup server routes (#8965)
rekram1-node Jan 16, 2026
6e028ec
chore: generate
actions-user Jan 16, 2026
b8e2895
fix(app): support anthropic models on azure cognitive services (#8335)
uanandaraja Jan 16, 2026
14d1e20
Revert "fix(app): support anthropic models on azure cognitive service…
rekram1-node Jan 16, 2026
95f7403
fix(app): truncate workspace title
adamdotdevin Jan 16, 2026
6e00348
fix(app): remember last opened project
adamdotdevin Jan 16, 2026
f5a6a4a
Revert "fix: ensure that tool attachments arent sent as user messages…
rekram1-node Jan 16, 2026
9127055
tweak: wording
rekram1-node Jan 16, 2026
8c24879
test: fix
rekram1-node Jan 16, 2026
968239b
release: v1.1.25
Jan 16, 2026
ad43ee2
sync: merge upstream v1.1.25 into shuvcode-dev
shuv1337 Jan 17, 2026
be9e277
sync: record last synced tag v1.1.25
shuv1337 Jan 17, 2026
56667a5
fix(ui): remove extra brace in message-part.css breaking production b…
shuv1337 Jan 17, 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
2 changes: 1 addition & 1 deletion .github/last-synced-tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.1.23
v1.1.25
4 changes: 3 additions & 1 deletion .github/workflows/nix-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
matrix:
os:
- blacksmith-4vcpu-ubuntu-2404
- blacksmith-4vcpu-ubuntu-2404-arm
- macos-15
- macos-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 60
Expand All @@ -33,7 +35,7 @@ jobs:
uses: actions/checkout@v6

- name: Setup Nix
uses: DeterminateSystems/nix-installer-action@v21
uses: nixbuild/nix-quick-install-action@v34

- name: Build desktop via flake
run: |
Expand Down
403 changes: 202 additions & 201 deletions STATS.md

Large diffs are not rendered by default.

905 changes: 850 additions & 55 deletions bun.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions infra/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const ZEN_MODELS = [
new sst.Secret("ZEN_MODELS5"),
new sst.Secret("ZEN_MODELS6"),
new sst.Secret("ZEN_MODELS7"),
new sst.Secret("ZEN_MODELS8"),
]
const ZEN_BLACK = new sst.Secret("ZEN_BLACK")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
Expand Down
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-4ndHIlS9t1ynRdFszJ1nvcu3YhunhuOc7jcuHI1FbnM=",
"aarch64-linux": "sha256-H9eUk/yVrQqVrAYONlb6As7mjkPXtOauBVfMBeVAmRo=",
"aarch64-darwin": "sha256-C0E9KAEj3GI83HwirIL2zlXYIe92T+7Iv6F51BB6slY=",
"x86_64-darwin": "sha256-wj5fZnyfu6Sf1HcqvsQM3M7dl5BKRAHmoqm1Ai1cL2M="
"x86_64-linux": "sha256-07XxcHLuToM4QfWVyaPLACxjPZ93ZM7gtpX2o08Lp18=",
"aarch64-linux": "sha256-0Im52dLeZ0ZtaPJr/U4m7+IRtOfziHNJI/Bu/V6cPho=",
"aarch64-darwin": "sha256-U2UvE70nM0OI0VhIku8qnX+ptPbA+Q/y1BGXbFMcyt4=",
"x86_64-darwin": "sha256-CpZFHBMPJSib2Vqs6oC8HQjQtviPUMa/qezHAe22N/A="
}
}
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.1.23",
"version": "1.1.25",
"description": "",
"type": "module",
"exports": {
Expand Down
178 changes: 153 additions & 25 deletions packages/app/src/components/dialog-select-file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,175 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
import { List } from "@opencode-ai/ui/list"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { useParams } from "@solidjs/router"
import { createMemo } from "solid-js"
import { createMemo, createSignal, onCleanup, Show } from "solid-js"
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
import { useLayout } from "@/context/layout"
import { useFile } from "@/context/file"

type EntryType = "command" | "file"

type Entry = {
id: string
type: EntryType
title: string
description?: string
keybind?: string
category: "Commands" | "Files"
option?: CommandOption
path?: string
}

export function DialogSelectFile() {
const command = useCommand()
const layout = useLayout()
const file = useFile()
const dialog = useDialog()
const params = useParams()
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
const tabs = createMemo(() => layout.tabs(sessionKey()))
const view = createMemo(() => layout.view(sessionKey()))
const state = { cleanup: undefined as (() => void) | void, committed: false }
const [grouped, setGrouped] = createSignal(false)
const common = ["session.new", "session.previous", "session.next", "terminal.toggle", "review.toggle"]
const limit = 5

const allowed = createMemo(() =>
command.options.filter(
(option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open",
),
)

const commandItem = (option: CommandOption): Entry => ({
id: "command:" + option.id,
type: "command",
title: option.title,
description: option.description,
keybind: option.keybind,
category: "Commands",
option,
})

const fileItem = (path: string): Entry => ({
id: "file:" + path,
type: "file",
title: path,
category: "Files",
path,
})

const list = createMemo(() => allowed().map(commandItem))

const picks = createMemo(() => {
const all = allowed()
const order = new Map(common.map((id, index) => [id, index]))
const picked = all.filter((option) => order.has(option.id))
const base = picked.length ? picked : all.slice(0, limit)
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
return sorted.map(commandItem)
})

const recent = createMemo(() => {
const all = tabs().all()
const active = tabs().active()
const order = active ? [active, ...all.filter((item) => item !== active)] : all
const seen = new Set<string>()
const items: Entry[] = []

for (const item of order) {
const path = file.pathFromTab(item)
if (!path) continue
if (seen.has(path)) continue
seen.add(path)
items.push(fileItem(path))
}

return items.slice(0, limit)
})

const items = async (filter: string) => {
const query = filter.trim()
setGrouped(query.length > 0)
if (!query) return [...picks(), ...recent()]
const files = await file.searchFiles(query)
const entries = files.map(fileItem)
return [...list(), ...entries]
}

const handleMove = (item: Entry | undefined) => {
state.cleanup?.()
if (!item) return
if (item.type !== "command") return
state.cleanup = item.option?.onHighlight?.()
}

const open = (path: string) => {
const value = file.tab(path)
tabs().open(value)
file.load(path)
view().reviewPanel.open()
}

const handleSelect = (item: Entry | undefined) => {
if (!item) return
state.committed = true
state.cleanup = undefined
dialog.close()

if (item.type === "command") {
item.option?.onSelect?.("palette")
return
}

if (!item.path) return
open(item.path)
}

onCleanup(() => {
if (state.committed) return
state.cleanup?.()
})

return (
<Dialog title="Select file">
<Dialog title="Search">
<List
search={{ placeholder: "Search files", autofocus: true }}
emptyMessage="No files found"
items={file.searchFiles}
key={(x) => x}
onSelect={(path) => {
if (path) {
const value = file.tab(path)
tabs().open(value)
file.load(path)
view().reviewPanel.open()
}
dialog.close()
}}
search={{ placeholder: "Search files and commands", autofocus: true }}
emptyMessage="No results found"
items={items}
key={(item) => item.id}
filterKeys={["title", "description", "category"]}
groupBy={(item) => (grouped() ? item.category : "")}
onMove={handleMove}
onSelect={handleSelect}
>
{(i) => (
<div class="w-full flex items-center justify-between rounded-md">
<div class="flex items-center gap-x-3 grow min-w-0">
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(i)}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
{(item) => (
<Show
when={item.type === "command"}
fallback={
<div class="w-full flex items-center justify-between rounded-md">
<div class="flex items-center gap-x-3 grow min-w-0">
<FileIcon node={{ path: item.path ?? "", type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(item.path ?? "")}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(item.path ?? "")}</span>
</div>
</div>
</div>
}
>
<div class="w-full flex items-center justify-between gap-4">
<div class="flex items-center gap-2 min-w-0">
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
<Show when={item.description}>
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
</Show>
</div>
<Show when={item.keybind}>
<span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(item.keybind ?? "")}</span>
</Show>
</div>
</div>
</Show>
)}
</List>
</Dialog>
Expand Down
14 changes: 8 additions & 6 deletions packages/app/src/components/titlebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ export function Titlebar() {
>
<Show when={mac()}>
<div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
<IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
</div>
</Show>
<Show when={!mac()}>
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
<IconButton icon="menu" variant="ghost" class="size-8 rounded-md" onClick={layout.mobileSidebar.toggle} />
</div>
</Show>
<IconButton
icon="menu"
variant="ghost"
class="xl:hidden size-8 rounded-md"
onClick={layout.mobileSidebar.toggle}
/>
<TooltipKeybind
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0"}
placement="bottom"
Expand Down
81 changes: 12 additions & 69 deletions packages/app/src/context/command.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { createMemo, createSignal, onCleanup, onMount, Show, type Accessor } from "solid-js"
import { createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"

const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)

Expand Down Expand Up @@ -114,67 +111,11 @@ export function formatKeybind(config: string): string {
return IS_MAC ? parts.join("") : parts.join("+")
}

function DialogCommand(props: { options: CommandOption[] }) {
const dialog = useDialog()
let cleanup: (() => void) | void
let committed = false

const handleMove = (option: CommandOption | undefined) => {
cleanup?.()
cleanup = option?.onHighlight?.()
}

const handleSelect = (option: CommandOption | undefined) => {
if (option) {
committed = true
cleanup = undefined
dialog.close()
option.onSelect?.("palette")
}
}

onCleanup(() => {
if (!committed) {
cleanup?.()
}
})

return (
<Dialog title="Commands">
<List
search={{ placeholder: "Search commands", autofocus: true }}
emptyMessage="No commands found"
items={() => props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)}
key={(x) => x?.id}
filterKeys={["title", "description", "category"]}
groupBy={(x) => x.category ?? ""}
onMove={handleMove}
onSelect={handleSelect}
>
{(option) => (
<div class="w-full flex items-center justify-between gap-4">
<div class="flex items-center gap-2 min-w-0">
<span class="text-14-regular text-text-strong whitespace-nowrap">{option.title}</span>
<Show when={option.description}>
<span class="text-14-regular text-text-weak truncate">{option.description}</span>
</Show>
</div>
<Show when={option.keybind}>
<span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(option.keybind!)}</span>
</Show>
</div>
)}
</List>
</Dialog>
)
}

export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
name: "Command",
init: () => {
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
const [suspendCount, setSuspendCount] = createSignal(0)
const dialog = useDialog()

const options = createMemo(() => {
const seen = new Set<string>()
Expand Down Expand Up @@ -202,12 +143,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex

const suspended = () => suspendCount() > 0

const showPalette = () => {
if (!dialog.active) {
dialog.show(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
const run = (id: string, source?: "palette" | "keybind" | "slash") => {
for (const option of options()) {
if (option.id === id || option.id === "suggested." + id) {
option.onSelect?.(source)
return
}
}
}

const showPalette = () => {
run("file.open", "palette")
}

const handleKeyDown = (event: KeyboardEvent) => {
if (suspended()) return

Expand Down Expand Up @@ -248,12 +196,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
})
},
trigger(id: string, source?: "palette" | "keybind" | "slash") {
for (const option of options()) {
if (option.id === id || option.id === "suggested." + id) {
option.onSelect?.(source)
return
}
}
run(id, source)
},
keybind(id: string) {
const option = options().find((x) => x.id === id || x.id === "suggested." + id)
Expand Down
Loading
Loading