* feat: Migrated headless orchestrator to use execution_complete events,… - "src/headless.ts" - "src/headless-ui.ts" - "src/tests/headless-v2-migration.test.ts" GSD-Task: S06/T02 * test: Wired pi-coding-agent to re-export JSONL utils from @gsd/rpc-clie… - "packages/pi-coding-agent/src/modes/rpc/jsonl.ts" - "packages/pi-coding-agent/package.json" - "packages/rpc-client/src/index.ts" - "packages/rpc-client/src/jsonl.ts" - "packages/rpc-client/src/rpc-client.ts" - "packages/rpc-client/src/rpc-types.ts" - "packages/rpc-client/src/rpc-client.test.ts" - "packages/rpc-client/package.json" GSD-Task: S06/T03 * feat: Wire --resume flag to resolve session IDs via prefix matching and… - "src/headless.ts" - "dist/headless.js" GSD-Task: S01/T01 * test: Added 5 e2e integration tests proving headless JSON batch, SIGINT… - "src/tests/integration/e2e-headless.test.ts" GSD-Task: S01/T02 * test: Updated @gsd/rpc-client and @gsd/mcp-server to 2.52.0 with publis… - "packages/rpc-client/package.json" - "packages/mcp-server/package.json" - "packages/rpc-client/.npmignore" - "packages/mcp-server/.npmignore" GSD-Task: S02/T01 * chore: auto-commit after complete-milestone GSD-Unit: M002-gzq23a * fix: revert jsonl.ts to inline implementation — @gsd-build/rpc-client not available at source-level test time in CI The re-export from @gsd-build/rpc-client fails in CI because tests run against TypeScript source (--experimental-strip-types) before any build step. The npm dependency resolves to node_modules/ which requires dist/ to exist. Reverting to the original inline implementation eliminates the cross-package dependency for source-level imports.
64 lines
1.6 KiB
TypeScript
64 lines
1.6 KiB
TypeScript
import type { Readable } from "node:stream";
|
|
import { StringDecoder } from "node:string_decoder";
|
|
|
|
/**
|
|
* Serialize a single strict JSONL record.
|
|
*
|
|
* Framing is LF-only. Payload strings may contain other Unicode separators such as
|
|
* U+2028 and U+2029. Clients must split records on `\n` only.
|
|
*/
|
|
export function serializeJsonLine(value: unknown): string {
|
|
return `${JSON.stringify(value)}\n`;
|
|
}
|
|
|
|
/**
|
|
* Attach an LF-only JSONL reader to a stream.
|
|
*
|
|
* This intentionally does not use Node readline. Readline splits on additional
|
|
* Unicode separators that are valid inside JSON strings and therefore does not
|
|
* implement strict JSONL framing.
|
|
*/
|
|
export function attachJsonlLineReader(stream: Readable, onLine: (line: string) => void): () => void {
|
|
const decoder = new StringDecoder("utf8");
|
|
let buffer = "";
|
|
|
|
const emitLine = (line: string) => {
|
|
onLine(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
};
|
|
|
|
const onData = (chunk: string | Buffer) => {
|
|
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
|
|
while (true) {
|
|
const newlineIndex = buffer.indexOf("\n");
|
|
if (newlineIndex === -1) {
|
|
return;
|
|
}
|
|
|
|
emitLine(buffer.slice(0, newlineIndex));
|
|
buffer = buffer.slice(newlineIndex + 1);
|
|
}
|
|
};
|
|
|
|
const onEnd = () => {
|
|
buffer += decoder.end();
|
|
if (buffer.length > 0) {
|
|
emitLine(buffer);
|
|
buffer = "";
|
|
}
|
|
};
|
|
|
|
const onError = (_err: Error) => {
|
|
// Stream errors are non-fatal for JSONL reading
|
|
};
|
|
|
|
stream.on("data", onData);
|
|
stream.on("end", onEnd);
|
|
stream.on("error", onError);
|
|
|
|
return () => {
|
|
stream.off("data", onData);
|
|
stream.off("end", onEnd);
|
|
stream.off("error", onError);
|
|
};
|
|
}
|