Harden terminal streaming and worktree setup flow

This commit is contained in:
Mohamed Boudra
2026-02-15 19:05:27 +07:00
parent 75dc802a3a
commit 0103b4da96
21 changed files with 1297 additions and 98 deletions
@@ -40,7 +40,18 @@ describe("TerminalStreamManager", () => {
expect(seen).toEqual(["hello"]);
expect(sendAck).toHaveBeenCalledTimes(1);
expect(sendAck).toHaveBeenCalledWith({ streamId: 7, offset: 9 });
});
test("acks buffered chunks even while no subscriber is attached", () => {
const sendAck = vi.fn();
const manager = new TerminalStreamManager({ sendAck });
manager.receiveChunk({
chunk: createChunk({ streamId: 2, offset: 0, data: "abc" }),
});
expect(sendAck).toHaveBeenCalledTimes(1);
expect(sendAck).toHaveBeenCalledWith({ streamId: 2, offset: 3 });
});
test("does not ack when every handler throws", () => {
@@ -79,8 +79,6 @@ export class TerminalStreamManager {
const streamHandlers = this.handlers.get(chunk.streamId);
if (!streamHandlers || streamHandlers.size === 0) {
this.bufferChunk({ chunk });
// Ack buffered chunks immediately to keep the daemon-side stream window
// moving even before a UI subscriber is attached.
this.maybeAckChunk({
streamId: chunk.streamId,
endOffset: chunk.endOffset,
@@ -753,6 +753,48 @@ describe("DaemonClient", () => {
unsubscribe();
});
test("handles terminal binary frames delivered as UTF-8 strings", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen();
await connectPromise;
const seen: string[] = [];
client.onTerminalStreamData(11, (chunk) => {
seen.push(new TextDecoder().decode(chunk.data));
});
const payload = new TextEncoder().encode("ls\r\n");
const frame = encodeBinaryMuxFrame({
channel: BinaryMuxChannel.Terminal,
messageType: TerminalBinaryMessageType.OutputUtf8,
streamId: 11,
offset: 0,
payload,
});
const frameAsString = new TextDecoder("utf-8", { fatal: true }).decode(frame);
mock.triggerMessage(frameAsString);
expect(seen).toEqual(["ls\r\n"]);
expect(mock.sent).toHaveLength(1);
const ackFrame = decodeBinaryMuxFrame(asUint8Array(mock.sent[0])!);
expect(ackFrame?.channel).toBe(BinaryMuxChannel.Terminal);
expect(ackFrame?.messageType).toBe(TerminalBinaryMessageType.Ack);
expect(ackFrame?.streamId).toBe(11);
expect(ackFrame?.offset).toBe(4);
});
test("acks buffered terminal chunks when handler is attached", async () => {
const logger = createMockLogger();
const mock = createMockTransport();