From 930cb16495a81a3df4b781fd3557a8d34975732f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=A0=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD?= Date: Sat, 26 Sep 2026 01:56:43 +0300 Subject: [PATCH] fix: collect response text from event subscription instead of prompt return MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit session.prompt() non-blocking — возвращает объект-приём, а не текст. Текст ответа приходит через ctx.event.subscribe() как live-only events. Подход: - pendingResponses Map: sessionID -> {resolve, reject, timer} - Event subscription: парсит события, извлекает текст из assistant сообщений - Message handler: создаёт Promise, ждёт события (timeout 30s) - Парсим несколько структур событий на всякий случай --- index.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/index.ts b/index.ts index e797aad..2c9c659 100644 --- a/index.ts +++ b/index.ts @@ -93,17 +93,72 @@ async function runPlugin(ctx: any, options: any, dataDir: string) { // Track Matrix context per OpenCode session const sessionContextMap = new Map() - // V2 Event subscription for response streaming + // Pending prompt responses — maps sessionID -> { resolve, reject, timer } + const pendingResponses = new Map void + reject: (err: any) => void + timer: ReturnType + }>() + + // V2 Event subscription — collects assistant response text from events void (async () => { try { for await (const event of ctx.event.subscribe({ signal: eventController.signal })) { - const eventType = typeof event === "object" && event !== null && "type" in event ? (event as { type: string }).type : "" - if (eventType === "session.updated") { - const sessionId = (event as { sessionID?: string }).sessionID - if (!sessionId) continue - const update = (event as { update?: { type: string; content?: string; toolName?: string } }).update - if (!update) continue - log(`V2 event: session=${sessionId}, update.type=${update.type}`) + // Parse event — V2EventEncoded is a JSON string + let eventData: any = event + if (typeof event === "string") { + try { + eventData = JSON.parse(event) + } catch { /* not JSON, use raw */ } + } + + const eventType = eventData?.type || "" + const sessionId = eventData?.sessionID || eventData?.session?.id || "" + + // Check if this event contains assistant response text for a pending prompt + if (sessionId && pendingResponses.has(sessionId)) { + const pending = pendingResponses.get(sessionId)! + + // Extract text from various event structures + let responseText = "" + + // Try: event.messages[].content[].text + const messages = eventData?.messages || eventData?.data?.messages || eventData?.update?.messages + if (Array.isArray(messages)) { + for (const msg of messages) { + if (msg?.type === "assistant" || msg?.role === "assistant") { + const content = msg?.content || msg?.text + if (typeof content === "string") responseText += content + else if (Array.isArray(content)) { + for (const part of content) { + if (part?.type === "text" && part?.text) responseText += part.text + } + } + } + } + } + + // Try: event.update.content (simple text update) + if (!responseText && eventData?.update?.content) { + responseText = String(eventData.update.content) + } + + // Try: event.payload.text (synthetic message) + if (!responseText && eventData?.payload?.text) { + responseText = eventData.payload.text + } + + // Try: event.data.text (generate response) + if (!responseText && eventData?.data?.text) { + responseText = eventData.data.text + } + + if (responseText) { + log(`Collected response text (${responseText.length} chars) for session ${sessionId}`) + clearTimeout(pending.timer) + pendingResponses.delete(sessionId) + pending.resolve(responseText) + } } } } catch (err) { @@ -176,11 +231,22 @@ async function runPlugin(ctx: any, options: any, dataDir: string) { } try { - const result = await ctx.session.prompt({ + // Trigger prompt — returns admission receipt, not response text + await ctx.session.prompt({ sessionID: opencodeSessionId, text: query, }) - const responseText = result ? String(result) : "No response" + + // Wait for response text from event subscription + const responsePromise = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + pendingResponses.delete(opencodeSessionId) + reject(new Error("Timeout waiting for response (30s)")) + }, 30_000) + pendingResponses.set(opencodeSessionId, { resolve, reject, timer: timeout }) + }) + + const responseText = await responsePromise session.outputChars += responseText.length await matrix.sendReply(context, responseText) } catch (err: any) {