summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2026-04-14 11:37:33 -0500
committerGitHub <[email protected]>2026-04-14 11:37:33 -0500
commitb1312a3181bdd5f93ab2fcefdf4423ed464e33e3 (patch)
treedec69b0c02f7eb2832440452277103880b9912ac /packages
parenta8f9f6b7059bc9edf8184f8ebddf32dd1c6030e8 (diff)
downloadopencode-b1312a3181bdd5f93ab2fcefdf4423ed464e33e3.tar.gz
opencode-b1312a3181bdd5f93ab2fcefdf4423ed464e33e3.zip
core: prevent duplicate user messages in ACP clients (#22468)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/acp/agent.ts6
-rw-r--r--packages/opencode/test/acp/event-subscription.test.ts40
2 files changed, 46 insertions, 0 deletions
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index 235280a0d..a22e6c462 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -453,6 +453,12 @@ export namespace ACP {
return
}
}
+
+ // ACP clients already know the prompt they just submitted, so replaying
+ // live user parts duplicates the message. We still replay user history in
+ // loadSession() and forkSession() via processMessage().
+ if (part.type !== "text" && part.type !== "file") return
+
return
}
diff --git a/packages/opencode/test/acp/event-subscription.test.ts b/packages/opencode/test/acp/event-subscription.test.ts
index a3ae01c5c..a0944e33b 100644
--- a/packages/opencode/test/acp/event-subscription.test.ts
+++ b/packages/opencode/test/acp/event-subscription.test.ts
@@ -295,6 +295,46 @@ describe("acp.agent event subscription", () => {
})
})
+ test("does not emit user_message_chunk for live prompt parts", async () => {
+ await using tmp = await tmpdir()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const { agent, controller, sessionUpdates, stop } = createFakeAgent()
+ const cwd = "/tmp/opencode-acp-test"
+ const sessionId = await agent.newSession({ cwd, mcpServers: [] } as any).then((x) => x.sessionId)
+
+ controller.push({
+ directory: cwd,
+ payload: {
+ type: "message.part.updated",
+ properties: {
+ sessionID: sessionId,
+ time: Date.now(),
+ part: {
+ id: "part_1",
+ sessionID: sessionId,
+ messageID: "msg_user",
+ type: "text",
+ text: "hello",
+ },
+ },
+ },
+ } as any)
+
+ await new Promise((r) => setTimeout(r, 20))
+
+ expect(
+ sessionUpdates
+ .filter((u) => u.sessionId === sessionId)
+ .some((u) => u.update.sessionUpdate === "user_message_chunk"),
+ ).toBe(false)
+
+ stop()
+ },
+ })
+ })
+
test("keeps concurrent sessions isolated when message.part.delta events are interleaved", async () => {
await using tmp = await tmpdir()
await Instance.provide({