From fdfa9f33f09353750772be771443b1f159f1dc3c Mon Sep 17 00:00:00 2001 From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:58:11 +0200 Subject: [PATCH] fix(v2): map failed and interrupted execution events to session.idle Auto-capture and profile learning only run on session.idle; without aliasing session.execution.failed/interrupted those sessions were never captured. Closes #383. --- src/v2/legacy-client.ts | 18 ++++++++++++------ tests/v2-legacy-client.test.ts | 19 +++++++++---------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/v2/legacy-client.ts b/src/v2/legacy-client.ts index a555ab4a..d73958df 100644 --- a/src/v2/legacy-client.ts +++ b/src/v2/legacy-client.ts @@ -317,16 +317,22 @@ export function legacyToolResult(value: unknown): { content: string; metadata?: return { content: JSON.stringify(value ?? null) }; } +// opencode v2 no longer publishes `session.idle`, which is what +// opencode-mem builds auto-capture and profile learning on. The +// `/api/event` stream emits `session.execution.{succeeded,failed,interrupted}` +// with `data.sessionID`; forwarding them under the legacy name is enough. +const LEGACY_EVENT_ALIASES: Record = { + "session.compaction.ended": "session.compacted", + "session.execution.succeeded": "session.idle", + "session.execution.failed": "session.idle", + "session.execution.interrupted": "session.idle", +}; + export function toLegacyEvent(raw: any): { type: string; properties: any } { const envelope = raw?.payload ?? raw; const source = envelope?.type === "sync" && envelope.syncEvent ? envelope.syncEvent : envelope; const rawType = typeof source?.type === "string" ? source.type.replace(/\.1$/, "") : source?.type; - const type = - rawType === "session.compaction.ended" - ? "session.compacted" - : rawType === "session.execution.succeeded" - ? "session.idle" - : rawType; + const type = typeof rawType === "string" ? (LEGACY_EVENT_ALIASES[rawType] ?? rawType) : rawType; const data = source?.data ?? {}; if (source && typeof source === "object" && "properties" in source) { return { type, properties: source.properties }; diff --git a/tests/v2-legacy-client.test.ts b/tests/v2-legacy-client.test.ts index f7ad3eef..22e061f6 100644 --- a/tests/v2-legacy-client.test.ts +++ b/tests/v2-legacy-client.test.ts @@ -384,16 +384,15 @@ describe("OpenCode v2 legacy client bridge", () => { }); }); - it("maps V2 execution.succeeded to session.idle for auto-capture", () => { - expect( - toLegacyEvent({ - type: "session.execution.succeeded", - data: { sessionID: "ses-1" }, - }) - ).toEqual({ - type: "session.idle", - properties: { sessionID: "ses-1" }, - }); + it("aliases v2 execution events to session.idle", () => { + const t = (raw: string) => toLegacyEvent({ type: raw, data: { sessionID: "s1" } }).type; + + expect(t("session.execution.succeeded")).toBe("session.idle"); + expect(t("session.execution.failed")).toBe("session.idle"); + expect(t("session.execution.interrupted")).toBe("session.idle"); + expect(t("session.compaction.ended")).toBe("session.compacted"); + expect(t("message.updated")).toBe("message.updated"); + expect(t("session.execution.succeeded.1")).toBe("session.idle"); }); it("filters the global event stream by direct or session location", async () => {