Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
765812c
feat: add configurable subagent visibility per agent
Sewer56 Nov 26, 2025
9de0068
docs: add subagents configuration documentation
Sewer56 Nov 26, 2025
b582b53
fix: TUI autocomplete now respects agent subagents visibility config
Sewer56 Nov 26, 2025
515e859
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Nov 26, 2025
3c7b3e5
Merge tag 'v1.0.115' into add-hide-subagents
Sewer56 Nov 26, 2025
eab8fc8
chore: format code
actions-user Nov 26, 2025
0d2a06c
Merge branch 'dev' into add-hide-subagents
Sewer56 Nov 29, 2025
1bbb7d0
Update Nix flake.lock and hashes
actions-user Nov 29, 2025
29cdde3
Merge branch 'dev' into add-hide-subagents
Sewer56 Nov 30, 2025
a7924bd
Update Nix flake.lock and hashes
actions-user Nov 30, 2025
1e5c116
Merge branch 'dev' into add-hide-subagents
Sewer56 Dec 3, 2025
d51e467
Merge branch 'dev' into add-hide-subagents
Sewer56 Dec 4, 2025
aaea27e
Merge branch 'dev' into add-hide-subagents
Sewer56 Dec 6, 2025
f3b40d7
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 12, 2025
a52acd1
feat(opencode): add visible field and task permission to agents
Sewer56 Dec 12, 2025
58f8dce
fix: don't hide visible==false from system prompt.
Sewer56 Dec 12, 2025
cbf549f
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 12, 2025
f353f08
improve: allow user to invoke agents even if they are denied
Sewer56 Dec 12, 2025
c42bd93
improve: added quick note signifying that the subagent call is user i…
Sewer56 Dec 12, 2025
3d290b8
refactor: rename visible to hidden for subagent config
Sewer56 Dec 16, 2025
50fd411
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 16, 2025
7bf9f16
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 18, 2025
3120e07
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 20, 2025
b761290
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 21, 2025
16aec20
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 21, 2025
74175bd
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 22, 2025
3cba3a9
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 22, 2025
bc82564
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 23, 2025
e004cdc
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 24, 2025
fe55ee1
Merge tag 'v1.0.195' into add-hide-subagents
Sewer56 Dec 24, 2025
cc79bd7
Merge tag 'v1.0.197' into add-hide-subagents
Sewer56 Dec 24, 2025
270ff9c
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 26, 2025
fbcb276
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 26, 2025
cac9b3a
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 27, 2025
5023b0c
Merge tag 'v1.0.204' into add-hide-subagents
Sewer56 Dec 28, 2025
fb5cc53
Merge remote-tracking branch 'origin/dev' into add-hide-subagents
Sewer56 Dec 31, 2025
7ae8567
Merge tag 'v1.0.220' into add-hide-subagents
Sewer56 Dec 31, 2025
42d66de
Merge tag 'v1.0.221' into add-hide-subagents
Sewer56 Jan 1, 2026
59c67a1
Merge tag 'v1.0.222' into add-hide-subagents
Sewer56 Jan 1, 2026
45b4078
Merge tag 'v1.0.223' into add-hide-subagents
Sewer56 Jan 2, 2026
51c0d37
Merge tag 'before-permission-rework' into add-hide-subagents
Sewer56 Jan 2, 2026
3eb358f
feat: allow hiding subagents and controlling task invocation permissions
Sewer56 Jan 2, 2026
54614c0
Merge 3eb358f: recreate hide subagents feature on permission rework
Sewer56 Jan 2, 2026
f9da342
Merge tag 'v1.0.224' into add-hide-subagents
Sewer56 Jan 2, 2026
2798b2f
fix: treat slash command subagents as user-invoked to bypass permissi…
Sewer56 Jan 2, 2026
da0e1dd
fix: treat slash command subagents as user-invoked to bypass permissi…
Sewer56 Jan 2, 2026
6176b51
Merge tag 'v1.1.1' into add-hide-subagents
Sewer56 Jan 5, 2026
c831237
Merge remote-tracking branch 'origin/production' into add-hide-subagents
Sewer56 Jan 5, 2026
932b457
fix: include task tool when subagent-specific patterns are allowed
Sewer56 Jan 5, 2026
1063b13
docs: clarify that last matching rule wins for task permissions
Sewer56 Jan 5, 2026
e354ac9
Merge branch 'dev' into add-hide-subagents
rekram1-node Jan 5, 2026
dff1c6d
rm todo
rekram1-node Jan 6, 2026
966171c
Merge branch 'dev' into add-hide-subagents
rekram1-node Jan 7, 2026
7a46f44
clean
rekram1-node Jan 7, 2026
2c670a6
test: fix
rekram1-node Jan 7, 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
1 change: 1 addition & 0 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export namespace Agent {
item.topP = value.top_p ?? item.topP
item.mode = value.mode ?? item.mode
item.color = value.color ?? item.color
item.hidden = value.hidden ?? item.hidden
item.name = value.name ?? item.name
item.steps = value.steps ?? item.steps
item.options = mergeDeep(item.options, value.options ?? {})
Expand Down
5 changes: 5 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ export namespace Config {
disable: z.boolean().optional(),
description: z.string().optional().describe("Description of when to use the agent"),
mode: z.enum(["subagent", "primary", "all"]).optional(),
hidden: z
.boolean()
.optional()
.describe("Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)"),
options: z.record(z.string(), z.any()).optional(),
color: z
.string()
Expand All @@ -490,6 +494,7 @@ export namespace Config {
"temperature",
"top_p",
"mode",
"hidden",
"color",
"steps",
"maxSteps",
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/permission/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export namespace PermissionNext {
const result = new Set<string>()
for (const tool of tools) {
const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool

const rule = ruleset.findLast((r) => Wildcard.match(permission, r.permission))
if (!rule) continue
if (rule.pattern === "*" && rule.action === "deny") result.add(tool)
Expand Down
49 changes: 44 additions & 5 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { SessionSummary } from "./summary"
import { NamedError } from "@opencode-ai/util/error"
import { fn } from "@/util/fn"
import { SessionProcessor } from "./processor"
import { TaskTool } from "@/tool/task"
import { TaskTool, filterSubagents, TASK_DESCRIPTION } from "@/tool/task"
import { Tool } from "@/tool/tool"
import { PermissionNext } from "@/permission/next"
import { SessionStatus } from "./status"
Expand Down Expand Up @@ -382,7 +382,8 @@ export namespace SessionPrompt {
messageID: assistantMessage.id,
sessionID: sessionID,
abort,
extra: { bypassAgentCheck: true },
callID: part.callID,
extra: { userInvokedAgents: [task.agent] },
async metadata(input) {
await Session.updatePart({
...part,
Expand Down Expand Up @@ -543,12 +544,20 @@ export namespace SessionPrompt {
model,
abort,
})

// Track agents explicitly invoked by user via @ autocomplete
const userInvokedAgents = msgs
.filter((m) => m.info.role === "user")
.flatMap((m) => m.parts.filter((p) => p.type === "agent") as MessageV2.AgentPart[])
.map((p) => p.name)

const tools = await resolveTools({
agent,
session,
model,
tools: lastUser.tools,
processor,
userInvokedAgents,
})

if (step === 1) {
Expand Down Expand Up @@ -637,6 +646,7 @@ export namespace SessionPrompt {
session: Session.Info
tools?: Record<string, boolean>
processor: SessionProcessor.Info
userInvokedAgents: string[]
}) {
using _ = log.time("resolveTools")
const tools: Record<string, AITool> = {}
Expand All @@ -646,7 +656,7 @@ export namespace SessionPrompt {
abort: options.abortSignal!,
messageID: input.processor.message.id,
callID: options.toolCallId,
extra: { model: input.model },
extra: { model: input.model, userInvokedAgents: input.userInvokedAgents },
agent: input.agent.name,
metadata: async (val: { title?: string; metadata?: any }) => {
const match = input.processor.partFromToolCall(options.toolCallId)
Expand Down Expand Up @@ -789,6 +799,29 @@ export namespace SessionPrompt {
}
tools[key] = item
}

// Regenerate task tool description with filtered subagents
if (tools.task) {
const all = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
const filtered = filterSubagents(all, input.agent.permission)

// If no subagents are permitted, remove the task tool entirely
if (filtered.length === 0) {
delete tools.task
} else {
const description = TASK_DESCRIPTION.replace(
"{agents}",
filtered
.map((a) => `- ${a.name}: ${a.description ?? "This subagent should only be called manually by the user."}`)
.join("\n"),
)
tools.task = {
...tools.task,
description,
}
}
}

return tools
}

Expand Down Expand Up @@ -1098,6 +1131,9 @@ export namespace SessionPrompt {
}

if (part.type === "agent") {
// Check if this agent would be denied by task permission
const perm = PermissionNext.evaluate("task", part.name, agent.permission)
const hint = perm.action === "deny" ? " . Invoked by user; guaranteed to exist." : ""
return [
{
id: Identifier.ascending("part"),
Expand All @@ -1111,9 +1147,12 @@ export namespace SessionPrompt {
sessionID: input.sessionID,
type: "text",
synthetic: true,
// An extra space is added here. Otherwise the 'Use' gets appended
// to user's last word; making a combined word
text:
"Use the above message and context to generate a prompt and call the task tool with subagent: " +
part.name,
" Use the above message and context to generate a prompt and call the task tool with subagent: " +
part.name +
hint,
},
]
}
Expand Down
11 changes: 10 additions & 1 deletion packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { SessionPrompt } from "../session/prompt"
import { iife } from "@/util/iife"
import { defer } from "@/util/defer"
import { Config } from "../config/config"
import { PermissionNext } from "@/permission/next"

export { DESCRIPTION as TASK_DESCRIPTION }

export function filterSubagents(agents: Agent.Info[], ruleset: PermissionNext.Ruleset) {
return agents.filter((a) => PermissionNext.evaluate("task", a.name, ruleset).action !== "deny")
}

export const TaskTool = Tool.define("task", async () => {
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
Expand All @@ -30,8 +37,10 @@ export const TaskTool = Tool.define("task", async () => {
}),
async execute(params, ctx) {
const config = await Config.get()

const userInvokedAgents = (ctx.extra?.userInvokedAgents ?? []) as string[]
// Skip permission check when invoked from a command subtask (user already approved by invoking the command)
if (!ctx.extra?.bypassAgentCheck) {
if (!ctx.extra?.bypassAgentCheck && !userInvokedAgents.includes(params.subagent_type)) {
await ctx.ask({
permission: "task",
patterns: [params.subagent_type],
Expand Down
Loading