From e331694df6597927133b3387e4bcc7285fdaa09f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 09:46:52 +0100 Subject: [PATCH] fix(gateway): unstick claude cli live e2e --- .../gateway-cli-backend.live-helpers.ts | 2 - src/gateway/gateway-cli-backend.live.test.ts | 1 + .../test-helpers.agent-results.test.ts | 37 +++++++++++++ src/gateway/test-helpers.agent-results.ts | 53 ++++++++++++++++++- 4 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/gateway/test-helpers.agent-results.test.ts diff --git a/src/gateway/gateway-cli-backend.live-helpers.ts b/src/gateway/gateway-cli-backend.live-helpers.ts index 638128f7c1e..563a647010e 100644 --- a/src/gateway/gateway-cli-backend.live-helpers.ts +++ b/src/gateway/gateway-cli-backend.live-helpers.ts @@ -316,8 +316,6 @@ export function applyCliBackendLiveEnv(preservedEnv: ReadonlySet): void process.env.OPENCLAW_SKIP_CRON = "1"; process.env.OPENCLAW_SKIP_CANVAS_HOST = "1"; process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER = "1"; - process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = - process.env.OPENCLAW_BUNDLED_PLUGINS_DIR ?? "openclaw-live-test-no-bundled-extensions"; process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "1"; if (!preservedEnv.has("ANTHROPIC_API_KEY")) { delete process.env.ANTHROPIC_API_KEY; diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index daefe479dd3..20fe0afac0b 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -148,6 +148,7 @@ describeLive("gateway live (cli backend)", () => { const nextCfg = { ...cfg, gateway: { + mode: "local", ...cfg.gateway, port, auth: { mode: "token", token }, diff --git a/src/gateway/test-helpers.agent-results.test.ts b/src/gateway/test-helpers.agent-results.test.ts new file mode 100644 index 00000000000..92225cc7fb1 --- /dev/null +++ b/src/gateway/test-helpers.agent-results.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { extractPayloadText } from "./test-helpers.agent-results.js"; + +describe("extractPayloadText", () => { + it("returns plain payload text unchanged", () => { + expect( + extractPayloadText({ + payloads: [{ text: "hello world" }], + }), + ).toBe("hello world"); + }); + + it("extracts final text from Claude CLI stream-json payloads", () => { + const streamJson = [ + JSON.stringify({ + type: "system", + subtype: "init", + }), + JSON.stringify({ + type: "assistant", + message: { + content: [{ type: "text", text: "CLI backend OK ABC123." }], + }, + }), + JSON.stringify({ + type: "result", + result: "CLI backend OK ABC123.", + }), + ].join("\n"); + + expect( + extractPayloadText({ + payloads: [{ text: streamJson }], + }), + ).toBe("CLI backend OK ABC123."); + }); +}); diff --git a/src/gateway/test-helpers.agent-results.ts b/src/gateway/test-helpers.agent-results.ts index f4ebd2611a6..94188594aff 100644 --- a/src/gateway/test-helpers.agent-results.ts +++ b/src/gateway/test-helpers.agent-results.ts @@ -4,13 +4,64 @@ type AgentDeltaEvent = { data: { delta: string }; }; +function extractCliStreamJsonText(text: string): string | null { + const lines = text + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0); + if (lines.length === 0) { + return null; + } + + let assistantText: string | null = null; + let resultText: string | null = null; + + for (const line of lines) { + let parsed: unknown; + try { + parsed = JSON.parse(line); + } catch { + continue; + } + if (!parsed || typeof parsed !== "object") { + continue; + } + const record = parsed as Record; + if (record.type === "assistant") { + const message = + record.message && typeof record.message === "object" + ? (record.message as Record) + : null; + const content = Array.isArray(message?.content) ? message.content : []; + const textParts = content + .map((entry) => + entry && typeof entry === "object" ? (entry as Record).text : undefined, + ) + .filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0); + if (textParts.length > 0) { + assistantText = textParts.join("\n").trim(); + } + continue; + } + if (record.type === "result" && typeof record.result === "string" && record.result.trim()) { + resultText = record.result.trim(); + } + } + + return resultText ?? assistantText; +} + export function extractPayloadText(result: unknown): string { const record = result as Record; const payloads = Array.isArray(record.payloads) ? record.payloads : []; const texts = payloads .map((p) => (p && typeof p === "object" ? (p as Record).text : undefined)) .filter((t): t is string => typeof t === "string" && t.trim().length > 0); - return texts.join("\n").trim(); + const joined = texts.join("\n").trim(); + if (!joined) { + return joined; + } + return extractCliStreamJsonText(joined) ?? joined; } export function buildAssistantDeltaResult(params: {