const assert = require("node:assert/strict"); const test = require("node:test"); const { createTerminalWorkerRuntime } = require("./runtime.cjs"); class FakePort { constructor() { this.messages = []; this.closed = false; this.started = false; this.listeners = new Map(); } postMessage(message) { this.messages.push(message); } start() { this.started = true; } close() { this.closed = true; } on(channel, callback) { this.listeners.set(channel, callback); } emitMessage(message) { const callback = this.listeners.get("message"); if (callback) { callback({ data: message }); return; } this.onmessage?.({ data: message }); } } function createParentPort() { const messages = []; const listeners = new Map(); return { messages, on(channel, cb) { listeners.set(channel, cb); }, postMessage(message) { messages.push(message); }, emitMessage(message) { listeners.get("message")?.(message); }, }; } test("runtime invokes registered request handlers and posts responses", async () => { const parentPort = createParentPort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:test", async (_event, payload) => ({ ok: true, payload })); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:test", payload: { value: 1 }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages, [ { kind: "response", requestId: "req-1", result: { ok: true, payload: { value: 1 } }, }, ]); }); test("runtime serializes overlapping same-id starts and closes the superseded session first", async () => { const parentPort = createParentPort(); const order = []; let active = null; let releaseFirst; const firstGate = new Promise((resolve) => { releaseFirst = resolve; }); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:reconnect", async (_event, payload) => { order.push(`start:${payload.attempt}`); if (payload.attempt === "first") await firstGate; active = payload.attempt; order.push(`publish:${payload.attempt}`); return { sessionId: payload.sessionId }; }); ipcMain.handle("netcatty:close:await", (_event, payload) => { order.push(`close:${active}:${payload.sessionId}`); active = null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt: "first" }, webContentsId: 7, }); parentPort.emitMessage({ kind: "request", requestId: "req-2", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt: "second" }, webContentsId: 8, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(order, ["start:first"]); releaseFirst(); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(order, [ "start:first", "publish:first", "close:first:session-1", "start:second", "publish:second", ]); assert.equal(active, "second"); assert.deepEqual( parentPort.messages.filter((message) => message.kind === "response").map((message) => message.requestId), ["req-1", "req-2"], ); }); test("runtime closes a completed same-id start before a later request begins", async () => { const parentPort = createParentPort(); const order = []; let active = null; const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:reconnect", async (_event, payload) => { order.push(`start:${payload.attempt}`); active = payload.attempt; order.push(`publish:${payload.attempt}`); return { sessionId: payload.sessionId }; }); ipcMain.handle("netcatty:close:await", (_event, payload) => { order.push(`close:${active}:${payload.sessionId}`); active = null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt: "first" }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); parentPort.emitMessage({ kind: "request", requestId: "req-2", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt: "second" }, webContentsId: 8, }); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(order, [ "start:first", "publish:first", "close:first:session-1", "start:second", "publish:second", ]); assert.equal(active, "second"); assert.deepEqual( parentPort.messages.filter((message) => message.kind === "session-superseding"), [{ kind: "session-superseding", sessionId: "session-1", sessionGeneration: 0, replacementRequestId: "req-2", }], ); }); test("runtime records a generated session id so a later same-id start closes it first", async () => { const parentPort = createParentPort(); const order = []; let active = null; const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:start", async (_event, payload) => { order.push(`start:${payload.attempt}`); active = payload.attempt; return { sessionId: "generated-1" }; }); ipcMain.handle("netcatty:close:await", (_event, payload) => { order.push(`close:${active}:${payload.sessionId}`); active = null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:local:start", payload: { attempt: "first" }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); parentPort.emitMessage({ kind: "request", requestId: "req-2", channel: "netcatty:local:start", payload: { sessionId: "generated-1", attempt: "second" }, webContentsId: 8, }); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(order, [ "start:first", "close:first:generated-1", "start:second", ]); assert.equal(active, "second"); assert.deepEqual( parentPort.messages .filter((message) => message.kind === "response") .map((message) => message.sessionGeneration), [0, 1], ); }); test("runtime close waits for a pending start and removes the session it publishes", async () => { const parentPort = createParentPort(); const order = []; let active = null; let releaseStart; const startGate = new Promise((resolve) => { releaseStart = resolve; }); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:reconnect", async (_event, payload) => { order.push("start"); await startGate; active = payload.sessionId; order.push("publish"); return { sessionId: payload.sessionId }; }); ipcMain.on("netcatty:close", (_event, payload) => { order.push(`close:${payload.sessionId}`); active = null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1" }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(order, ["start"]); parentPort.emitMessage({ kind: "send", channel: "netcatty:close", payload: { sessionId: "session-1" }, webContentsId: 7, }); releaseStart(); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(order, ["start", "publish", "close:session-1"]); assert.equal(active, null); }); test("runtime close cancels every same-id start that was queued before it", async () => { const parentPort = createParentPort(); const starts = []; let active = null; let releaseFirst; const firstGate = new Promise((resolve) => { releaseFirst = resolve; }); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:reconnect", async (_event, payload) => { starts.push(payload.attempt); if (payload.attempt === "first") await firstGate; active = payload.attempt; return { sessionId: payload.sessionId }; }); ipcMain.on("netcatty:close", () => { active = null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt: "first" }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); parentPort.emitMessage({ kind: "request", requestId: "req-2", channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt: "second" }, webContentsId: 7, }); parentPort.emitMessage({ kind: "send", channel: "netcatty:close", payload: { sessionId: "session-1" }, webContentsId: 7, }); releaseFirst(); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(starts, ["first"]); assert.equal(active, null); assert.equal(parentPort.messages.some((message) => ( message.requestId === "req-2" && /cancelled by close/u.test(message.error) )), true); }); test("runtime tags a failing start exit so its queued replacement can distinguish it", async () => { const parentPort = createParentPort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:reconnect", async (event, payload) => { if (payload.attempt === "first") { event.sender.send("netcatty:exit", { sessionId: payload.sessionId, reason: "failed", }); throw new Error("first failed"); } return { sessionId: payload.sessionId }; }); ipcMain.handle("netcatty:close:await", () => {}); }, }); runtime.start(); for (const [requestId, attempt] of [["req-1", "first"], ["req-2", "second"]]) { parentPort.emitMessage({ kind: "request", requestId, channel: "netcatty:local:reconnect", payload: { sessionId: "session-1", attempt }, webContentsId: 7, }); } await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); const exit = parentPort.messages.find((message) => message.kind === "renderer-event"); assert.equal(exit.originRequestId, "req-1"); assert.equal(parentPort.messages.some((message) => ( message.requestId === "req-2" && message.result?.sessionId === "session-1" )), true); }); test("runtime prunes sessions that naturally exit before their start response completes", async () => { const parentPort = createParentPort(); const runtime = createTerminalWorkerRuntime({ parentPort, sessionLifecycleTombstoneTtlMs: 60_000, maxSessionLifecycleTombstones: 1, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:start", async (event, payload) => { const generation = event.sender.claimSessionGeneration(payload.sessionId); event.sender.send("netcatty:exit", { sessionId: payload.sessionId, reason: "exited-during-start", _terminalSessionGeneration: generation, }); return { sessionId: payload.sessionId }; }); }, }); runtime.start(); for (let index = 0; index < 3; index += 1) { parentPort.emitMessage({ kind: "request", requestId: `start-exit-${index}`, channel: "netcatty:local:start", payload: { sessionId: `start-exit-${index}` }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); } assert.deepEqual(runtime._getSessionLifecycleStateCountsForTests(), { outputGenerations: 1, closeEpochs: 0, }); }); test("runtime routes interceptor ports to the worker-owned data pipeline", () => { const parentPort = createParentPort(); const attached = []; const detached = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { attach(message, port) { attached.push({ message, port }); }, detach(sessionId, direction) { detached.push({ sessionId, direction }); }, }, registerBridges() {}, }); runtime.start(); const port = new FakePort(); parentPort.emitMessage({ data: { kind: "terminal-interceptor-port", sessionId: "session-1", direction: "output" }, ports: [port], }); assert.equal(attached[0].port, port); parentPort.emitMessage({ kind: "terminal-interceptor-detach", sessionId: "session-1", direction: "output" }); assert.deepEqual(detached, [{ sessionId: "session-1", direction: "output" }]); }); test("fresh sender lookups preserve an explicitly bound backend session generation", () => { const parentPort = createParentPort(); const detached = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { detach(sessionId, direction, reason) { detached.push({ sessionId, direction, reason }); }, }, registerBridges() {}, }); runtime.start(); const oldGeneration = runtime.createSender(7).claimSessionGeneration("session-1"); runtime.createSender(7).send("netcatty:exit", { sessionId: "session-1", reason: "error", _terminalSessionGeneration: oldGeneration, }); const newGeneration = runtime.createSender(8).claimSessionGeneration("session-1"); runtime.createSender(7).send("netcatty:exit", { sessionId: "session-1", reason: "close", _terminalSessionGeneration: oldGeneration, }); runtime.createSender(8).send("netcatty:exit", { sessionId: "session-1", reason: "exited", _terminalSessionGeneration: newGeneration, }); assert.deepEqual( parentPort.messages.map((message) => message.sessionGeneration), [0, 0, 1], ); assert.equal(parentPort.messages.some((message) => ( Object.hasOwn(message.payload, "_terminalSessionGeneration") )), false); assert.deepEqual(detached, [ { sessionId: "session-1", direction: undefined, reason: "session-closed" }, { sessionId: "session-1", direction: undefined, reason: "session-closed" }, ]); }); test("stale backend output is rejected before taps, observation, or interception", async () => { const parentPort = createParentPort(); const observed = []; const intercepted = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 3; }, observeOutput(sessionId, data) { observed.push({ sessionId, data }); return false; }, async interceptOutput(sessionId, data) { intercepted.push({ sessionId, data }); return data; }, detach() {}, }, registerBridges() {}, }); runtime.start(); const oldSender = runtime.createSender(7); const oldGeneration = oldSender.claimSessionGeneration("session-1"); oldSender.send("netcatty:exit", { sessionId: "session-1", reason: "error", _terminalSessionGeneration: oldGeneration, }); const newSender = runtime.createSender(8); const newGeneration = newSender.claimSessionGeneration("session-1"); parentPort.messages.length = 0; runtime.createSender(7).send("netcatty:data", { sessionId: "session-1", data: "old-secret", _terminalSessionGeneration: oldGeneration, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages, []); assert.deepEqual(observed, []); assert.deepEqual(intercepted, []); runtime.createSender(8).send("netcatty:data", { sessionId: "session-1", data: "new-output", _terminalSessionGeneration: newGeneration, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(observed, [{ sessionId: "session-1", data: "new-output" }]); assert.deepEqual(intercepted, [{ sessionId: "session-1", data: "new-output" }]); assert.deepEqual(parentPort.messages.map((message) => message.kind), ["output-tap", "output"]); }); test("runtime bounds closed lifecycle generations and safely reuses a recent session id", async () => { const parentPort = createParentPort(); let now = 0; const runtime = createTerminalWorkerRuntime({ parentPort, now: () => now, sessionLifecycleTombstoneTtlMs: 100, maxSessionLifecycleTombstones: 8, registerBridges(ipcMain) { ipcMain.on("netcatty:close", () => {}); }, }); runtime.start(); const recentSenders = new Map(); for (let index = 0; index < 24; index += 1) { const sessionId = `closed-${index}`; const sender = runtime.createSender(7); recentSenders.set(sessionId, { sender, generation: sender.claimSessionGeneration(sessionId), }); parentPort.emitMessage({ kind: "send", channel: "netcatty:close", payload: { sessionId }, webContentsId: 7, }); } await new Promise((resolve) => setImmediate(resolve)); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(runtime._getSessionLifecycleStateCountsForTests(), { outputGenerations: 8, closeEpochs: 8, }); const sessionId = "closed-23"; const recent = recentSenders.get(sessionId); parentPort.messages.length = 0; now = 99; recent.sender.send("netcatty:data", { sessionId, data: "stale", _terminalSessionGeneration: recent.generation, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages, [], "old sender stays fenced during the safety window"); const replacement = runtime.createSender(8); const replacementGeneration = replacement.claimSessionGeneration(sessionId); replacement.send("netcatty:data", { sessionId, data: "fresh", _terminalSessionGeneration: replacementGeneration, }); await new Promise((resolve) => setImmediate(resolve)); assert.ok(replacementGeneration > recent.generation); assert.deepEqual(parentPort.messages.map((message) => message.kind), ["output-tap", "output"]); }); test("runtime bounds natural-exit lifecycle generations and allows same-id restart", async () => { const parentPort = createParentPort(); let now = 0; let starts = 0; const senders = new Map(); const generations = new Map(); const runtime = createTerminalWorkerRuntime({ parentPort, now: () => now, sessionLifecycleTombstoneTtlMs: 100, maxSessionLifecycleTombstones: 8, registerBridges(ipcMain) { ipcMain.handle("netcatty:local:start", async (event, payload) => { starts += 1; senders.set(payload.sessionId, event.sender); generations.set(payload.sessionId, event.sender.claimSessionGeneration(payload.sessionId)); return { sessionId: payload.sessionId }; }); }, }); runtime.start(); for (let index = 0; index < 24; index += 1) { const sessionId = `natural-exit-${index}`; parentPort.emitMessage({ kind: "request", requestId: `start-${index}`, channel: "netcatty:local:start", payload: { sessionId }, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); senders.get(sessionId).send("netcatty:exit", { sessionId, reason: "exited", _terminalSessionGeneration: generations.get(sessionId), }); } await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(runtime._getSessionLifecycleStateCountsForTests(), { outputGenerations: 8, closeEpochs: 0, }); const sessionId = "natural-exit-23"; const exitedSender = senders.get(sessionId); const exitedGeneration = generations.get(sessionId); parentPort.messages.length = 0; now = 99; exitedSender.send("netcatty:data", { sessionId, data: "late-output", _terminalSessionGeneration: exitedGeneration, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages, [], "natural exit keeps late output fenced"); parentPort.emitMessage({ kind: "request", requestId: "restart-natural-exit", channel: "netcatty:local:start", payload: { sessionId }, webContentsId: 8, }); await new Promise((resolve) => setImmediate(resolve)); const replacementSender = senders.get(sessionId); const replacementGeneration = generations.get(sessionId); assert.ok(replacementGeneration > exitedGeneration); assert.equal(starts, 25); parentPort.messages.length = 0; replacementSender.send("netcatty:data", { sessionId, data: "fresh-output", _terminalSessionGeneration: replacementGeneration, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages.map((message) => message.kind), ["output-tap", "output"]); }); test("runtime invokes fire-and-forget listeners", () => { const parentPort = createParentPort(); const calls = []; const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.on("netcatty:write", (_event, payload) => calls.push(payload)); }, }); runtime.start(); parentPort.emitMessage({ kind: "send", channel: "netcatty:write", payload: { sessionId: "s1", data: "x" }, webContentsId: 7, }); assert.deepEqual(calls, [{ sessionId: "s1", data: "x" }]); }); test("runtime sends output drain markers through the session output port", () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges() {} }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1" }, ports: [outputPort], }); parentPort.emitMessage({ kind: "output-drain", sessionId: "s1", requestId: "drain-1" }); assert.deepEqual(outputPort.messages, [ { kind: "drain", requestId: "drain-1", sessionId: "s1" }, ]); }); test("runtime closes an urgent input port when its renderer is destroyed", () => { const parentPort = createParentPort(); const urgentPort = new FakePort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges() {} }); runtime.start(); parentPort.emitMessage({ data: { kind: "urgent-input-port", webContentsId: 7 }, ports: [urgentPort], }); parentPort.emitMessage({ kind: "close-urgent-input-port", webContentsId: 7 }); assert.equal(urgentPort.closed, true); }); test("runtime routes urgent input port interrupts to the interrupt listener", () => { const parentPort = createParentPort(); const urgentPort = new FakePort(); const calls = []; const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.on("netcatty:interrupt", (event, payload) => { calls.push({ senderId: event.sender.id, payload }); }); }, }); runtime.start(); parentPort.emitMessage({ data: { kind: "urgent-input-port", webContentsId: 7, }, ports: [urgentPort], }); urgentPort.emitMessage({ kind: "interrupt", sessionId: "s1", trace: { traceId: "trace-1" }, }); assert.equal(urgentPort.started, true); assert.deepEqual(calls, [ { senderId: 7, payload: { sessionId: "s1", trace: { traceId: "trace-1" }, urgentInputPort: true, }, }, ]); }); test("runtime clears host-sensitive input state before dispatching an interrupt", () => { const parentPort = createParentPort(); const order = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { clearSensitiveInput(sessionId) { order.push(`clear:${sessionId}`); }, }, registerBridges(ipcMain) { ipcMain.on("netcatty:interrupt", (_event, payload) => order.push(`write:${payload.sessionId}`)); }, }); runtime.start(); parentPort.emitMessage({ kind: "send", channel: "netcatty:interrupt", payload: { sessionId: "s1" }, webContentsId: 7, }); assert.deepEqual(order, ["clear:s1", "write:s1"]); }); test("runtime routes terminal data over output messages", async () => { const parentPort = createParentPort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:test", (event) => { event.sender.send("netcatty:data", { sessionId: "s1", data: "hello" }); return { done: true }; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:test", payload: {}, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages[0], { kind: "output-tap", sessionId: "s1", data: "hello", sessionGeneration: 0, }); assert.deepEqual(parentPort.messages[1], { kind: "output", sessionId: "s1", data: "hello", tapped: true, sessionGeneration: 0, }); }); test("runtime keeps the no-interceptor output path synchronous and allocation-free", async () => { const parentPort = createParentPort(); let interceptCalls = 0; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 0; }, async interceptOutput() { interceptCalls += 1; return "changed"; }, }, registerBridges(ipcMain) { ipcMain.handle("netcatty:test", (event) => { event.sender.send("netcatty:data", { sessionId: "s1", data: "hello" }); assert.equal(parentPort.messages[1]?.data, "hello"); return null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-no-plugin", channel: "netcatty:test", payload: {}, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.equal(interceptCalls, 0); }); test("runtime sends transformed output to the renderer while host taps retain original data", async () => { const parentPort = createParentPort(); const observed = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 3; }, observeOutput(sessionId, data) { observed.push({ sessionId, data }); return true; }, async interceptOutput(_sessionId, data) { return String(data).toUpperCase(); }, }, registerBridges(ipcMain) { ipcMain.handle("netcatty:test", (event) => { event.sender.send("netcatty:data", { sessionId: "s1", data: "hello" }); return null; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-plugin", channel: "netcatty:test", payload: {}, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages[0], { kind: "output-tap", sessionId: "s1", data: "hello", sessionGeneration: 0, }); assert.deepEqual(parentPort.messages[1], { kind: "output", sessionId: "s1", data: "HELLO", tapped: true, sessionGeneration: 0, meta: { pluginPipelineIngressBytes: 5, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: true, }, }); assert.deepEqual(observed, [{ sessionId: "s1", data: "hello" }]); }); test("runtime classifies original prompts when only an output interceptor is active", async () => { const parentPort = createParentPort(); const observed = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 2; }, observeOutput(sessionId, data) { observed.push({ sessionId, data }); return true; }, async interceptOutput() { return "masked> "; }, }, registerBridges() {}, }); runtime.start(); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "Password: " }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(observed, [{ sessionId: "s1", data: "Password: " }]); assert.deepEqual(parentPort.messages.at(-1), { kind: "output", sessionId: "s1", data: "masked> ", tapped: true, sessionGeneration: 0, meta: { pluginPipelineIngressBytes: 10, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: true, }, }); }); test("runtime publishes an explicit sensitive-state clear from original output", async () => { const parentPort = createParentPort(); const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 3; }, observeOutput() { return false; }, async interceptOutput() { return "renamed prompt> "; }, }, registerBridges() {}, }); runtime.start(); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "\r\nAccess denied\r\n$ ", }); await new Promise((resolve) => setImmediate(resolve)); assert.equal( parentPort.messages.at(-1).meta.pluginPipelineSensitiveInput, false, ); }); test("runtime delivers pending intercepted output before closing the session", async () => { const parentPort = createParentPort(); let releaseOutput; const detached = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 2; }, observeOutput() { return false; }, interceptOutput() { return new Promise((resolve) => { releaseOutput = resolve; }); }, detach(sessionId, direction, reason) { detached.push({ sessionId, direction, reason }); }, }, registerBridges() {}, }); runtime.start(); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "final" }); runtime.createSender(7).send("netcatty:exit", { sessionId: "s1", reason: "closed" }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages.map((message) => message.kind), ["output-tap"]); releaseOutput("FINAL"); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages.map((message) => message.kind), [ "output-tap", "output", "renderer-event", ]); assert.equal(parentPort.messages[1].data, "FINAL"); assert.equal(parentPort.messages[2].channel, "netcatty:exit"); assert.deepEqual(detached, [{ sessionId: "s1", direction: undefined, reason: "session-closed" }]); }); test("runtime keeps direct output ordered behind a pending chunk after interceptor disable", async () => { const parentPort = createParentPort(); let mode = 2; let releaseOutput; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return mode; }, observeOutput() { return false; }, interceptOutput() { mode = 0; return new Promise((resolve) => { releaseOutput = resolve; }); }, }, registerBridges() {}, }); runtime.start(); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "first" }); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "second" }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages.map((message) => message.kind), ["output-tap", "output-tap"]); releaseOutput("FIRST"); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual( parentPort.messages.filter((message) => message.kind === "output").map((message) => message.data), ["FIRST", "second"], ); }); test("runtime drops a pending intercepted chunk after the session output route is reused", async () => { const parentPort = createParentPort(); const oldPort = new FakePort(); const newPort = new FakePort(); const detached = []; let releaseOutput; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 2; }, observeOutput() { return false; }, interceptOutput() { return new Promise((resolve) => { releaseOutput = resolve; }); }, detach(sessionId, direction, reason) { detached.push({ sessionId, direction, reason }); }, }, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [] }, ports: [oldPort], }); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "old" }); await new Promise((resolve) => setImmediate(resolve)); parentPort.emitMessage({ kind: "close-output-port", sessionId: "s1" }); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [] }, ports: [newPort], }); releaseOutput("STALE"); await new Promise((resolve) => setImmediate(resolve)); assert.equal(oldPort.closed, true); assert.deepEqual(newPort.messages, []); assert.deepEqual(detached, [{ sessionId: "s1", direction: undefined, reason: "session-closed", }]); }); test("runtime submits queued output to the bounded pipeline before earlier transforms finish", async () => { const parentPort = createParentPort(); const releases = []; const calls = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode() { return 2; }, observeOutput() { return false; }, interceptOutput(_sessionId, data) { calls.push(data); return new Promise((resolve) => releases.push(() => resolve(data.toUpperCase()))); }, }, registerBridges() {}, }); runtime.start(); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "first" }); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "second" }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(calls, ["first", "second"]); releases[0](); releases[1](); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual( parentPort.messages.filter((message) => message.kind === "output").map((message) => message.data), ["FIRST", "SECOND"], ); }); test("runtime routes terminal data over a transferred output port", async () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:test", (event) => { event.sender.send("netcatty:data", { sessionId: "s1", data: "hello" }); return { done: true }; }); }, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: ["early"], }, ports: [outputPort], }); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:test", payload: {}, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.equal(outputPort.started, true); assert.deepEqual(outputPort.messages, [ { sessionId: "s1", data: "early" }, { sessionId: "s1", data: "hello" }, ]); assert.equal(parentPort.messages[0].kind, "output-port-ready"); assert.deepEqual(parentPort.messages[1], { kind: "output-tap", sessionId: "s1", data: "hello", sessionGeneration: 0, }); assert.equal(parentPort.messages.some((message) => message.kind === "output"), false); }); test("runtime routes buffered startup output through the selected interceptor", async () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const intercepted = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode: () => 2, observeOutput: () => false, interceptOutput(sessionId, data) { intercepted.push({ sessionId, data }); return Promise.resolve(data.toUpperCase()); }, }, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [{ data: "early", meta: { source: "startup" } }], }, ports: [outputPort], }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(intercepted, [{ sessionId: "s1", data: "early" }]); assert.deepEqual(outputPort.messages, [{ sessionId: "s1", data: "EARLY", meta: { source: "startup", pluginPipelineIngressBytes: 5, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: false, }, }]); assert.deepEqual(parentPort.messages, [{ kind: "output-port-ready", sessionId: "s1" }]); }); test("runtime does not re-intercept already transformed buffered output", async () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const intercepted = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode: () => 2, observeOutput: () => false, interceptOutput(sessionId, data) { intercepted.push({ sessionId, data }); return Promise.resolve(`again:${data}`); }, }, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [{ data: "EARLY", meta: { pluginPipelineIngressBytes: 5, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: true, }, }], }, ports: [outputPort], }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(intercepted, []); assert.deepEqual(outputPort.messages, [{ sessionId: "s1", data: "EARLY", meta: { pluginPipelineIngressBytes: 5, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: true, }, }]); assert.deepEqual(parentPort.messages, [{ kind: "output-port-ready", sessionId: "s1" }]); }); test("runtime re-intercepts raw buffered output that only inherited ingress accounting", async () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const intercepted = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode: () => 2, observeOutput: () => false, interceptOutput(sessionId, data) { intercepted.push({ sessionId, data }); return Promise.resolve(`processed:${data}`); }, }, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [{ data: "raw", meta: { pluginPipelineIngressBytes: 9 }, }], }, ports: [outputPort], }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(intercepted, [{ sessionId: "s1", data: "raw" }]); assert.deepEqual(outputPort.messages, [{ sessionId: "s1", data: "processed:raw", meta: { pluginPipelineIngressBytes: 12, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: false, }, }]); assert.deepEqual(parentPort.messages, [{ kind: "output-port-ready", sessionId: "s1" }]); }); test("runtime adds retained raw bytes after manager partial-trim accounting", async () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const intercepted = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode: () => 2, observeOutput: () => false, interceptOutput(sessionId, data) { intercepted.push({ sessionId, data }); return Promise.resolve(`processed:${data}`); }, }, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [{ data: "bcde", meta: { pluginPipelineIngressBytes: 11 }, }], }, ports: [outputPort], }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(intercepted, [{ sessionId: "s1", data: "bcde" }]); assert.deepEqual(outputPort.messages, [{ sessionId: "s1", data: "processed:bcde", meta: { pluginPipelineIngressBytes: 15, pluginPipelineProcessed: true, pluginPipelineSensitiveInput: false, }, }]); }); test("runtime counts retained raw bytes when replay continues without an interceptor", async () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: { getOutputMode: () => 0, observeOutput: () => false, }, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1", bufferedOutput: [{ data: "raw", meta: { pluginPipelineIngressBytes: 9 }, }], }, ports: [outputPort], }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(outputPort.messages, [{ sessionId: "s1", data: "raw", meta: { pluginPipelineIngressBytes: 12 }, }]); }); test("runtime.createSender uses the transferred output port", () => { const parentPort = createParentPort(); const outputPort = new FakePort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges() {}, }); runtime.start(); parentPort.emitMessage({ data: { kind: "output-port", sessionId: "s1" }, ports: [outputPort], }); runtime.createSender(7).send("netcatty:data", { sessionId: "s1", data: "hello" }); assert.deepEqual(outputPort.messages, [{ sessionId: "s1", data: "hello" }]); assert.deepEqual(parentPort.messages.at(-1), { kind: "output-tap", sessionId: "s1", data: "hello", sessionGeneration: 0, }); }); test("runtime forwards non-output renderer events to the parent", async () => { const parentPort = createParentPort(); const runtime = createTerminalWorkerRuntime({ parentPort, registerBridges(ipcMain) { ipcMain.handle("netcatty:test", (event) => { event.sender.send("netcatty:exit", { sessionId: "s1", reason: "closed" }); return { done: true }; }); }, }); runtime.start(); parentPort.emitMessage({ kind: "request", requestId: "req-1", channel: "netcatty:test", payload: {}, webContentsId: 7, }); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(parentPort.messages[0], { kind: "renderer-event", webContentsId: 7, channel: "netcatty:exit", payload: { sessionId: "s1", reason: "closed" }, sessionGeneration: 0, }); }); test("runtime contains throws from fire-and-forget send listeners", () => { const parentPort = createParentPort(); const seenPayloads = []; const runtime = createTerminalWorkerRuntime({ parentPort, terminalDataPipeline: null, registerBridges(ipcMain) { ipcMain.on("netcatty:write", (_event, payload) => { seenPayloads.push(payload); throw new Error("listener blew up synchronously"); }); }, }); runtime.start(); assert.doesNotThrow(() => parentPort.emitMessage({ kind: "send", channel: "netcatty:write", payload: { sessionId: "s1", data: "ls\r" }, webContentsId: 7, })); assert.deepEqual(seenPayloads, [{ sessionId: "s1", data: "ls\r" }]); // A following message must still dispatch normally after the swallowed throw. assert.doesNotThrow(() => parentPort.emitMessage({ kind: "send", channel: "netcatty:write", payload: { sessionId: "s1", data: "pwd\r" }, webContentsId: 7, })); assert.deepEqual(seenPayloads, [ { sessionId: "s1", data: "ls\r" }, { sessionId: "s1", data: "pwd\r" }, ]); });