Keep completed OpenCode turns idle (#2336)
Ignore post-turn metadata updates for user messages already emitted so completed OpenCode turns remain idle.
This commit is contained in:
@@ -2728,6 +2728,65 @@ describe("OpenCode provider subagent contract", () => {
|
||||
await parent.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("does not start a new autonomous turn when post-turn user message updates arrive for an already emitted message", async () => {
|
||||
const { parent, openCode } = await createParentSession("ses_parent_post_turn");
|
||||
openCode.sessionPromptAsyncEvents = [
|
||||
...userMessageEvents({
|
||||
sessionId: "ses_parent_post_turn",
|
||||
messageId: "msg_user_1",
|
||||
text: "Hello OpenCode",
|
||||
}),
|
||||
...assistantTurnEvents({
|
||||
sessionId: "ses_parent_post_turn",
|
||||
text: "Response from OpenCode",
|
||||
}),
|
||||
];
|
||||
const events: AgentStreamEvent[] = [];
|
||||
const streamDrained = createTestDeferred<void>();
|
||||
parent.subscribe((event) => {
|
||||
events.push(event);
|
||||
if (event.type === "provider_subagent") {
|
||||
streamDrained.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await parent.startTurn("Hello OpenCode");
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(events).toContainEqual(expect.objectContaining({ type: "turn_completed" }));
|
||||
});
|
||||
|
||||
// Post-turn message update for the same user message ID that already completed
|
||||
openCode.emitEvent({
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: { id: "msg_user_1", sessionID: "ses_parent_post_turn", role: "user" },
|
||||
},
|
||||
});
|
||||
|
||||
openCode.emitEvent({
|
||||
type: "session.created",
|
||||
properties: {
|
||||
info: {
|
||||
id: "ses_child_drain_marker",
|
||||
parentID: "ses_parent_post_turn",
|
||||
title: "Stream drain marker",
|
||||
directory: "/workspace/repo",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await streamDrained.promise;
|
||||
|
||||
// Verify that no new turn_started was emitted after turn_completed
|
||||
const turnStartedCount = events.filter((e) => e.type === "turn_started").length;
|
||||
expect(turnStartedCount).toBe(1);
|
||||
} finally {
|
||||
await parent.close();
|
||||
}
|
||||
});
|
||||
test("does not adopt late output from an interrupted Paseo turn", async () => {
|
||||
const { parent, openCode } = await createParentSession("ses_parent_interrupted");
|
||||
openCode.sessionPromptAsyncEvents = [];
|
||||
|
||||
@@ -3662,6 +3662,9 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
// busy. That message is the earliest unambiguous boundary for a plugin-
|
||||
// initiated parent turn; session metadata and assistant echoes are not.
|
||||
if (event.type === "message.updated" && event.properties.info.role === "user") {
|
||||
if (this.emittedUserMessageIds.has(event.properties.info.id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!this.externallyDriven) {
|
||||
|
||||
Reference in New Issue
Block a user