b7fea83ed6
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
114 lines
3.0 KiB
JavaScript
114 lines
3.0 KiB
JavaScript
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();
|
|
}
|
|
});
|