From dbe24b609db298f032e3fb7d8c1923f1b711902d Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 15 May 2026 15:22:57 +0700 Subject: [PATCH] Fix OpenCode turn timing --- .../agent/providers/opencode-agent.test.ts | 76 +++++++++++++++++++ .../server/agent/providers/opencode-agent.ts | 1 + 2 files changed, 77 insertions(+) diff --git a/packages/server/src/server/agent/providers/opencode-agent.test.ts b/packages/server/src/server/agent/providers/opencode-agent.test.ts index edfd2b355..3f717bc63 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.test.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.test.ts @@ -618,6 +618,82 @@ describe("OpenCode adapter context-window normalization", () => { }); describe("OpenCode adapter startTurn error handling", () => { + test("emits turn_started before live OpenCode timeline items", async () => { + const globalEvents = [ + { + payload: { + type: "server.connected", + properties: {}, + }, + }, + { + directory: "/tmp/test", + payload: { + type: "message.updated", + properties: { + info: { + id: "msg_assistant", + sessionID: "ses_unit_test", + role: "assistant", + }, + }, + }, + }, + { + directory: "/tmp/test", + payload: { + type: "message.part.delta", + properties: { + sessionID: "ses_unit_test", + messageID: "msg_assistant", + partID: "prt_text", + field: "text", + delta: "Hello from global", + }, + }, + }, + { + directory: "/tmp/test", + payload: { + type: "session.status", + properties: { + sessionID: "ses_unit_test", + status: { type: "idle" }, + }, + }, + }, + ]; + const fakeClient = { + global: { + event: vi.fn().mockResolvedValue({ stream: createAsyncIterable(globalEvents) }), + }, + session: { + promptAsync: vi.fn().mockResolvedValue({ data: {}, error: undefined }), + }, + } as never; + + const session = new __openCodeInternals.OpenCodeAgentSession( + { provider: "opencode", cwd: "/tmp/test" }, + fakeClient, + "ses_unit_test", + createTestLogger(), + "/tmp/opencode-storage", + ); + + const turn = await collectTurnEvents(streamSession(session, "hello")); + + expect(turn.events.map((event) => event.type)).toEqual([ + "turn_started", + "timeline", + "turn_completed", + ]); + expect(turn.events.map((event) => ("turnId" in event ? event.turnId : undefined))).toEqual([ + "opencode-turn-0", + "opencode-turn-0", + "opencode-turn-0", + ]); + }); + test("unwraps OpenCode global event payloads during a turn", async () => { const globalEvents = [ { diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index 890c1d725..1030856f5 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -2464,6 +2464,7 @@ class OpenCodeAgentSession implements AgentSession { // consumeEventStream already finished the turn with the subscription error. return { turnId }; } + this.notifySubscribers({ type: "turn_started", provider: "opencode" }, turnId); const slashCommand = await this.resolveSlashCommandInvocation(prompt); if (slashCommand) {