diff --git a/e2e/lib/event-parser.ts b/e2e/lib/event-parser.ts index 177a46b7..9db6066a 100644 --- a/e2e/lib/event-parser.ts +++ b/e2e/lib/event-parser.ts @@ -56,14 +56,20 @@ function tryParseEvent(text: string, events: ParsedEvent[]): void { events.push({ ...data, raw: text }) } } catch { - // Retry with non-ASCII characters escaped as Unicode sequences to work - // around JSC JSON.parse issues with multi-byte characters in large strings + // Fallback: write to temp file and read back to force fresh string allocation, + // working around potential JSC string representation issues with JSON.parse try { - const escaped = text.replace( - /[\u0080-\uffff]/g, - (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`, + const { writeFileSync, readFileSync, unlinkSync } = require("node:fs") + const { tmpdir } = require("node:os") + const { join } = require("node:path") + const tmpFile = join( + tmpdir(), + `event-parse-${Date.now()}-${Math.random().toString(36).slice(2)}.json`, ) - const data = JSON.parse(escaped) as RunEvent + writeFileSync(tmpFile, text) + const fresh = readFileSync(tmpFile, "utf-8") + unlinkSync(tmpFile) + const data = JSON.parse(fresh) as RunEvent if (data.type) { events.push({ ...data, raw: text }) } diff --git a/e2e/perstack-cli/providers.test.ts b/e2e/perstack-cli/providers.test.ts index d202e1fd..4610886b 100644 --- a/e2e/perstack-cli/providers.test.ts +++ b/e2e/perstack-cli/providers.test.ts @@ -69,26 +69,36 @@ describe.concurrent("LLM Providers", () => { process.stderr.write(`[DEBUG ${provider}] parse OK\n`) } catch (e) { process.stderr.write(`[DEBUG ${provider}] parse FAIL: ${(e as Error).message}\n`) - const escaped = line.replace( - /[\u0080-\uffff]/g, - (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`, + // Try file round-trip + const { writeFileSync, readFileSync, unlinkSync } = await import("node:fs") + const tmpFile = `/tmp/debug-parse-${Date.now()}.json` + writeFileSync(tmpFile, line) + const fresh = readFileSync(tmpFile, "utf-8") + unlinkSync(tmpFile) + process.stderr.write( + `[DEBUG ${provider}] freshLen=${fresh.length} match=${fresh === line}\n`, ) try { - JSON.parse(escaped) - process.stderr.write(`[DEBUG ${provider}] escaped parse OK!\n`) + JSON.parse(fresh) + process.stderr.write(`[DEBUG ${provider}] file roundtrip parse OK!\n`) } catch (e2) { process.stderr.write( - `[DEBUG ${provider}] escaped parse FAIL: ${(e2 as Error).message}\n`, + `[DEBUG ${provider}] file roundtrip parse FAIL: ${(e2 as Error).message}\n`, ) } - const buf = Buffer.from(line) - process.stderr.write( - `[DEBUG ${provider}] byteLen=${buf.length} charLen=${line.length}\n`, - ) - const first20hex = Array.from(buf.subarray(0, 20)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(" ") - process.stderr.write(`[DEBUG ${provider}] first20hex=${first20hex}\n`) + // Try TextEncoder/TextDecoder round-trip + const encoded = new TextEncoder().encode(line) + const decoded = new TextDecoder().decode(encoded) + try { + JSON.parse(decoded) + process.stderr.write(`[DEBUG ${provider}] TextEncoder roundtrip parse OK!\n`) + } catch (e3) { + process.stderr.write( + `[DEBUG ${provider}] TextEncoder roundtrip parse FAIL: ${(e3 as Error).message}\n`, + ) + } + // Try reading stdout file directly as Buffer + process.stderr.write(`[DEBUG ${provider}] rawStdoutLen=${result.stdout.length}\n`) } } else { process.stderr.write(`[DEBUG ${provider}] startRun NOT FOUND in any line\n`)