From 71ebf2f84c18761ef8b9e29dbff86559fe178690 Mon Sep 17 00:00:00 2001 From: Sami Jawhar Date: Mon, 27 Apr 2026 02:25:49 +0000 Subject: [PATCH] fix(session): preserve agent and model on async prompt When a prompt comes in without explicit agent/model fields (the common case for prompt_async), createUserMessage was falling back to the config's default agent and that agent's default model. This caused the session to silently switch agents/models mid-conversation. Fix: read the most recent user message in the session and prefer its agent and model as the fallback before going to defaults. Both fields fall through together because the user's previous choice was a coherent pairing. Replaces the dropped fix/prompt-async-agent-preserve branch with a minimal version. Loop continuation guard and 30-min timeout from the original branch are intentionally omitted; can be added later if needed. --- packages/opencode/src/session/prompt.ts | 10 ++++-- packages/opencode/test/session/prompt.test.ts | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 22fe4d81cd40..0ac920be37f2 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -691,7 +691,13 @@ export const layer = Layer.effect( }) const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) { - const agentName = input.agent + const prev = input.agent + ? undefined + : (yield* MessageV2.filterCompactedEffect(input.sessionID)).findLast( + (m): m is MessageV2.WithParts & { info: MessageV2.User } => + m.info.role === "user" && !!m.info.agent, + ) + const agentName = input.agent ?? prev?.info.agent const ag = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo() if (!ag) { const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name) @@ -708,7 +714,7 @@ export const layer = Layer.effect( .where(eq(SessionTable.id, input.sessionID)) .get(), ) - const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID)) + const model = input.model ?? prev?.info.model ?? ag.model ?? (yield* currentModel(input.sessionID)) const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID const full = !input.variant && ag.variant && same diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 4c4647457814..ceedd1a105f7 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -2187,6 +2187,41 @@ it.instance( // Agent variant +noLLMServer.instance( + "prompt without agent and model preserves current session agent and model", + () => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + model: ref, + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + + const next = yield* prompt.prompt({ + sessionID: session.id, + noReply: true, + parts: [{ type: "text", text: "hello again" }], + }) + if (next.info.role !== "user") throw new Error("expected user message") + expect(next.info.agent).toBe("build") + expect(next.info.model).toEqual(ref) + + yield* sessions.remove(session.id) + }), + { + config: { + ...cfg, + default_agent: "plan", + }, + }, +) + noLLMServer.instance( "applies agent variant only when using agent model", () => @@ -2256,6 +2291,7 @@ noLLMServer.instance( }, ) + // Agent / command resolution errors noLLMServer.instance(