Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
160 commits
Select commit Hold shift + click to select a range
38376f4
PATCH /config hot-reload w/ selective cache invalidation (#15)
shuv1337 Nov 13, 2025
f471203
Merge branch 'dev' into config-hot-reload
shuv1337 Nov 13, 2025
3dbd5ed
typecheck fix
shuv1337 Nov 13, 2025
df3e955
chore: format code
actions-user Nov 13, 2025
f71cb8b
Merge branch 'dev' into config-hot-reload
shuv1337 Nov 13, 2025
6ee86eb
fix for failing test
shuv1337 Nov 13, 2025
725a878
fix for failing test
shuv1337 Nov 14, 2025
865955e
chore: format code
actions-user Nov 14, 2025
bdf7f7d
Merge branch 'dev' into config-hot-reload
shuv1337 Nov 14, 2025
bad3021
Merge branch 'integration' into config-hot-reload
shuv1337 Nov 14, 2025
f28361f
chore: format code
actions-user Nov 14, 2025
410fd58
Merge branch 'integration' into config-hot-reload
shuv1337 Nov 14, 2025
37527bd
Fix cache corruption from partial updates in file refresh (#18)
Copilot Nov 14, 2025
ce411d0
Merge branch 'integration' into config-hot-reload
shuv1337 Nov 14, 2025
66546ce
Add favorites to model selector (#23)
shuv1337 Nov 15, 2025
e3d696e
Merge branch 'integration' into config-hot-reload
shuv1337 Nov 17, 2025
03d3c80
revert desktop changes
shuv1337 Nov 17, 2025
76d48d8
revert desktop changes
shuv1337 Nov 17, 2025
4d5f6b5
proper fix for image attachments
shuv1337 Nov 20, 2025
26b6b1e
Merge branch 'dev' into fix-tui-img-attachments
shuv1337 Nov 21, 2025
f3b5b37
chore: format code
actions-user Nov 21, 2025
2c264a7
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
a069899
Update Nix flake.lock and hashes
actions-user Nov 21, 2025
d9a0881
chore: format code
actions-user Nov 21, 2025
1aedc8a
chore: format code
actions-user Nov 21, 2025
0a1f6d8
fix syntax error
shuv1337 Nov 21, 2025
15b72aa
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
51eaba8
Merge branch 'dev' into fix-tui-img-attachments
shuv1337 Nov 21, 2025
16aed1b
chore: format code
actions-user Nov 21, 2025
e6b665e
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
0b96087
Merge branch 'dev' into fix-tui-img-attachments
shuv1337 Nov 21, 2025
a8832fa
Update Nix flake.lock and hashes
actions-user Nov 21, 2025
fd4d38b
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
65b7569
Merge branch 'dev' into fix-tui-img-attachments
shuv1337 Nov 21, 2025
1296e46
Merge origin/dev into config-hot-reload
shuv1337 Nov 21, 2025
9c9d2ed
Merge branch 'dev' into fix-tui-img-attachments
rekram1-node Nov 21, 2025
8cfe611
fix: resolve type error in provider configuration loop
shuv1337 Nov 21, 2025
980c331
gen sdk
shuv1337 Nov 21, 2025
62a8131
Update Nix flake.lock and hashes
actions-user Nov 21, 2025
01133dd
Merge branch 'dev' into config-hot-reload
shuv1337 Nov 21, 2025
3f44edd
Merge branch 'sst:dev' into integration
shuv1337 Nov 21, 2025
a4cbc52
Update Nix flake.lock and hashes
actions-user Nov 21, 2025
f1ff202
Local test merge of PR #29: Config hot reload
shuv1337 Nov 21, 2025
cf53bc3
Local test merge of PR #27: Add model favorites
shuv1337 Nov 21, 2025
3538e54
Local test merge of PR #26: proper fix for image attachments
shuv1337 Nov 21, 2025
4423bc5
Local test merge of PR #30: Merge Dev into Integration
shuv1337 Nov 21, 2025
b67a0bb
Local test merge of PR #27: Add model favorites
shuv1337 Nov 21, 2025
1dab0fa
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
e99a697
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
6d6be71
Local test merge of dev into integration: whitelist/blacklist provide…
shuv1337 Nov 21, 2025
087f075
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 21, 2025
69a9960
Merge branch 'dev' into integration
shuv1337 Nov 21, 2025
d162131
chore: format code
actions-user Nov 22, 2025
9cab986
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 22, 2025
283ee53
Merge branch 'dev' into integration
shuv1337 Nov 22, 2025
5d587f0
Update Nix flake.lock and hashes
actions-user Nov 22, 2025
93997a0
Update Nix flake.lock and hashes
actions-user Nov 22, 2025
210b9b5
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 22, 2025
0314155
Merge branch 'dev' into integration
shuv1337 Nov 22, 2025
6a751a5
chore: format code
actions-user Nov 22, 2025
62370a3
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 22, 2025
fab0427
Merge branch 'dev' into integration
shuv1337 Nov 22, 2025
78f0927
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 22, 2025
37aa350
chore: format code
actions-user Nov 22, 2025
f432c6b
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 22, 2025
2f9a780
Merge branch 'dev' into integration
shuv1337 Nov 22, 2025
dd471fc
gen new sdk
shuv1337 Nov 22, 2025
1e21e93
Merge branch 'dev' into integration
shuv1337 Nov 23, 2025
ab3b2be
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 23, 2025
bc83f23
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 24, 2025
a909581
Update Nix flake.lock and hashes
actions-user Nov 24, 2025
82ba1b2
Update Nix flake.lock and hashes
actions-user Nov 24, 2025
cc49b53
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 24, 2025
87cf82f
ci: fix action
rekram1-node Nov 23, 2025
bc54218
feature: optional selectedListItemText element in themes and luminanc…
ariane-emory Nov 23, 2025
64fd8bc
ignore: update download stats 2025-11-23
actions-user Nov 23, 2025
f39f5e1
ignore: update @solidjs/start dependency and fix console redirect han…
thdxr Nov 23, 2025
17cad73
Update Nix flake.lock and hashes
actions-user Nov 23, 2025
c292e28
tweak: fix bool
rekram1-node Nov 23, 2025
4abd520
sync
thdxr Nov 23, 2025
fc74ff7
ci: stuff
thdxr Nov 23, 2025
34ef9f1
Update Nix flake.lock and hashes
actions-user Nov 23, 2025
f3a9208
ci: stuff
thdxr Nov 23, 2025
a01166c
Update Nix flake.lock and hashes
actions-user Nov 23, 2025
a444fe6
fix bash tool wsl
rekram1-node Nov 23, 2025
41ebbdb
ci: ignore
thdxr Nov 23, 2025
530c51a
release: v1.0.106
Nov 23, 2025
56cc494
Update Nix flake.lock and hashes
actions-user Nov 23, 2025
41f7f89
ci: ignore
thdxr Nov 23, 2025
10dc9f0
token
thdxr Nov 23, 2025
b026964
ci: secret
thdxr Nov 23, 2025
09ee41b
chore: format code
actions-user Nov 23, 2025
1e87f4f
ci: ignore
thdxr Nov 23, 2025
a4765c3
ci: ignore
thdxr Nov 23, 2025
b9aa445
ci: ignore
thdxr Nov 23, 2025
0bfbdd6
ci: remove log
thdxr Nov 23, 2025
1b7e822
tweak: slight improvements to title gen
rekram1-node Nov 23, 2025
d68e4b4
wip: zen
Nov 23, 2025
6eaccae
wip: zen
Nov 23, 2025
20f57ff
chore: format code
actions-user Nov 23, 2025
5e29dd7
fix: add explicit fallback model and prevent direct opencode provide…
no1wudi Nov 24, 2025
6a42f4b
nix: bundle js dist with bun and patch tree-sitter wasm paths (#4644)
Alb-O Nov 24, 2025
7e508e9
chore: format code
actions-user Nov 24, 2025
a8a8811
fix: fatal: undefined is not an object (evaluating 'color.buffer')
rekram1-node Nov 24, 2025
e2b7fd9
bump copilot plugin, give better error message for copilot (#4678)
rekram1-node Nov 24, 2025
4a99e4d
release: v1.0.107
Nov 24, 2025
83615fd
tweak: modified files sidebar
rekram1-node Nov 24, 2025
b703f3a
fix: persist light vs dark mode
rekram1-node Nov 24, 2025
30e223b
chore: format code
actions-user Nov 24, 2025
92b566b
tweak: display MCPs in alphabetic order in the sidebar. (#4680)
ariane-emory Nov 24, 2025
8b0d311
TUI: fix: add null check for user.time in duration calculation (#4679)
WohthaN Nov 24, 2025
7afdb2a
ignore: update download stats 2025-11-24
actions-user Nov 24, 2025
769ae2b
docs: add Ollama Cloud provider setup to providers.mdx (#4693)
fifthfrankie Nov 24, 2025
7f34821
non-corpo loading spinner
kommander Nov 24, 2025
7175cf2
chore: format code
actions-user Nov 24, 2025
bf4c515
release: v1.0.108
Nov 24, 2025
cfa3759
textarea highlight cursor color
kommander Nov 24, 2025
540ae3f
chore: format code
actions-user Nov 24, 2025
df67e68
wip: zen
Nov 24, 2025
bcd5243
ci: nix hash
rekram1-node Nov 24, 2025
b96d908
add autoupdate: notify
rekram1-node Nov 24, 2025
00c579b
zen: add opus 4.5
Nov 24, 2025
184dc30
Update Nix flake.lock and hashes
actions-user Nov 24, 2025
138572b
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 24, 2025
7990520
chore: format code
actions-user Nov 24, 2025
28cd7be
chore: format code
actions-user Nov 24, 2025
1984ba5
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 24, 2025
d43386f
Update Nix flake.lock and hashes
actions-user Nov 24, 2025
b6bf1fc
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 24, 2025
bf688b4
Token count (#33)
shuv1337 Nov 24, 2025
a93f752
Integration merge (#34)
shuv1337 Nov 24, 2025
7949469
Merge dev into integration: resolve conflicts
shuv1337 Nov 24, 2025
912d7ec
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 25, 2025
f7057b1
Merge upstream/dev into integration: sync with latest sst/opencode ch…
shuv1337 Nov 25, 2025
80b45f5
updates
shuv1337 Nov 25, 2025
12f6be4
Merge branch 'dev' into add-model-favorites
shuv1337 Nov 25, 2025
bd6efe0
Update Nix flake.lock and hashes
actions-user Nov 25, 2025
7d85dac
trying different favorites layout
shuv1337 Nov 25, 2025
e747767
one more tweak
shuv1337 Nov 25, 2025
13b2839
increasing recent model storage to allow for 5 favorites + 5 non-favo…
shuv1337 Nov 25, 2025
8d762ec
updates
shuv1337 Nov 25, 2025
2a615df
chore: format code
actions-user Nov 25, 2025
e0ca7d1
Add model favorites (#35)
shuv1337 Nov 25, 2025
b431e3f
continue limiting recents to 5
shuv1337 Nov 25, 2025
25f3396
Local test merge of add-model-favorites into integration
shuv1337 Nov 25, 2025
885b4d1
Update Nix flake.lock and hashes
actions-user Nov 25, 2025
90b5930
feat: add automated upstream sync workflow
shuv1337 Nov 25, 2025
d469ed2
fix: handle already up-to-date case in upstream sync
shuv1337 Nov 25, 2025
8ebc6da
remove .opencode in local fork
shuv1337 Nov 25, 2025
75ea4b5
integration updated to v1.0.110
shuv1337 Nov 25, 2025
9794c31
integration updated to v1.0.110
shuv1337 Nov 25, 2025
7f74843
chore: format code
actions-user Nov 25, 2025
7708540
shuvcode logo
shuv1337 Nov 25, 2025
bad3b0c
Merge origin/dev: sync to v1.0.112
shuv1337 Nov 25, 2025
edae6fe
chore: format code
actions-user Nov 25, 2025
01e86d7
Update Nix flake.lock and hashes
actions-user Nov 25, 2025
83c38a9
ci: use GITHUB_TOKEN instead of SST_GITHUB_TOKEN
shuv1337 Nov 26, 2025
bcd9930
ci: only trigger snapshot on dev branch
shuv1337 Nov 26, 2025
f810c0a
ci: pass token to checkout action
shuv1337 Nov 26, 2025
122015b
ci: use PAT_TOKEN for workflow permissions
shuv1337 Nov 26, 2025
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
255 changes: 130 additions & 125 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { generateObject, type ModelMessage } from "ai"
import PROMPT_GENERATE from "./generate.txt"
import { SystemPrompt } from "../session/system"
import { Instance } from "../project/instance"
import { State } from "../project/state"
import { mergeDeep } from "remeda"

export namespace Agent {
Expand Down Expand Up @@ -39,145 +40,149 @@ export namespace Agent {
})
export type Info = z.infer<typeof Info>

const state = Instance.state(async () => {
const cfg = await Config.get()
const defaultTools = cfg.tools ?? {}
const defaultPermission: Info["permission"] = {
edit: "allow",
bash: {
"*": "allow",
},
webfetch: "allow",
doom_loop: "ask",
external_directory: "ask",
}
const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})

const planPermission = mergeAgentPermissions(
{
edit: "deny",
const state = State.register(
"agent",
() => Instance.directory,
async () => {
const cfg = await Config.get()
const defaultTools = cfg.tools ?? {}
const defaultPermission: Info["permission"] = {
edit: "allow",
bash: {
"cut*": "allow",
"diff*": "allow",
"du*": "allow",
"file *": "allow",
"find * -delete*": "ask",
"find * -exec*": "ask",
"find * -fprint*": "ask",
"find * -fls*": "ask",
"find * -fprintf*": "ask",
"find * -ok*": "ask",
"find *": "allow",
"git diff*": "allow",
"git log*": "allow",
"git show*": "allow",
"git status*": "allow",
"git branch": "allow",
"git branch -v": "allow",
"grep*": "allow",
"head*": "allow",
"less*": "allow",
"ls*": "allow",
"more*": "allow",
"pwd*": "allow",
"rg*": "allow",
"sort --output=*": "ask",
"sort -o *": "ask",
"sort*": "allow",
"stat*": "allow",
"tail*": "allow",
"tree -o *": "ask",
"tree*": "allow",
"uniq*": "allow",
"wc*": "allow",
"whereis*": "allow",
"which*": "allow",
"*": "ask",
"*": "allow",
},
webfetch: "allow",
},
cfg.permission ?? {},
)
doom_loop: "ask",
external_directory: "ask",
}
const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})

Comment on lines +43 to 59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clarify per-agent permission merge semantics (possible loss of default doom_loop / external_directory behavior)

In the per-agent customization loop you do:

const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})

...

if (permission ?? cfg.permission) {
  item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
}

This means:

  • You first give each agent agentPermission, which includes defaultPermission’s doom_loop: "ask" and external_directory: "ask" when cfg.permission doesn’t override them.
  • For any config that has a global cfg.permission (even if it doesn’t mention doom_loop / external_directory), the later mergeAgentPermissions(cfg.permission ?? {}, permission ?? {}) overwrites item.permission, and mergeAgentPermissions will leave doom_loop / external_directory as undefined if they’re not present in cfg.permission or the per-agent override.

If the intent is “global defaults (with doom_loop/external_directory = ask) + global permission overrides + per-agent overrides”, you probably want to base this merge on the already-computed agentPermission (or item.permission) instead of raw cfg.permission, e.g.:

- if (permission ?? cfg.permission) {
-   item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
- }
+ if (permission) {
+   item.permission = mergeAgentPermissions(item.permission, permission)
+ }

(or an equivalent variant).

Also, mergeAgentPermissions currently mutates its arguments when bash is a string:

if (typeof basePermission.bash === "string") {
  basePermission.bash = { "*": basePermission.bash }
}

If callers pass shared config objects (e.g., cfg.permission), this will change their shape in-place. Consider cloning those inputs first if you want Config to remain structurally immutable.

Also applies to: 106-181, 226-262

🤖 Prompt for AI Agents
In packages/opencode/src/agent/agent.ts around lines 43-59, the per-agent
permission merge uses mergeAgentPermissions(cfg.permission ?? {}, permission ??
{}) which can drop default keys like doom_loop/external_directory and also
mergeAgentPermissions mutates input objects when bash is a string; change the
per-agent merge to base off the already-computed agentPermission (or
item.permission) so global defaults are preserved (e.g.,
mergeAgentPermissions(agentPermission, permission ?? {})), and update
mergeAgentPermissions to never mutate its arguments by cloning inputs before
converting bash strings to objects (create a shallow copy of the permission
objects and replace bash with a new object if it is a string), and apply the
same fix pattern to the other occurrences at lines 106-181 and 226-262.

const result: Record<string, Info> = {
general: {
name: "general",
description:
"General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
tools: {
todoread: false,
todowrite: false,
...defaultTools,
const planPermission = mergeAgentPermissions(
{
edit: "deny",
bash: {
"cut*": "allow",
"diff*": "allow",
"du*": "allow",
"file *": "allow",
"find * -delete*": "ask",
"find * -exec*": "ask",
"find * -fprint*": "ask",
"find * -fls*": "ask",
"find * -fprintf*": "ask",
"find * -ok*": "ask",
"find *": "allow",
"git diff*": "allow",
"git log*": "allow",
"git show*": "allow",
"git status*": "allow",
"git branch": "allow",
"git branch -v": "allow",
"grep*": "allow",
"head*": "allow",
"less*": "allow",
"ls*": "allow",
"more*": "allow",
"pwd*": "allow",
"rg*": "allow",
"sort --output=*": "ask",
"sort -o *": "ask",
"sort*": "allow",
"stat*": "allow",
"tail*": "allow",
"tree -o *": "ask",
"tree*": "allow",
"uniq*": "allow",
"wc*": "allow",
"whereis*": "allow",
"which*": "allow",
"*": "ask",
},
webfetch: "allow",
},
options: {},
permission: agentPermission,
mode: "subagent",
builtIn: true,
},
build: {
name: "build",
tools: { ...defaultTools },
options: {},
permission: agentPermission,
mode: "primary",
builtIn: true,
},
plan: {
name: "plan",
options: {},
permission: planPermission,
tools: {
...defaultTools,
cfg.permission ?? {},
)

const result: Record<string, Info> = {
general: {
name: "general",
description:
"General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
tools: {
todoread: false,
todowrite: false,
...defaultTools,
},
options: {},
permission: agentPermission,
mode: "subagent",
builtIn: true,
},
mode: "primary",
builtIn: true,
},
}
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
if (value.disable) {
delete result[key]
continue
}
let item = result[key]
if (!item)
item = result[key] = {
name: key,
mode: "all",
build: {
name: "build",
tools: { ...defaultTools },
options: {},
permission: agentPermission,
mode: "primary",
builtIn: true,
},
plan: {
name: "plan",
options: {},
tools: {},
builtIn: false,
}
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, color, ...extra } = value
item.options = {
...item.options,
...extra,
permission: planPermission,
tools: {
...defaultTools,
},
mode: "primary",
builtIn: true,
},
}
if (model) item.model = Provider.parseModel(model)
if (prompt) item.prompt = prompt
if (tools)
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
if (value.disable) {
delete result[key]
continue
}
let item = result[key]
if (!item)
item = result[key] = {
name: key,
mode: "all",
permission: agentPermission,
options: {},
tools: {},
builtIn: false,
}
const { name, model, prompt, tools, description, temperature, top_p, mode, color, permission, ...extra } = value
item.options = {
...item.options,
...extra,
}
if (model) item.model = Provider.parseModel(model)
if (prompt) item.prompt = prompt
if (tools)
item.tools = {
...item.tools,
...tools,
}
item.tools = {
...defaultTools,
...item.tools,
...tools,
}
item.tools = {
...defaultTools,
...item.tools,
}
if (description) item.description = description
if (temperature != undefined) item.temperature = temperature
if (top_p != undefined) item.topP = top_p
if (mode) item.mode = mode
if (color) item.color = color
// just here for consistency & to prevent it from being added as an option
if (name) item.name = name
if (description) item.description = description
if (temperature != undefined) item.temperature = temperature
if (top_p != undefined) item.topP = top_p
if (mode) item.mode = mode
if (color) item.color = color
// just here for consistency & to prevent it from being added as an option
if (name) item.name = name

if (permission ?? cfg.permission) {
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
if (permission ?? cfg.permission) {
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
}
}
}
return result
})
return result
},
)

export async function get(agent: string) {
return state().then((x) => x[agent])
Expand Down
18 changes: 18 additions & 0 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,24 @@ function App() {
local.model.cycle(-1)
},
},
{
title: "Favorite cycle",
value: "model.cycle_favorite",
keybind: "model_cycle_favorite",
category: "Agent",
onSelect: () => {
local.model.cycleFavorite(1)
},
},
{
title: "Favorite cycle reverse",
value: "model.cycle_favorite_reverse",
keybind: "model_cycle_favorite_reverse",
category: "Agent",
onSelect: () => {
local.model.cycleFavorite(-1)
},
},
{
title: "Switch agent",
value: "agent.list",
Expand Down
Loading