From f092ec05632a4f8a2a4e5cbd6e2faa8301da4152 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sat, 3 Jan 2026 13:28:33 +0800 Subject: [PATCH 1/3] feat(server): add session parameter to /event endpoint Add optional `session` query parameter to the /event SSE endpoint that filters events by session ID. This allows clients to subscribe only to events for a specific session instead of receiving all events. - Add session parameter to OpenAPI spec with description - Implement filtering logic that checks sessionID in various event property locations (properties.sessionID, properties.info.sessionID, properties.part.sessionID) - Events without sessionID are still passed through when filtering Fixes #6686 --- packages/opencode/src/server/server.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index bc727b02857..2b55de58c26 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -2714,7 +2714,8 @@ export namespace Server { "/event", describeRoute({ summary: "Subscribe to events", - description: "Get events", + description: + "Subscribe to server-sent events. Optionally filter events by session ID to only receive events for a specific session.", operationId: "event.subscribe", responses: { 200: { @@ -2727,8 +2728,15 @@ export namespace Server { }, }, }), + validator( + "query", + z.object({ + session: z.string().optional().meta({ description: "Filter events by session ID" }), + }), + ), async (c) => { - log.info("event connected") + const sessionFilter = c.req.valid("query").session + log.info("event connected", { session: sessionFilter }) return streamSSE(c, async (stream) => { stream.writeSSE({ data: JSON.stringify({ @@ -2737,6 +2745,14 @@ export namespace Server { }), }) const unsub = Bus.subscribeAll(async (event) => { + // Filter by session if specified + if (sessionFilter) { + const eventSessionID = + event.properties?.sessionID || event.properties?.info?.sessionID || event.properties?.part?.sessionID + if (eventSessionID && eventSessionID !== sessionFilter) { + return + } + } await stream.writeSSE({ data: JSON.stringify(event), }) From 9195392e499417fe653f7b6f4c5d80fde0f8318f Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sat, 3 Jan 2026 13:42:48 +0800 Subject: [PATCH 2/3] chore: regenerate SDK after adding session parameter to /event --- packages/sdk/js/src/v2/gen/sdk.gen.ts | 15 +++++++++++++-- packages/sdk/js/src/v2/gen/types.gen.ts | 4 ++++ packages/sdk/openapi.json | 10 +++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 702af632457..cc6a11cce55 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -2810,15 +2810,26 @@ export class Event extends HeyApiClient { /** * Subscribe to events * - * Get events + * Subscribe to server-sent events. Optionally filter events by session ID to only receive events for a specific session. */ public subscribe( parameters?: { directory?: string + session?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "session" }, + ], + }, + ], + ) return (options?.client ?? this.client).sse.get({ url: "/event", ...options, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index f083dc85d6e..f5444159f73 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4513,6 +4513,10 @@ export type EventSubscribeData = { path?: never query?: { directory?: string + /** + * Filter events by session ID + */ + session?: string } url: "/event" } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0ace5a84e47..b678a5e3327 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5285,10 +5285,18 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "session", + "schema": { + "type": "string" + }, + "description": "Filter events by session ID" } ], "summary": "Subscribe to events", - "description": "Get events", + "description": "Subscribe to server-sent events. Optionally filter events by session ID to only receive events for a specific session.", "responses": { "200": { "description": "Event stream", From 75f23d6273453c3895c7ad5aa694d7cea55f69b8 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sat, 3 Jan 2026 13:54:10 +0800 Subject: [PATCH 3/3] fix: filter out non-session events to prevent cross-session leakage When session filter is active, only allow: 1. Events with matching sessionID 2. server.* events (connected, heartbeat) which are connection-level All other events without sessionID (ide.*, file.*, lsp.*, mcp.*, etc.) are now filtered out to maintain strict session isolation. --- packages/opencode/src/server/server.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 2b55de58c26..a8d01fda62e 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -2749,6 +2749,11 @@ export namespace Server { if (sessionFilter) { const eventSessionID = event.properties?.sessionID || event.properties?.info?.sessionID || event.properties?.part?.sessionID + // Only allow server.* events (connected, heartbeat, etc.) to pass through without sessionID + // All other events without sessionID are filtered out to prevent cross-session leakage + if (!eventSessionID && !event.type.startsWith("server.")) { + return + } if (eventSessionID && eventSessionID !== sessionFilter) { return }