feat(bridge): establish local pi status bridge

Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
This commit is contained in:
2026-07-27 14:51:45 +02:00
commit b7fea83ed6
91 changed files with 15029 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { createAgentRegistry } from "../src/bridge/agent-registry.js";
test("recovers an exited agent with its last Pi session reference", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-recovery-"));
const home = join(root, "home");
const sessionRoot = join(root, "sessions");
await mkdir(home);
const sessionFile = join(sessionRoot, "pi-session.jsonl");
const calls = [];
const registry = createAgentRegistry({
homeWorktree: home,
sessionRoot,
recoveryDelayForAttempt: () => 0,
sleep: async () => {},
startAdapter: (options) => {
const adapter = {
send: async () => ({
type: "response",
success: true,
data: { sessionFile },
}),
respondToExtension: () => {},
stop: async () => {},
};
calls.push({ options, adapter });
return adapter;
},
});
const agent = await registry.start();
calls[0].options.onError({ code: "child_exited", message: "crashed" });
await registry.waitForRecovery(agent.id);
assert.equal(calls.length, 2);
assert.equal(calls[1].options.sessionPath, sessionFile);
assert.deepEqual(
registry.eventsAfter(agent.id, 0).map((event) => event.type),
["agent_state", "recovery", "recovery", "agent_state"],
);
await registry.stop();
});
+240
View File
@@ -0,0 +1,240 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { createAgentRegistry } from "../src/bridge/agent-registry.js";
async function createWorktrees() {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-worktrees-"));
const home = join(root, "home");
const feature = join(root, "feature");
await Promise.all([mkdir(home), mkdir(feature)]);
return { root, home, feature, sessionRoot: join(root, "sessions") };
}
function createAdapterFactory() {
const calls = [];
return {
calls,
startAdapter: (options) => {
const adapter = {
sent: [],
extensionResponses: [],
stopped: false,
send(command) {
this.sent.push(command);
return Promise.resolve({
type: "response",
command: command.type,
success: true,
});
},
respondToExtension(requestId, response) {
this.extensionResponses.push({ requestId, response });
},
async stop() {
this.stopped = true;
},
};
calls.push({ options, adapter });
return adapter;
},
};
}
test("starts only the home agent and creates other worktree agents on explicit selection", async () => {
const worktrees = await createWorktrees();
const fixture = createAdapterFactory();
const registry = createAgentRegistry({
homeWorktree: worktrees.home,
sessionRoot: worktrees.sessionRoot,
startAdapter: fixture.startAdapter,
});
const home = await registry.start();
assert.equal(registry.listAgents().length, 1);
assert.equal(home.worktreePath, worktrees.home);
const feature = await registry.selectWorktree(worktrees.feature);
const selectedAgain = await registry.selectWorktree(
`${worktrees.feature}/../feature`,
);
assert.equal(feature.id, selectedAgain.id);
assert.notEqual(home.id, feature.id);
assert.deepEqual(
fixture.calls.map(({ options }) => options.cwd),
[worktrees.home, worktrees.feature],
);
assert.notEqual(home.sessionDir, feature.sessionDir);
await registry.stop();
assert.ok(fixture.calls.every(({ adapter }) => adapter.stopped));
});
test("catalogues managed directories and exposes only their sessions", async () => {
const worktrees = await createWorktrees();
const fixture = createAdapterFactory();
const registry = createAgentRegistry({
homeWorktree: worktrees.home,
sessionRoot: worktrees.sessionRoot,
startAdapter: fixture.startAdapter,
});
await registry.start();
const feature = await registry.selectWorktree(worktrees.feature);
const olderSession = join(feature.sessionDir, "older.jsonl");
const newestSession = join(feature.sessionDir, "newest.jsonl");
await writeFile(
olderSession,
[
JSON.stringify({
type: "session",
id: "older",
timestamp: "2025-01-01T00:00:00.000Z",
cwd: worktrees.feature,
}),
JSON.stringify({
type: "message",
message: { role: "user", content: "Older task" },
}),
].join("\n"),
);
await writeFile(
newestSession,
[
JSON.stringify({
type: "session",
id: "newest",
timestamp: "2025-01-02T00:00:00.000Z",
cwd: worktrees.feature,
}),
JSON.stringify({ type: "session_info", name: "Newest work" }),
JSON.stringify({
type: "message",
message: { role: "user", content: "Newest task" },
}),
].join("\n"),
);
await writeFile(
join(feature.sessionDir, "bridge-agent.json"),
`${JSON.stringify({ sessionPath: newestSession, worktreePath: worktrees.feature })}\n`,
);
const sessions = await registry.listSessions(feature.id);
assert.deepEqual(
sessions.map(({ id, name, isCurrent }) => ({ id, name, isCurrent })),
[
{ id: "newest", name: "Newest work", isCurrent: false },
{ id: "older", name: undefined, isCurrent: false },
],
);
await registry.route(feature.id, "switch_session", {
sessionPath: newestSession,
});
await registry.route(feature.id, "new_session");
assert.deepEqual(
fixture.calls[1].adapter.sent.map((command) => command.type),
["get_state", "switch_session", "get_state", "new_session", "get_state"],
);
await assert.rejects(
registry.route(feature.id, "switch_session", {
sessionPath: olderSession.replace("older", "../outside"),
}),
/ENOENT|belong/,
);
await registry.stop();
const restored = createAgentRegistry({
homeWorktree: worktrees.home,
sessionRoot: worktrees.sessionRoot,
startAdapter: createAdapterFactory().startAdapter,
});
await restored.start();
assert.deepEqual(await restored.listDirectories(), [
{ worktreePath: worktrees.feature, state: "inactive" },
{
worktreePath: worktrees.home,
state: "idle",
agentId: restored.listAgents()[0].id,
},
]);
await restored.stop();
});
test("routes commands by explicit agent ID and replays only events after the cursor", async () => {
const worktrees = await createWorktrees();
const fixture = createAdapterFactory();
const registry = createAgentRegistry({
homeWorktree: worktrees.home,
sessionRoot: worktrees.sessionRoot,
startAdapter: fixture.startAdapter,
});
const agent = await registry.start();
const [{ options, adapter }] = fixture.calls;
const response = await registry.route(agent.id, "prompt", {
message: "Stay in this worktree",
});
assert.deepEqual(response, {
type: "response",
command: "prompt",
success: true,
});
const stats = await registry.route(agent.id, "get_session_stats");
assert.deepEqual(stats, {
type: "response",
command: "get_session_stats",
success: true,
});
const commands = await registry.route(agent.id, "get_commands");
assert.deepEqual(commands, {
type: "response",
command: "get_commands",
success: true,
});
assert.deepEqual(adapter.sent, [
{ type: "get_state" },
{ type: "prompt", message: "Stay in this worktree" },
{ type: "get_session_stats" },
{ type: "get_commands" },
]);
options.onEvent({
type: "agent_state",
data: { state: "streaming" },
});
await registry.route(agent.id, "submit_prompt", {
message: "After this turn",
});
assert.deepEqual(adapter.sent.at(-1), {
type: "follow_up",
message: "After this turn",
});
options.onEvent({
type: "stream",
data: { event: { type: "message_update" } },
});
options.onEvent({ type: "queue", data: { event: { type: "queue_update" } } });
assert.deepEqual(
registry.eventsAfter(agent.id, 0).map(({ seq, type }) => ({ seq, type })),
[
{ seq: 1, type: "agent_state" },
{ seq: 2, type: "stream" },
{ seq: 3, type: "queue" },
],
);
assert.deepEqual(
registry.eventsAfter(agent.id, 1).map(({ seq, type }) => ({ seq, type })),
[
{ seq: 2, type: "stream" },
{ seq: 3, type: "queue" },
],
);
await assert.rejects(
registry.route("missing-agent", "abort"),
/unknown agent/,
);
await registry.stop();
});
+113
View File
@@ -0,0 +1,113 @@
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { mkdir, mkdtemp } from "node:fs/promises";
import { createConnection } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { startBridgeService } from "../src/bridge/service.js";
function connect(socketPath) {
return new Promise((resolve, reject) => {
const socket = createConnection(socketPath);
socket.once("connect", () => resolve(socket));
socket.once("error", reject);
});
}
function request(socket, frame) {
return new Promise((resolve, reject) => {
let buffer = "";
const onData = (chunk) => {
buffer += chunk;
const newline = buffer.indexOf("\n");
if (newline === -1) return;
socket.off("data", onData);
try {
resolve(JSON.parse(buffer.slice(0, newline)));
} catch (error) {
reject(error);
}
};
socket.on("data", onData);
socket.once("error", reject);
socket.write(`${JSON.stringify(frame)}\n`);
});
}
function createAdapterFactory() {
const calls = [];
return {
calls,
startAdapter: (options) => {
const adapter = new EventEmitter();
adapter.sent = [];
adapter.send = async (command) => {
adapter.sent.push(command);
return {
type: "response",
command: command.type,
success: true,
data: { command },
};
};
adapter.respondToExtension = () => {};
adapter.stop = async () => {};
calls.push({ options, adapter });
return adapter;
},
};
}
test("starts the home agent and dispatches local protocol requests to the selected worktree", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-service-"));
const home = join(root, "home");
const feature = join(root, "feature");
await Promise.all([mkdir(home), mkdir(feature)]);
const fixture = createAdapterFactory();
const service = await startBridgeService({
homeWorktree: home,
runtimeDir: join(root, "runtime"),
sessionRoot: join(root, "sessions"),
startAdapter: fixture.startAdapter,
});
const socket = await connect(service.socketPath);
try {
const agents = await request(socket, {
version: "v1",
id: "list-1",
op: "list_agents",
});
assert.equal(agents.result.agents.length, 1);
assert.equal(agents.result.agents[0].worktreePath, home);
const selected = await request(socket, {
version: "v1",
id: "select-1",
op: "select_agent",
payload: { worktreePath: feature },
});
const featureAgent = selected.result.agent;
const prompt = await request(socket, {
version: "v1",
id: "prompt-1",
op: "prompt",
agentId: featureAgent.id,
payload: { message: "Use this worktree" },
});
assert.equal(prompt.ok, true);
assert.deepEqual(
fixture.calls.map(({ options }) => options.cwd),
[home, feature],
);
assert.deepEqual(fixture.calls[1].adapter.sent, [
{ type: "get_state" },
{ type: "prompt", message: "Use this worktree" },
]);
} finally {
socket.destroy();
await service.close();
}
});
+45
View File
@@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdir, mkdtemp } from "node:fs/promises";
import { promisify } from "node:util";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { startBridgeService } from "../src/bridge/service.js";
const execFileAsync = promisify(execFile);
test("executes an approved bridge request through the local client CLI", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-cli-"));
const home = join(root, "home");
await mkdir(home);
const service = await startBridgeService({
homeWorktree: home,
runtimeDir: join(root, "runtime"),
sessionRoot: join(root, "sessions"),
startAdapter: () => ({
send: async () => ({ type: "response", success: true }),
respondToExtension: () => {},
stop: async () => {},
}),
});
try {
const { stdout } = await execFileAsync(
process.execPath,
[
"src/client/cli.js",
"--socket",
service.socketPath,
"request",
'{"op":"list_agents"}',
],
{ cwd: process.cwd() },
);
const result = JSON.parse(stdout);
assert.equal(result.agents.length, 1);
assert.equal(result.agents[0].worktreePath, home);
} finally {
await service.close();
}
});
+73
View File
@@ -0,0 +1,73 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { startBridgeDaemon } from "../src/bridge/daemon.js";
import { createNoctaliaStateRelay } from "../src/client/noctalia-relay.js";
test("starts a bridge daemon around the local bridge service", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-daemon-"));
const home = join(root, "home");
await mkdir(home);
const daemon = await startBridgeDaemon({
homeWorktree: home,
runtimeDir: join(root, "runtime"),
sessionRoot: join(root, "sessions"),
startAdapter: () => ({
send: async () => ({ type: "response", success: true }),
respondToExtension: () => {},
stop: async () => {},
}),
});
try {
assert.match(daemon.socketPath, /bridge\.sock$/);
assert.equal(daemon.listAgents()[0].worktreePath, home);
} finally {
await daemon.close();
}
});
test("relays only the selected agent's bridge state into a presentation update", async () => {
let listener;
const updates = [];
const relay = createNoctaliaStateRelay({
client: {
request: async () => ({ data: { isStreaming: false } }),
subscribe: async (_agentId, _cursor, callback) => {
listener = callback;
return () => {};
},
},
agent: { id: "agent-1", worktreePath: "/worktrees/feature" },
onState: (state) => updates.push(state),
});
await relay.start();
listener({ type: "agent_state", data: { state: "streaming" } });
listener({
type: "queue",
data: { event: { steering: ["focus"], followUp: [] } },
});
assert.deepEqual(updates, [
{
state: "idle",
projectLabel: "feature",
attentionCount: 0,
detail: "Idle",
},
{
state: "streaming",
projectLabel: "feature",
attentionCount: 0,
detail: "Streaming",
},
{
state: "streaming",
projectLabel: "feature",
attentionCount: 1,
detail: "Streaming",
},
]);
});
+106
View File
@@ -0,0 +1,106 @@
import assert from "node:assert/strict";
import { mkdtemp, readFile, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import {
BridgeAlreadyRunningError,
BridgeLockError,
acquireBridgeLock,
lockMode,
} from "../src/bridge/instance-lock.js";
async function paths() {
const directory = await mkdtemp(join(tmpdir(), "pi-status-bridge-lock-"));
return {
lockPath: join(directory, "bridge.lock"),
socketPath: join(directory, "bridge.sock"),
};
}
test("acquires an owner-only lock and releases only its own token", async () => {
const { lockPath, socketPath } = await paths();
const lock = await acquireBridgeLock({
lockPath,
socketPath,
pid: 101,
now: () => new Date("2026-01-02T03:04:05.000Z"),
});
const lockContents = await readFile(lockPath, "utf8");
let metadata;
try {
metadata = JSON.parse(lockContents);
} catch (error) {
assert.fail(
error instanceof Error ? error.message : "lock file was not JSON",
);
}
const lockStats = await stat(lockPath);
assert.equal(metadata.pid, 101);
assert.equal(metadata.socketPath, socketPath);
assert.equal(lockStats.mode & 0o777, 0o600);
assert.equal(lockMode, 0o600);
assert.equal(await lock.release(), true);
assert.equal(await lock.release(), false);
});
test("refuses to replace a live owner", async () => {
const { lockPath, socketPath } = await paths();
await writeFile(
lockPath,
JSON.stringify({
pid: 202,
socketPath,
startedAt: "2026-01-02T03:04:05.000Z",
token: "live-owner",
}),
);
await assert.rejects(
acquireBridgeLock({
lockPath,
socketPath,
pid: 303,
processAlive: (pid) => pid === 202,
}),
BridgeAlreadyRunningError,
);
});
test("recovers only a proven-stale lock", async () => {
const { lockPath, socketPath } = await paths();
await writeFile(
lockPath,
JSON.stringify({
pid: 404,
socketPath,
startedAt: "2026-01-02T03:04:05.000Z",
token: "stale-owner",
}),
);
const lock = await acquireBridgeLock({
lockPath,
socketPath,
pid: 505,
processAlive: () => false,
});
assert.equal(lock.metadata.pid, 505);
await lock.release();
});
test("does not reclaim a malformed lock", async () => {
const { lockPath, socketPath } = await paths();
await writeFile(lockPath, "broken");
await assert.rejects(
acquireBridgeLock({
lockPath,
socketPath,
pid: 606,
processAlive: () => false,
}),
BridgeLockError,
);
});
+59
View File
@@ -0,0 +1,59 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { connectLocalClient } from "../src/client/local-client.js";
import { startBridgeService } from "../src/bridge/service.js";
test("uses only the approved local protocol for requests and live event subscriptions", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-client-"));
const home = join(root, "home");
await mkdir(home);
const starts = [];
const service = await startBridgeService({
homeWorktree: home,
runtimeDir: join(root, "runtime"),
sessionRoot: join(root, "sessions"),
startAdapter: (options) => {
const adapter = {
send: async () => ({ type: "response", success: true }),
respondToExtension: () => {},
stop: async () => {},
};
starts.push({ options, adapter });
return adapter;
},
});
const client = await connectLocalClient({ socketPath: service.socketPath });
try {
const agents = await client.request("list_agents");
assert.equal(agents.agents.length, 1);
const events = [];
const unsubscribe = await client.subscribe(
agents.agents[0].id,
0,
(event) => events.push(event),
);
starts[0].options.onEvent({
type: "queue",
data: { event: { type: "queue_update" } },
});
await new Promise((resolve) => setTimeout(resolve, 10));
assert.deepEqual(events, [
{
version: "v1",
seq: 1,
type: "queue",
agentId: agents.agents[0].id,
data: { event: { type: "queue_update" } },
},
]);
unsubscribe();
} finally {
await client.close();
await service.close();
}
});
+34
View File
@@ -0,0 +1,34 @@
import assert from "node:assert/strict";
import test from "node:test";
import { publishNoctaliaState } from "../src/client/noctalia-ipc.js";
test("publishes presentation state through Noctalia's documented IPC command", async () => {
const calls = [];
await publishNoctaliaState(
{
state: "streaming",
projectLabel: "feature",
attentionCount: 2,
detail: "Streaming",
},
{ execute: async (command, args) => calls.push({ command, args }) },
);
assert.deepEqual(calls, [
{
command: "qs",
args: [
"-c",
"noctalia-shell",
"ipc",
"call",
"plugin:pi-status-bridge",
"setBridgeState",
"streaming",
"feature",
"2",
"Streaming",
],
},
]);
});
+34
View File
@@ -0,0 +1,34 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
test("ships a Noctalia plugin with compact bar and presentation-only panel entry points", async () => {
const manifestText = await readFile(
"noctalia-plugin/pi-status-bridge/manifest.json",
"utf8",
);
let manifest;
try {
manifest = JSON.parse(manifestText);
} catch (error) {
assert.fail(
error instanceof Error ? error.message : "manifest was not valid JSON",
);
}
const [bar, panel, main] = await Promise.all([
readFile("noctalia-plugin/pi-status-bridge/BarWidget.qml", "utf8"),
readFile("noctalia-plugin/pi-status-bridge/Panel.qml", "utf8"),
readFile("noctalia-plugin/pi-status-bridge/Main.qml", "utf8"),
]);
assert.equal(manifest.id, "pi-status-bridge");
assert.deepEqual(manifest.entryPoints, {
main: "Main.qml",
barWidget: "BarWidget.qml",
panel: "Panel.qml",
});
assert.match(bar, /pluginApi\.togglePanel\(root\.screen, visualCapsule\)/);
assert.match(panel, /allowAttach: true/);
assert.match(main, /IpcHandler/);
assert.match(main, /setBridgeState/);
});
+21
View File
@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
test("ships a v5 Noctalia compatibility launcher instead of a primary panel", async () => {
const [manifest, bridge] = await Promise.all([
readFile("noctalia-v5-plugin/pi-status-bridge/plugin.toml", "utf8"),
readFile("noctalia-v5-plugin/pi-status-bridge/bridge.luau", "utf8"),
]);
assert.doesNotMatch(manifest, /\[\[panel\]\]/);
assert.match(manifest, /entry = "bridge\.luau"/);
assert.match(bridge, /PI_STATUS_UI_BINARY/);
assert.match(bridge, /pi-status-ui/);
assert.match(bridge, /noctalia\.runAsync/);
assert.match(bridge, /setsid -f env TMPDIR=\/tmp/);
assert.match(bridge, /pi-status-ui\.log/);
assert.doesNotMatch(bridge, /togglePanel/);
assert.match(bridge, /Pi reconnecting/);
assert.match(bridge, /discover_home_agent\(socket\)/);
});
+168
View File
@@ -0,0 +1,168 @@
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import test from "node:test";
import { startPiRpcAdapter } from "../src/bridge/pi-rpc-adapter.js";
class FakePiChild extends EventEmitter {
constructor() {
super();
this.stdin = new PassThrough();
this.stdout = new PassThrough();
this.stderr = new PassThrough();
this.killed = false;
}
kill(signal) {
this.killed = true;
this.emit("exit", null, signal);
return true;
}
}
function parseSent(stdin) {
try {
return stdin
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
} catch (error) {
throw new Error(
`fake child received invalid JSON: ${error instanceof Error ? error.message : "unknown error"}`,
);
}
}
function createFixture() {
const child = new FakePiChild();
const calls = [];
let stdin = "";
child.stdin.on("data", (chunk) => {
stdin += chunk;
});
return {
child,
calls,
spawn: (command, args, options) => {
calls.push({ command, args, options });
return child;
},
sent: () => parseSent(stdin),
};
}
test("starts Pi in RPC mode and correlates a command response", async () => {
const fixture = createFixture();
const events = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
onEvent: (event) => events.push(event),
});
const response = adapter.send({
type: "prompt",
message: "Implement the bridge",
});
const [command] = fixture.sent();
assert.deepEqual(fixture.calls[0], {
command: "pi",
args: ["--mode", "rpc", "--session-dir", "/workspace/sessions"],
options: { cwd: "/workspace/home", stdio: ["pipe", "pipe", "pipe"] },
});
assert.equal(command.type, "prompt");
assert.equal(command.message, "Implement the bridge");
assert.match(command.id, /^bridge-/);
fixture.child.stdout.write('{"type":"agent_start"}\n');
fixture.child.stdout.write(
`${JSON.stringify({ type: "response", id: command.id, command: "prompt", success: true })}\n`,
);
assert.deepEqual(await response, {
type: "response",
id: command.id,
command: "prompt",
success: true,
});
assert.deepEqual(events, [
{
seq: 1,
type: "agent_state",
data: { state: "streaming", event: { type: "agent_start" } },
},
]);
await adapter.stop();
});
test("requests Pi session statistics for context and token status", async () => {
const fixture = createFixture();
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
});
const pending = adapter.send({ type: "get_session_stats" });
const [command] = fixture.sent();
assert.equal(command.type, "get_session_stats");
fixture.child.stdout.write(
`${JSON.stringify({ type: "response", id: command.id, command: "get_session_stats", success: true, data: { contextUsage: { tokens: 32000, contextWindow: 200000 } } })}\n`,
);
const stats = await pending;
assert.equal(stats.data.contextUsage.tokens, 32000);
await adapter.stop();
});
test("forwards extension responses without replacing Pi's request ID", async () => {
const fixture = createFixture();
const events = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
onEvent: (event) => events.push(event),
});
const extensionRequest = {
type: "extension_ui_request",
id: "extension-request-1",
method: "confirm",
title: "Keep U+2028 here",
message: "Approve?",
};
fixture.child.stdout.write(`${JSON.stringify(extensionRequest)}\n`);
adapter.respondToExtension("extension-request-1", { confirmed: false });
assert.deepEqual(events, [
{ seq: 1, type: "extension_ui_request", data: { event: extensionRequest } },
]);
assert.deepEqual(fixture.sent(), [
{
type: "extension_ui_response",
id: "extension-request-1",
confirmed: false,
},
]);
await adapter.stop();
});
test("reports invalid child output and rejects pending commands on child exit", async () => {
const fixture = createFixture();
const errors = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: fixture.spawn,
onError: (error) => errors.push(error),
});
const pending = adapter.send({ type: "get_state" });
fixture.child.stdout.write("not-json\n");
fixture.child.emit("exit", 1, null);
await assert.rejects(pending, /exited before responding/);
assert.equal(errors[0].code, "invalid_json");
await adapter.stop();
});
+96
View File
@@ -0,0 +1,96 @@
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import test from "node:test";
import { startPiRpcAdapter } from "../src/bridge/pi-rpc-adapter.js";
function createChild() {
const child = new EventEmitter();
child.stdin = new PassThrough();
child.stdout = new PassThrough();
child.stderr = new PassThrough();
child.kill = (signal) => {
child.emit("exit", null, signal);
return true;
};
return child;
}
test("normalizes streaming, tool, and queue events in child output order", async () => {
const child = createChild();
const events = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: () => child,
onEvent: (event) => events.push(event),
});
const piEvents = [
{
type: "message_update",
message: { role: "assistant" },
assistantMessageEvent: { type: "text_delta", delta: "Hello" },
},
{
type: "tool_execution_start",
toolCallId: "tool-1",
toolName: "bash",
args: { command: "pwd" },
},
{
type: "tool_execution_end",
toolCallId: "todo-1",
toolName: "todo",
result: {
details: {
tasks: [{ id: 1, subject: "Ship pane", status: "in_progress" }],
},
},
isError: false,
},
{ type: "queue_update", steering: ["Refocus"], followUp: [] },
];
child.stdout.write(JSON.stringify(piEvents[0]).slice(0, 30));
child.stdout.write(
`${JSON.stringify(piEvents[0]).slice(30)}\n${JSON.stringify(piEvents[1])}\n${JSON.stringify(piEvents[2])}\n${JSON.stringify(piEvents[3])}\n`,
);
assert.deepEqual(events, [
{ seq: 1, type: "stream", data: { event: piEvents[0] } },
{ seq: 2, type: "tool", data: { event: piEvents[1] } },
{ seq: 3, type: "tool", data: { event: piEvents[2] } },
{ seq: 4, type: "queue", data: { event: piEvents[3] } },
]);
await adapter.stop();
});
test("marks Pi idle only after it settles and preserves extension errors as diagnostics", async () => {
const child = createChild();
const events = [];
const adapter = startPiRpcAdapter({
cwd: "/workspace/home",
sessionDir: "/workspace/sessions",
spawnProcess: () => child,
onEvent: (event) => events.push(event),
});
const piEvents = [
{ type: "agent_start" },
{ type: "agent_end", willRetry: false },
{ type: "agent_settled" },
{ type: "extension_error", error: "non-fatal extension failure" },
];
child.stdout.write(
`${piEvents.map((event) => JSON.stringify(event)).join("\n")}\n`,
);
assert.deepEqual(
events.map((event) => ({ type: event.type, state: event.data.state })),
[
{ type: "agent_state", state: "streaming" },
{ type: "transcript", state: undefined },
{ type: "agent_state", state: "idle" },
{ type: "transcript", state: undefined },
],
);
await adapter.stop();
});
+198
View File
@@ -0,0 +1,198 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
MAX_FRAME_BYTES,
ProtocolError,
parseRequestFrame,
validateEvent,
} from "../src/protocol/index.js";
const request = (value) =>
JSON.stringify({ version: "v1", id: "request-1", ...value });
test("accepts a command scoped to its selected agent", () => {
assert.deepEqual(
parseRequestFrame(
request({
op: "prompt",
agentId: "agent-main",
payload: { message: "implement the protocol" },
}),
),
{
version: "v1",
id: "request-1",
op: "prompt",
agentId: "agent-main",
payload: { message: "implement the protocol" },
},
);
});
test("accepts an agent transcript request without a synthetic cursor", () => {
assert.deepEqual(
parseRequestFrame(request({ op: "get_transcript", agentId: "agent-main" })),
{
version: "v1",
id: "request-1",
op: "get_transcript",
agentId: "agent-main",
},
);
});
test("accepts bridge-managed directory and session operations", () => {
assert.deepEqual(parseRequestFrame(request({ op: "list_directories" })), {
version: "v1",
id: "request-1",
op: "list_directories",
});
assert.deepEqual(
parseRequestFrame(request({ op: "list_sessions", agentId: "agent-main" })),
{
version: "v1",
id: "request-1",
op: "list_sessions",
agentId: "agent-main",
},
);
assert.deepEqual(
parseRequestFrame(request({ op: "new_session", agentId: "agent-main" })),
{
version: "v1",
id: "request-1",
op: "new_session",
agentId: "agent-main",
},
);
assert.deepEqual(
parseRequestFrame(
request({
op: "switch_session",
agentId: "agent-main",
payload: { sessionPath: "/sessions/session.jsonl" },
}),
),
{
version: "v1",
id: "request-1",
op: "switch_session",
agentId: "agent-main",
payload: { sessionPath: "/sessions/session.jsonl" },
},
);
assert.throws(
() =>
parseRequestFrame(
request({
op: "switch_session",
agentId: "agent-main",
payload: { sessionPath: "relative.jsonl" },
}),
),
/must be absolute/,
);
});
test("accepts an agent session-statistics request", () => {
assert.deepEqual(
parseRequestFrame(
request({ op: "get_session_stats", agentId: "agent-main" }),
),
{
version: "v1",
id: "request-1",
op: "get_session_stats",
agentId: "agent-main",
},
);
});
test("accepts a default composer submit request", () => {
assert.deepEqual(
parseRequestFrame(
request({
op: "submit_prompt",
agentId: "agent-main",
payload: { message: "Continue" },
}),
),
{
version: "v1",
id: "request-1",
op: "submit_prompt",
agentId: "agent-main",
payload: { message: "Continue" },
},
);
});
test("accepts a selected agent get_commands request", () => {
assert.deepEqual(
parseRequestFrame(request({ op: "get_commands", agentId: "agent-main" })),
{
version: "v1",
id: "request-1",
op: "get_commands",
agentId: "agent-main",
},
);
});
test("rejects malformed, unknown, and oversized frames", () => {
assert.throws(() => parseRequestFrame("not json"), ProtocolError);
assert.throws(
() => parseRequestFrame(request({ op: "teleport" })),
/not supported/,
);
assert.throws(
() => parseRequestFrame("x".repeat(MAX_FRAME_BYTES + 1)),
/frame exceeds/,
);
});
test("rejects cross-worktree routing fields on agent commands", () => {
assert.throws(
() =>
parseRequestFrame(
request({
op: "prompt",
agentId: "agent-main",
payload: { message: "wrong worktree", worktreePath: "/other" },
}),
),
/not supported/,
);
});
test("requires explicit absolute worktree selection", () => {
assert.throws(
() =>
parseRequestFrame(
request({
op: "select_agent",
payload: { worktreePath: "relative/project" },
}),
),
/must be absolute/,
);
});
test("normalizes sequenced bridge events", () => {
assert.deepEqual(
validateEvent({
version: "v1",
seq: 2,
type: "extension_ui_request",
agentId: "agent-main",
data: { id: "extension-1", method: "confirm" },
}),
{
version: "v1",
seq: 2,
type: "extension_ui_request",
agentId: "agent-main",
data: { id: "extension-1", method: "confirm" },
},
);
});
+65
View File
@@ -0,0 +1,65 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createRecoverySupervisor } from "../src/bridge/recovery-supervisor.js";
test("restarts after bounded exponential delays and stops after exhaustion", async () => {
const delays = [];
const states = [];
let starts = 0;
const supervisor = createRecoverySupervisor({
start: async () => ({ attempt: ++starts }),
maxAttempts: 2,
delayForAttempt: (attempt) => attempt * 100,
sleep: async (delay) => delays.push(delay),
onState: (state) => states.push(state),
});
assert.deepEqual(await supervisor.handleUnexpectedExit(), { attempt: 1 });
assert.deepEqual(await supervisor.handleUnexpectedExit(), { attempt: 2 });
assert.equal(await supervisor.handleUnexpectedExit(), undefined);
assert.deepEqual(delays, [100, 200]);
assert.deepEqual(
states.map(({ state, attempt }) => ({ state, attempt })),
[
{ state: "recovering", attempt: 1 },
{ state: "healthy", attempt: 1 },
{ state: "recovering", attempt: 2 },
{ state: "healthy", attempt: 2 },
{ state: "failed", attempt: 2 },
],
);
});
test("restarts failed launches within the same bounded recovery budget", async () => {
let starts = 0;
const supervisor = createRecoverySupervisor({
start: async () => {
starts += 1;
if (starts === 1) throw new Error("temporary spawn failure");
return { attempt: starts };
},
maxAttempts: 2,
delayForAttempt: () => 0,
sleep: async () => {},
});
assert.deepEqual(await supervisor.handleUnexpectedExit(), { attempt: 2 });
assert.equal(starts, 2);
});
test("cancels scheduled recovery when stopped", async () => {
let resolveSleep;
const supervisor = createRecoverySupervisor({
start: async () => assert.fail("must not restart after stop"),
delayForAttempt: () => 1,
sleep: () =>
new Promise((resolve) => {
resolveSleep = resolve;
}),
});
const recovering = supervisor.handleUnexpectedExit();
supervisor.stop();
resolveSleep();
assert.equal(await recovering, undefined);
});
+29
View File
@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import { mkdtemp, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import test from "node:test";
import { BridgeAlreadyRunningError } from "../src/bridge/instance-lock.js";
import {
createRuntimePaths,
startBridgeServer,
} from "../src/bridge/runtime.js";
test("creates a private runtime directory and enforces one live bridge", async () => {
const runtimeDir = await mkdtemp(`${tmpdir()}/pi-status-bridge-runtime-`);
const paths = await createRuntimePaths(runtimeDir);
const runtimeStats = await stat(paths.directory);
assert.equal(runtimeStats.mode & 0o777, 0o700);
const bridge = await startBridgeServer({
runtimeDir,
handleRequest: async () => ({}),
});
try {
await assert.rejects(
startBridgeServer({ runtimeDir, handleRequest: async () => ({}) }),
BridgeAlreadyRunningError,
);
} finally {
await bridge.close();
}
});
+115
View File
@@ -0,0 +1,115 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp } from "node:fs/promises";
import { createConnection } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { startBridgeService } from "../src/bridge/service.js";
function connect(socketPath) {
return new Promise((resolve, reject) => {
const socket = createConnection(socketPath);
socket.once("connect", () => resolve(socket));
socket.once("error", reject);
});
}
function collectFrames(socket, count) {
return new Promise((resolve, reject) => {
const frames = [];
let buffer = "";
const onData = (chunk) => {
buffer += chunk;
while (buffer.includes("\n")) {
const index = buffer.indexOf("\n");
const line = buffer.slice(0, index);
buffer = buffer.slice(index + 1);
try {
frames.push(JSON.parse(line));
} catch (error) {
reject(error);
return;
}
if (frames.length === count) {
socket.off("data", onData);
resolve(frames);
return;
}
}
};
socket.on("data", onData);
socket.once("error", reject);
});
}
test("replays then streams subscribed agent events on the same local socket", async () => {
const root = await mkdtemp(join(tmpdir(), "pi-status-bridge-subscribe-"));
const home = join(root, "home");
await mkdir(home);
const calls = [];
const service = await startBridgeService({
homeWorktree: home,
runtimeDir: join(root, "runtime"),
sessionRoot: join(root, "sessions"),
startAdapter: (options) => {
const adapter = {
send: async () => ({ type: "response", success: true }),
respondToExtension: () => {},
stop: async () => {},
};
calls.push({ options, adapter });
return adapter;
},
});
calls[0].options.onEvent({
type: "stream",
data: { event: { type: "message_update", delta: "replayed" } },
});
const socket = await connect(service.socketPath);
try {
const frames = collectFrames(socket, 2);
socket.write(
`${JSON.stringify({ version: "v1", id: "subscribe-1", op: "subscribe", agentId: service.listAgents()[0].id, payload: {} })}\n`,
);
setTimeout(() => {
calls[0].options.onEvent({
type: "stream",
data: { event: { type: "message_update", delta: "live" } },
});
}, 10);
assert.deepEqual(await frames, [
{
version: "v1",
id: "subscribe-1",
ok: true,
result: {
events: [
{
version: "v1",
seq: 1,
type: "stream",
agentId: service.listAgents()[0].id,
data: { event: { type: "message_update", delta: "replayed" } },
},
],
},
},
{
version: "v1",
type: "event",
event: {
version: "v1",
seq: 2,
type: "stream",
agentId: service.listAgents()[0].id,
data: { event: { type: "message_update", delta: "live" } },
},
},
]);
} finally {
socket.destroy();
await service.close();
}
});
+214
View File
@@ -0,0 +1,214 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
test("renders every Pi RPC extension UI method without replying to fire-and-forget requests", async () => {
const source = await readFile("ui/src/App.tsx", "utf8");
for (const method of [
"select",
"confirm",
"input",
"editor",
"notify",
"setStatus",
"setWidget",
"setTitle",
"set_editor_text",
]) {
assert.match(source, new RegExp(`case "${method}"`));
}
assert.match(source, /setPendingExtension\(extension\)/);
assert.doesNotMatch(source, /This extension UI is unsupported/);
assert.match(source, /function stripAnsi/);
assert.match(source, /stripAnsi\(extension\.statusText\)/);
assert.match(source, /className="pi-controls"/);
assert.match(source, /aria-label="Model and thinking controls"/);
assert.doesNotMatch(source, /<details className="pi-controls">/);
assert.doesNotMatch(
source,
/event\.ctrlKey && event\.key\.toLowerCase\(\) === "c"/,
);
assert.match(source, /getCurrentWindow\(\)\s*\.hide\(\)\s*\.catch/);
assert.match(
source,
/window\.addEventListener\("keydown", handleKeydown, true\)/,
);
assert.match(source, /useLayoutEffect/);
assert.match(source, /transcript\.scrollTop = transcript\.scrollHeight/);
assert.match(source, /requestAnimationFrame\(scrollToLatest\)/);
assert.match(source, /const loadGeneration = useRef\(0\)/);
assert.match(
source,
/const lastEventSequence = useRef<Record<string, number>>/,
);
assert.match(source, /generation !== loadGeneration\.current/);
assert.match(source, /current\.phase !== "working"/);
assert.match(source, /bridgeEvent\.seq <= \(lastEventSequence\.current/);
assert.match(source, /\.toFixed\(1\)/);
assert.match(source, /formatTokens\(stats\.tokens\.input\)/);
assert.match(source, /formatTokens\(stats\.tokens\.output\)/);
assert.match(source, /formatTokens\(cacheTokens\)/);
assert.match(source, /function updateWorkProgress/);
assert.match(
source,
/className=\{`work-progress composer-progress \$\{workProgress\.phase\}`\}/,
);
assert.match(source, /event\.type === "tool"/);
assert.match(source, /event\.type === "queue"/);
assert.match(source, /composer-progress/);
assert.doesNotMatch(
source,
/<section className=\{`work-progress \$\{workProgress\.phase\}`\}/,
);
});
test("makes the window draggable from the non-interactive header", async () => {
const [component, styles, capability] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
readFile("ui/src-tauri/capabilities/default.json", "utf8"),
]);
assert.match(component, /startDragging\(\)/);
assert.match(
component,
/closest\(\s*"button, input, select, textarea",?\s*\)/,
);
assert.match(styles, /\.workflow-header \{[\s\S]*cursor: grab/);
assert.match(capability, /core:window:allow-start-dragging/);
});
test("opens local follow-up choices for /model and /resume", async () => {
const [component, styles] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
]);
assert.match(component, /const controlCommands: Command\[\]/);
assert.match(component, /followUp: "model"/);
assert.match(component, /followUp: "resume"/);
assert.match(component, /followUp: "thinking"/);
assert.match(component, /const nativeCommand = controlCommands\.find/);
assert.match(component, /function chooseThinking/);
assert.match(component, /function openCommandFollowUp/);
assert.match(component, /function chooseModel/);
assert.match(component, /className="command-followup"/);
assert.match(component, /New session/);
assert.match(styles, /\.command-followup \{/);
assert.match(styles, /\.command-choice-list \{/);
});
test("shows bridge-managed directory status and sessions for the opened directory", async () => {
const [component, styles] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
]);
assert.match(
component,
/invoke<\{ directories: Directory\[\] \}>\("list_directories"\)/,
);
assert.match(
component,
/invoke<\{ sessions: Session\[\] \}>\("list_sessions"/,
);
assert.match(component, /function chooseDirectory/);
assert.match(component, /function startNewSession/);
assert.match(component, /function switchSession/);
assert.match(component, /className="session-panel"/);
assert.match(component, /New session/);
assert.match(component, /state\.isStreaming/);
assert.match(styles, /\.session-panel \{/);
assert.match(styles, /\.session-list \{/);
});
test("renders todo-plugin state and compact context/token status", async () => {
const [component, styles] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
]);
assert.match(component, /function currentTodos/);
assert.match(component, /message\.toolName === "todo"/);
assert.match(component, /className="todos-pane"/);
assert.match(component, /className="workflow-main"/);
assert.match(component, /ctx \{formatTokens\(contextTokens\)\}/);
assert.match(component, /tok \{formatTokens\(stats\.tokens\.total\)\}/);
assert.match(styles, /\.workflow-main \{/);
assert.match(styles, /\.todos-pane \{/);
assert.match(styles, /\.status-metric \{/);
});
test("shows an optimistic prompt until the transcript receives it", async () => {
const [component, styles] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
]);
assert.match(component, /type PendingSubmission/);
assert.match(
component,
/const \[pendingSubmissions, setPendingSubmissions\]/,
);
assert.match(component, /setStatus\("Sending prompt to Pi…"\)/);
assert.match(component, /phase: "sending"/);
assert.match(component, /phase: "sent"/);
assert.match(component, /Sent · waiting for Pi/);
assert.match(component, /receivedMessages\.some/);
assert.match(component, /pending-message/);
assert.match(styles, /\.pending-message \{/);
assert.match(styles, /\.pending-message\.sending/);
});
test("distinguishes user and assistant messages", async () => {
const styles = await readFile("ui/src/App.css", "utf8");
assert.match(styles, /\.message\.user \{/);
assert.match(styles, /background: #192536/);
assert.match(styles, /\.message\.assistant \{/);
assert.match(styles, /background: #1a241a/);
});
test("uses a subtle focus treatment for the prompt editor", async () => {
const styles = await readFile("ui/src/App.css", "utf8");
assert.match(styles, /\.composer textarea:focus \{/);
assert.match(styles, /outline: none/);
assert.match(styles, /border-color: #6f8b56/);
});
test("submits the composer with Enter and preserves Shift+Enter for newlines", async () => {
const source = await readFile("ui/src/App.tsx", "utf8");
assert.match(source, /onKeyDown=\{\(event\) => \{/);
assert.match(source, /event\.key !== "Enter"/);
assert.match(source, /event\.shiftKey/);
assert.match(source, /event\.nativeEvent\.isComposing/);
assert.match(source, /event\.preventDefault\(\);\s*void submit\(\);/);
assert.match(source, /Enter to send · Shift\+Enter for newline/);
});
test("uses a compact workflow layout that keeps the composer and transcript stable", async () => {
const [component, styles] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
]);
assert.match(component, /className="workflow-header"/);
assert.match(component, /className="workflow"/);
assert.match(component, /className="workflow-footer"/);
assert.match(component, /className="composer-stack"/);
assert.match(
styles,
/\.workflow \{[\s\S]*grid-template-rows: minmax\(0, 1fr\) auto/,
);
assert.match(styles, /\.transcript,\s*\.settings \{[\s\S]*overflow: auto/);
assert.match(
component,
/className=\{`transcript \$\{pendingExtension \? "with-extension" : ""\} \$\{workProgress\.phase\}`\}/,
);
assert.match(
styles,
/\.transcript\.with-extension \{[\s\S]*padding-bottom: 112px/,
);
assert.match(styles, /\.transcript\.working \{/);
assert.match(styles, /conic-gradient\(/);
assert.match(styles, /transcript-border-orbit/);
assert.match(styles, /prefers-reduced-motion: reduce/);
assert.match(
styles,
/\.workflow-main \{[\s\S]*grid-template-columns: minmax\(0, 1fr\) 210px/,
);
assert.match(styles, /\.pi-controls \{[\s\S]*padding: 4px 6px/);
});
+91
View File
@@ -0,0 +1,91 @@
import assert from "node:assert/strict";
import { mkdtemp, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createConnection } from "node:net";
import test from "node:test";
import { startUnixSocketServer } from "../src/bridge/unix-server.js";
function connect(socketPath) {
return new Promise((resolve, reject) => {
const socket = createConnection(socketPath);
socket.once("connect", () => resolve(socket));
socket.once("error", reject);
});
}
function readFrame(socket) {
return new Promise((resolve, reject) => {
let buffer = "";
const onData = (chunk) => {
buffer += chunk;
const newline = buffer.indexOf("\n");
if (newline === -1) return;
socket.off("data", onData);
try {
resolve(JSON.parse(buffer.slice(0, newline)));
} catch (error) {
reject(error);
}
};
socket.on("data", onData);
socket.once("error", reject);
});
}
test("serves validated JSONL requests over an owner-only Unix socket", async () => {
const directory = await mkdtemp(join(tmpdir(), "pi-status-bridge-"));
const socketPath = join(directory, "bridge.sock");
const bridge = await startUnixSocketServer({
socketPath,
handleRequest: async (request) => ({ echoedOperation: request.op }),
});
const socket = await connect(socketPath);
try {
const socketStats = await stat(socketPath);
const mode = socketStats.mode & 0o777;
assert.equal(mode, 0o600);
const response = readFrame(socket);
socket.write(
`${JSON.stringify({ version: "v1", id: "request-2", op: "list_agents" })}\r\n`,
);
assert.deepEqual(await response, {
version: "v1",
id: "request-2",
ok: true,
result: { echoedOperation: "list_agents" },
});
} finally {
socket.destroy();
await bridge.close();
}
});
test("returns protocol errors without routing invalid input", async () => {
const directory = await mkdtemp(join(tmpdir(), "pi-status-bridge-"));
const socketPath = join(directory, "bridge.sock");
const bridge = await startUnixSocketServer({
socketPath,
handleRequest: async () => ({}),
});
const socket = await connect(socketPath);
try {
const response = readFrame(socket);
socket.write('{"version":"v1","id":"request-3","op":"unknown"}\n');
assert.deepEqual(await response, {
version: "v1",
id: "request-3",
ok: false,
error: {
code: "unsupported_operation",
message: "operation unknown is not supported",
},
});
} finally {
socket.destroy();
await bridge.close();
}
});