From fbacffae447bdda6838ca2d76250678762f1e9fe Mon Sep 17 00:00:00 2001 From: ops Date: Wed, 31 Dec 2025 23:46:11 +0100 Subject: [PATCH 1/3] (feat) customizable subagent config and system env prompt --- packages/opencode/src/session/compaction.ts | 3 ++- packages/opencode/src/session/llm.ts | 5 +++- packages/opencode/src/session/prompt.ts | 3 ++- packages/opencode/src/session/summary.ts | 25 +++++++++++++++++-- packages/opencode/src/session/system.ts | 27 ++++++++++++++++----- packages/opencode/src/tool/task.ts | 10 +++++--- 6 files changed, 59 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index fb382530291..5095a8bb768 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -97,7 +97,8 @@ export namespace SessionCompaction { auto: boolean }) { const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User - const agent = await Agent.get("compaction") + const msgAgent = await Agent.get(userMessage.agent) + const agent = await Agent.get(msgAgent.options.agents?.compaction ?? "compaction") const model = agent.model ? await Provider.getModel(agent.model.providerID, agent.model.modelID) : await Provider.getModel(userMessage.model.providerID, userMessage.model.modelID) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index d651308032e..4e6b58801d8 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -67,13 +67,16 @@ export namespace LLM { const isCodex = provider.id === "openai" && auth?.type === "oauth" const system = [] + const customSystem = input.agent.options.system + ? await SystemPrompt.environment(input.model, input.agent.options.system) + : input.system system.push( [ // use agent prompt otherwise provider prompt // For Codex sessions, skip SystemPrompt.provider() since it's sent via options.instructions ...(input.agent.prompt ? [input.agent.prompt] : isCodex ? [] : SystemPrompt.provider(input.model)), // any custom prompt passed into this call - ...input.system, + ...customSystem, // any custom prompt from last user message ...(input.user.system ? [input.user.system] : []), ] diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 23ca473541c..b408c5ad817 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1773,7 +1773,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the const subtaskParts = firstRealUser.parts.filter((p) => p.type === "subtask") as MessageV2.SubtaskPart[] const hasOnlySubtaskParts = subtaskParts.length > 0 && firstRealUser.parts.every((p) => p.type === "subtask") - const agent = await Agent.get("title") + const msgAgent = await Agent.get(firstRealUser.info.agent) + const agent = await Agent.get(msgAgent.options.agents?.title ?? "title") if (!agent) return const model = await iife(async () => { if (agent.model) return await Provider.getModel(agent.model.providerID, agent.model.modelID) diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 91a520a9bdf..2c5a9077216 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -132,8 +132,22 @@ export namespace SessionSummary { const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart if (textPart && !userMsg.summary?.title) { - const agent = await Agent.get("title") - if (!agent) return + const msgAgent = await Agent.get(userMsg.agent) + const titleAgent = msgAgent.options.agents?.title ?? "title" + if (titleAgent === "none") { + const title = textPart.text.length > 50 ? textPart.text.slice(0, 50) + "..." : textPart.text + userMsg.summary.title = title + await Session.updateMessage(userMsg) + await Session.update( + userMsg.sessionID, + (draft) => { + draft.title = title + }, + { touch: false }, + ) + return + } + const agent = await Agent.get(titleAgent) const stream = await LLM.stream({ agent, user: userMsg, @@ -163,6 +177,13 @@ export namespace SessionSummary { log.info("title", { title: result }) userMsg.summary.title = result await Session.updateMessage(userMsg) + await Session.update( + userMsg.sessionID, + (draft) => { + draft.title = result + }, + { touch: false }, + ) } } diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index d34a086fe44..d84fc590450 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -24,11 +24,20 @@ export namespace SystemPrompt { return [PROMPT_ANTHROPIC_WITHOUT_TODO] } - export async function environment(model: Provider.Model) { + export async function environment( + model: Provider.Model, + options?: { model_id?: boolean; env?: boolean; files?: boolean }, + ) { const project = Instance.project - return [ - [ + const parts: string[] = [] + + if (options?.model_id !== false) + parts.push( `You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`, + ) + + if (options?.env !== false) + parts.push( `Here is some useful information about the environment you are running in:`, ``, ` Working directory: ${Instance.directory}`, @@ -36,9 +45,14 @@ export namespace SystemPrompt { ` Platform: ${process.platform}`, ` Today's date: ${new Date().toDateString()}`, ``, + ) + + // files info was disabled during permission rework + if (options?.files !== false && false) + parts.push( ``, ` ${ - project.vcs === "git" && false + project.vcs === "git" ? await Ripgrep.tree({ cwd: Instance.directory, limit: 200, @@ -46,7 +60,8 @@ export namespace SystemPrompt { : "" }`, ``, - ].join("\n"), - ] + ) + + return [parts.join("\n")] } } diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index c87add638aa..afdbf9e2dd4 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -41,20 +41,24 @@ export const TaskTool = Tool.define("task", async (ctx) => { async execute(params: z.infer, ctx) { const config = await Config.get() + // Resolve subagent type through calling agent's mapping + const callingAgent = ctx.agent ? await Agent.get(ctx.agent) : undefined + const resolvedSubagentType = callingAgent?.options.agents?.[params.subagent_type] ?? params.subagent_type + // Skip permission check when user explicitly invoked via @ or command subtask if (!ctx.extra?.bypassAgentCheck) { await ctx.ask({ permission: "task", - patterns: [params.subagent_type], + patterns: [resolvedSubagentType], always: ["*"], metadata: { description: params.description, - subagent_type: params.subagent_type, + subagent_type: resolvedSubagentType, }, }) } - const agent = await Agent.get(params.subagent_type) + const agent = await Agent.get(resolvedSubagentType) if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`) const hasTaskPermission = agent.permission.some((rule) => rule.permission === "task") From 3adcdc8a10199d18d020828beb5865d13a75bb8f Mon Sep 17 00:00:00 2001 From: ops Date: Wed, 28 Jan 2026 09:00:23 +0100 Subject: [PATCH 2/3] gpt subagent fix --- packages/opencode/src/session/llm.ts | 4 +--- packages/opencode/src/session/prompt.ts | 21 +++++++++++++++++++-- packages/opencode/src/session/summary.ts | 14 -------------- packages/opencode/src/session/system.ts | 3 +-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 4e6b58801d8..45aadc53000 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -67,9 +67,7 @@ export namespace LLM { const isCodex = provider.id === "openai" && auth?.type === "oauth" const system = [] - const customSystem = input.agent.options.system - ? await SystemPrompt.environment(input.model, input.agent.options.system) - : input.system + const customSystem = input.system system.push( [ // use agent prompt otherwise provider prompt diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index b408c5ad817..ca547b792bc 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -945,7 +945,7 @@ export namespace SessionPrompt { ] } break - case "file:": + case "file:": { log.info("file", { mime: part.mime }) // have to normalize, symbol search returns absolute paths // Decode the pathname since URL constructor doesn't automatically decode it @@ -1128,6 +1128,7 @@ export namespace SessionPrompt { source: part.source, }, ] + } } } @@ -1774,7 +1775,23 @@ NOTE: At any point in time through this workflow you should feel free to ask the const hasOnlySubtaskParts = subtaskParts.length > 0 && firstRealUser.parts.every((p) => p.type === "subtask") const msgAgent = await Agent.get(firstRealUser.info.agent) - const agent = await Agent.get(msgAgent.options.agents?.title ?? "title") + const titleAgent = msgAgent.options.agents?.title ?? "title" + if (titleAgent === "none") { + const textPart = firstRealUser.parts.find((part) => part.type === "text" && !part.synthetic) as + | MessageV2.TextPart + | undefined + const text = textPart?.text?.trim() + if (!text) return + const title = text.length > 50 ? text.slice(0, 50) + "..." : text + return Session.update( + input.session.id, + (draft) => { + draft.title = title + }, + { touch: false }, + ) + } + const agent = await Agent.get(titleAgent) if (!agent) return const model = await iife(async () => { if (agent.model) return await Provider.getModel(agent.model.providerID, agent.model.modelID) diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 2c5a9077216..2932d9154b4 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -138,13 +138,6 @@ export namespace SessionSummary { const title = textPart.text.length > 50 ? textPart.text.slice(0, 50) + "..." : textPart.text userMsg.summary.title = title await Session.updateMessage(userMsg) - await Session.update( - userMsg.sessionID, - (draft) => { - draft.title = title - }, - { touch: false }, - ) return } const agent = await Agent.get(titleAgent) @@ -177,13 +170,6 @@ export namespace SessionSummary { log.info("title", { title: result }) userMsg.summary.title = result await Session.updateMessage(userMsg) - await Session.update( - userMsg.sessionID, - (draft) => { - draft.title = result - }, - { touch: false }, - ) } } diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index d84fc590450..fd537657d37 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -47,8 +47,7 @@ export namespace SystemPrompt { ``, ) - // files info was disabled during permission rework - if (options?.files !== false && false) + if (options?.files !== false) parts.push( ``, ` ${ From 001c20127da42965222de586ee5f4326a6538df9 Mon Sep 17 00:00:00 2001 From: ops Date: Wed, 28 Jan 2026 16:58:50 +0100 Subject: [PATCH 3/3] resolve slop --- packages/opencode/src/session/llm.ts | 4 +++- packages/opencode/src/session/prompt.ts | 3 +-- packages/opencode/src/session/system.ts | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 45aadc53000..4e6b58801d8 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -67,7 +67,9 @@ export namespace LLM { const isCodex = provider.id === "openai" && auth?.type === "oauth" const system = [] - const customSystem = input.system + const customSystem = input.agent.options.system + ? await SystemPrompt.environment(input.model, input.agent.options.system) + : input.system system.push( [ // use agent prompt otherwise provider prompt diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ca547b792bc..ac610d20d64 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -945,7 +945,7 @@ export namespace SessionPrompt { ] } break - case "file:": { + case "file:": log.info("file", { mime: part.mime }) // have to normalize, symbol search returns absolute paths // Decode the pathname since URL constructor doesn't automatically decode it @@ -1128,7 +1128,6 @@ export namespace SessionPrompt { source: part.source, }, ] - } } } diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index fd537657d37..d84fc590450 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -47,7 +47,8 @@ export namespace SystemPrompt { ``, ) - if (options?.files !== false) + // files info was disabled during permission rework + if (options?.files !== false && false) parts.push( ``, ` ${