From c1a6c3fe0222a88cd63bfb91351195f9f091a496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=A8=E5=8D=93?= Date: Thu, 13 Aug 2026 16:10:56 +0800 Subject: [PATCH 1/2] fix(cli): preserve session agent for headless prompts Fall back to the agent selected during session creation when a headless prompt does not specify one. This preserves custom agent models and permissions instead of resetting the session to the default code agent. Fixes #13027 --- .changeset/calm-agents-wait.md | 5 ++ packages/opencode/src/session/prompt.ts | 2 +- .../kilocode/headless-session-agent.test.ts | 73 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 .changeset/calm-agents-wait.md create mode 100644 packages/opencode/test/kilocode/headless-session-agent.test.ts diff --git a/.changeset/calm-agents-wait.md b/.changeset/calm-agents-wait.md new file mode 100644 index 00000000000..e745549cb83 --- /dev/null +++ b/.changeset/calm-agents-wait.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Preserve the selected session agent when sending headless prompts without an explicit agent. diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0007b11b75d..86660b977e6 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -816,7 +816,7 @@ export const layer = Layer.effect( }) const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) { - const agentName = input.agent + const agentName = input.agent ?? (yield* sessions.get(input.sessionID).pipe(Effect.orDie)).agent // kilocode_change 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) diff --git a/packages/opencode/test/kilocode/headless-session-agent.test.ts b/packages/opencode/test/kilocode/headless-session-agent.test.ts new file mode 100644 index 00000000000..cc8ac79b8b1 --- /dev/null +++ b/packages/opencode/test/kilocode/headless-session-agent.test.ts @@ -0,0 +1,73 @@ +import { afterEach, expect, test } from "bun:test" +import { Server } from "../../src/server/server" +import { disposeAllInstances, provideTestInstance, tmpdir } from "../fixture/fixture" +import { resetDatabase } from "../fixture/db" + +afterEach(async () => { + await disposeAllInstances() + await resetDatabase() +}) + +test("headless prompts preserve the agent selected when the session was created", async () => { + await using tmp = await tmpdir({ + config: { + formatter: false, + lsp: false, + agent: { + "test-engineer": { + mode: "primary", + model: "mock/custom-model", + }, + }, + provider: { + mock: { + npm: "@ai-sdk/openai-compatible", + name: "Mock", + options: { baseURL: "http://127.0.0.1:1/v1", apiKey: "test" }, + models: { + "custom-model": { + name: "Custom Model", + limit: { context: 128000, output: 8192 }, + }, + }, + }, + }, + }, + }) + + await provideTestInstance({ + directory: tmp.path, + fn: async () => { + const app = Server.Default().app + const headers = { "content-type": "application/json", "x-kilo-directory": tmp.path } + const created = await app.request("/session", { + method: "POST", + headers, + body: JSON.stringify({ agent: "test-engineer" }), + }) + expect(created.status).toBe(200) + const session = (await created.json()) as { id: string; agent?: string } + expect(session.agent).toBe("test-engineer") + + const prompted = await app.request(`/session/${session.id}/prompt`, { + method: "POST", + headers, + body: JSON.stringify({ + noReply: true, + parts: [{ type: "text", text: "hello" }], + }), + }) + expect(prompted.status).toBe(200) + const message = (await prompted.json()) as { + info: { agent: string; model: { providerID: string; modelID: string } } + } + expect(message.info.agent).toBe("test-engineer") + expect(message.info.model).toEqual({ providerID: "mock", modelID: "custom-model" }) + + const loaded = await app.request(`/session/${session.id}`, { headers }) + expect(loaded.status).toBe(200) + const current = (await loaded.json()) as { agent?: string } + expect(current.agent).toBe("test-engineer") + }, + }) +}) From 80b54f7bfdc40ceb825d60efc668256f7535986b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=A8=E5=8D=93?= Date: Thu, 13 Aug 2026 16:43:04 +0800 Subject: [PATCH 2/2] test(cli): use the correct headless prompt endpoint Send the regression test request through the v1 session message endpoint so it reaches the synchronous prompt handler. Addresses review feedback on #13102. --- packages/opencode/test/kilocode/headless-session-agent.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/test/kilocode/headless-session-agent.test.ts b/packages/opencode/test/kilocode/headless-session-agent.test.ts index cc8ac79b8b1..f06db730924 100644 --- a/packages/opencode/test/kilocode/headless-session-agent.test.ts +++ b/packages/opencode/test/kilocode/headless-session-agent.test.ts @@ -49,7 +49,7 @@ test("headless prompts preserve the agent selected when the session was created" const session = (await created.json()) as { id: string; agent?: string } expect(session.agent).toBe("test-engineer") - const prompted = await app.request(`/session/${session.id}/prompt`, { + const prompted = await app.request(`/session/${session.id}/message`, { method: "POST", headers, body: JSON.stringify({