From b042fc5698aa97a3512e5dae1bdd9d28b5265aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E5=82=B2=E5=AE=B8?= Date: Wed, 11 Mar 2026 17:17:34 +0800 Subject: [PATCH] fix session loop exit for external user message ids --- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/test/session/prompt.test.ts | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 655afd2b14d9..071818eca26f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -319,7 +319,7 @@ export namespace SessionPrompt { if ( lastAssistant?.finish && !["tool-calls", "unknown"].includes(lastAssistant.finish) && - lastUser.id < lastAssistant.id + lastAssistant.parentID === lastUser.id ) { log.info("exiting loop", { sessionID }) break diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index e8a8c65b03dd..48cabb286104 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1,6 +1,7 @@ import path from "path" import { describe, expect, test } from "bun:test" import { fileURLToPath } from "url" +import { Identifier } from "../../src/id/id" import { Instance } from "../../src/project/instance" import { Session } from "../../src/session" import { MessageV2 } from "../../src/session/message-v2" @@ -209,3 +210,65 @@ describe("session.prompt agent variant", () => { } }) }) + +describe("session.prompt loop exit", () => { + test( + "stops after the first finished assistant when the user id is provided externally", + async () => { + await using tmp = await tmpdir({ + config: {}, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await Session.create({ title: "loop exit" }) + const messageID = Identifier.create("message", false, Date.now() + 60_000) + await Session.updateMessage({ + id: messageID, + sessionID: session.id, + role: "user", + time: { created: Date.now() }, + agent: "build", + model: { providerID: "missing", modelID: "missing" }, + tools: {}, + }) + const reply = await Session.updateMessage({ + id: Identifier.ascending("message"), + sessionID: session.id, + role: "assistant", + parentID: messageID, + modelID: "missing", + providerID: "missing", + mode: "build", + agent: "build", + path: { + cwd: tmp.path, + root: tmp.path, + }, + cost: 0, + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + time: { + created: Date.now(), + completed: Date.now(), + }, + finish: "stop", + }) + + const result = await SessionPrompt.loop({ sessionID: session.id }) + expect(result.info.role).toBe("assistant") + expect(result.info.id).toBe(reply.id) + if (result.info.role !== "assistant") return + expect(result.info.parentID).toBe(messageID) + + await Session.remove(session.id) + }, + }) + }, + ) +})