Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293import assert from "node:assert/strict";import { mkdtempSync, readFileSync, rmSync } from "node:fs";import { tmpdir } from "node:os";import path from "node:path";import test from "node:test";import { fileURLToPath } from "node:url";import { ClaudeServer, DEFAULT_CLAUDE_MODEL } from "../src/claude-server.mjs";
const directory = path.dirname(fileURLToPath(import.meta.url));const fake = path.join(directory, "fake-claude-cli.mjs");
// A flag's value, or undefined when the flag is not there. Reaching for// `argv.indexOf(name) + 1` instead reads argv[0] on a missing flag and// compares against "--print", which fails in a way that names the wrong cause.function flagIn(argv, name) { const index = argv.indexOf(name); return index < 0 ? undefined : argv[index + 1];}
// Every spawn that recorded itself, oldest first.function launches(argvFile) { const raw = readFileSync(argvFile, "utf8"); const lines = raw.split("\n").filter(Boolean).map((line) => JSON.parse(line)); return { raw, lines, argvs: lines.map((line) => line.argv) };}
function scratch() { const root = mkdtempSync(path.join(tmpdir(), "aesthetic-claude-")); return { root, cleanup: () => rmSync(root, { recursive: true, force: true }) };}
function bridge(t, options = {}) { const engine = new ClaudeServer({ cwd: directory, command: process.execPath, args: [fake], ...options, }); t.after(() => engine.close()); return engine;}
test("drives a turn, streams text, and routes the approval to the interface", async (t) => { const { root, cleanup } = scratch(); const decisionFile = path.join(root, "decision.json"); const engine = bridge(t, { environment: { FAKE_CLAUDE_DECISION: decisionFile } });
const deltas = []; const codeDeltas = []; const items = []; const completed = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "item/agentMessage/delta") deltas.push(params.delta); if (method === "item/modelCode/delta") codeDeltas.push(params.delta); if (method === "item/started") items.push(params.item); if (method === "item/completed") items.push(params.item); if (method === "turn/completed") resolve(params.turn.status); }); }); engine.on("request", (request) => engine.respond(request.id, { decision: "accept" }));
const connection = await engine.connect(); // The bridge is ready on the handshake, before the CLI has opened a turn, // so the thread is named by the id the launch minted. assert.match(connection.thread.id, /^[0-9a-f-]{36}$/); assert.equal(connection.thread.id, engine.threadId); assert.equal(connection.model, DEFAULT_CLAUDE_MODEL);
const turn = await engine.startTurn("make a piece"); assert.equal(turn.turn.status, "inProgress"); assert.equal(await completed, "completed"); assert.equal(deltas.join(""), "Writing the piece."); assert.deepEqual(codeDeltas,['{"content":"// hi"}']);
// The interface follows the piece through fileChange items. const started = items.find((item) => item.type === "fileChange"); assert.deepEqual(started.changes, [{ path: "/tmp/piece.mjs" }]); assert.equal(items.at(-1).status, "completed");
assert.deepEqual(JSON.parse(readFileSync(decisionFile, "utf8")).behavior, "allow"); cleanup();});
test("a declined approval fails the item rather than the turn", async (t) => { const engine = bridge(t); const items = []; const completed = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "item/completed") items.push(params.item); if (method === "turn/completed") resolve(params.turn.status); }); }); engine.on("request", (request) => engine.respond(request.id, { decision: "decline" }));
await engine.connect(); await engine.startTurn("make a piece"); assert.equal(await completed, "completed"); assert.equal(items.at(-1).status, "failed");});
test("accept-for-session pins the suggested rules to the session, never to disk", async (t) => { const { root, cleanup } = scratch(); const decisionFile = path.join(root, "decision.json"); const engine = bridge(t, { environment: { FAKE_CLAUDE_DECISION: decisionFile } }); const completed = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "turn/completed") resolve(params.turn.status); }); }); engine.on("request", (request) => engine.respond(request.id, { decision: "acceptForSession" }));
await engine.connect(); await engine.startTurn("make a piece"); await completed;
const decision = JSON.parse(readFileSync(decisionFile, "utf8")); assert.equal(decision.behavior, "allow"); assert.equal(decision.updatedPermissions.length, 1); assert.equal(decision.updatedPermissions[0].destination, "session"); cleanup();});
test("the launch carries the approval contract and the workspace", async (t) => { const { root, cleanup } = scratch(); const argvFile = path.join(root, "argv.json"); const engine = bridge(t, { developerInstructions: "piece rules", environment: { FAKE_CLAUDE_ARGV: argvFile }, }); await engine.connect(); const record = launches(argvFile); assert.equal(record.lines.length, 1, `expected one spawn, got:\n${record.raw}`); const argv = record.argvs[0]; const flag = (name) => flagIn(argv, name);
assert.equal(flag("--model"), DEFAULT_CLAUDE_MODEL); // Without this the CLI has nobody to ask and denies every prompt itself. assert.equal(flag("--permission-prompt-tool"), "stdio"); assert.equal(flag("--permission-prompts"), "host"); assert.equal(flag("--permission-mode"), "manual"); // The user's own allow-lists, hooks and MCP servers stay out of a session. assert.equal(flag("--setting-sources"), ""); assert.ok(argv.includes("--strict-mcp-config")); for (const tool of ["WebFetch", "WebSearch", "Task"]) assert.ok(argv.includes(tool)); // aesel's own read-only tools are the one MCP server let through, pre-allowed. const mcp = JSON.parse(flag("--mcp-config")); assert.equal(mcp.mcpServers.ac.command, process.execPath); assert.ok(mcp.mcpServers.ac.args[0].endsWith("tools.mjs")); assert.deepEqual(mcp.mcpServers.ac.args.slice(1), ["--cwd", directory]); assert.equal(flag("--allowedTools"), "mcp__ac"); assert.equal(flag("--add-dir"), directory); assert.equal(flag("--append-system-prompt"), "piece rules"); assert.ok(argv.includes("--session-id")); assert.ok(!argv.includes("--resume")); cleanup();});
test("resuming names the thread instead of minting one", async (t) => { const { root, cleanup } = scratch(); const argvFile = path.join(root, "argv.json"); const threadId = "00000000-0000-0000-0000-000000000001"; const engine = bridge(t, { resumeThreadId: threadId, environment: { FAKE_CLAUDE_ARGV: argvFile } }); const completed = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "turn/completed") resolve(params.turn.status); }); }); engine.on("request", (request) => engine.respond(request.id, { decision: "accept" }));
await engine.connect(); const record = launches(argvFile); assert.equal(record.lines.length, 1, `expected one spawn, got:\n${record.raw}`); const argv = record.argvs[0]; assert.equal(flagIn(argv, "--resume"), threadId, `argv was:\n${record.raw}`); assert.ok(!argv.includes("--session-id"));
// Take a turn before reading the id back. `system/init` arrives with the // turn, and it is what revises the thread id — asserting straight after // connect() races that message and can pass without ever seeing it. await engine.startTurn("carry on"); assert.equal(await completed, "completed"); assert.equal(engine.threadId, threadId, "a resumed thread keeps its id"); cleanup();});
test("an interrupted turn reads as interrupted, not as a failure", async (t) => { const engine = bridge(t); const completed = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "turn/completed") resolve(params.turn); }); }); await engine.connect(); await engine.startTurn("count to a thousand"); await engine.interrupt(); const turn = await completed; assert.equal(turn.status, "interrupted"); assert.equal(turn.error, undefined);});
// A turn that the API refuses reports `interrupted`, and an interrupted turn// carries no error — so if the refusal itself is dropped, the session simply// stops for no stated reason. That is what two parallel sessions looked like.test("a refused turn says why, instead of stopping in silence", async (t) => { const engine = bridge(t, { environment: { FAKE_CLAUDE_API_ERROR: "Usage limit reached. Try again at 6pm." }, });
const errors = []; const finished = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "error") errors.push(params.error?.message); if (method === "turn/completed") resolve(params.turn); }); });
await engine.connect(); await engine.startTurn("make a piece"); const turn = await finished;
assert.equal(turn.status, "interrupted", "the turn still reports how it ended"); assert.deepEqual(errors, ["Usage limit reached. Try again at 6pm."], "and the reason reaches the interface");});
test('passes chosen effort to the Claude CLI',async t=>{ const {root,cleanup}=scratch();t.after(cleanup); const argvFile=path.join(root,'args.json'); const engine=bridge(t,{effort:'high',model:'sonnet',environment:{FAKE_CLAUDE_ARGV:argvFile}}); await engine.connect(); assert.equal(flagIn(launches(argvFile).argvs[0],'--effort'),'high'); assert.equal(flagIn(launches(argvFile).argvs[0],'--model'),'sonnet');});
test('missing unsaved Claude session recovers only with explicit aesel handoff',async t=>{ const {root,cleanup}=scratch();t.after(cleanup);const argvFile=path.join(root,'args.json'); const engine=bridge(t,{resumeThreadId:'missing',recoveryInstructions:'Retained aesel conversation',environment:{FAKE_CLAUDE_ARGV:argvFile,FAKE_CLAUDE_MISSING:'1'}}); let fatals=0;engine.on('fatal',()=>fatals++); // The CLI's empty error result for the missing session is not a turn, and // must not reach the interface as "turn failed" before anything was typed. const completed=[];engine.on('notification',n=>{if(n.method==='turn/completed')completed.push(n.params.turn);}); const c=await engine.connect();assert.notEqual(c.thread.id,'missing');assert.equal(fatals,0);assert.deepEqual(completed,[]); const args=launches(argvFile).argvs;assert.equal(args.length,2);assert.match(flagIn(args[1],'--append-system-prompt'),/Retained aesel conversation/);});// Energy is estimated from token counts, so the counts have to arrive — and// under the name of the model that actually ran. The fake CLI reports Fable// while the bridge asked for the default, which is the case a readout keyed to// the requested model would describe wrongly.test("a finished turn reports what it spent, keyed to the model that ran", async (t) => { const engine = bridge(t); const spent = []; const completed = new Promise((resolve) => { engine.on("notification", ({ method, params }) => { if (method === "turn/usage") spent.push(params); if (method === "turn/completed") resolve(params.turn.status); }); }); engine.on("request", (request) => engine.respond(request.id, { decision: "accept" }));
await engine.connect(); await engine.startTurn("make a piece"); assert.equal(await completed, "completed");
assert.equal(spent.length, 1); assert.equal(spent[0].model, "claude-fable-5-1"); assert.equal(spent[0].usage.outputTokens, 300);
const { joulesFor, readUsage } = await import("../src/energy.mjs"); assert.ok(joulesFor(readUsage(spent[0].usage), spent[0].model) > 0);});
// Pro is the user's own harness: their settings, skills, hooks and MCP servers// are the point, so the isolation flags go — and only those. The approval// contract is what keeps every prompt coming back to this terminal.test("passthrough keeps the approval contract and drops the isolation", async (t) => { const { root, cleanup } = scratch(); const argvFile = path.join(root, "argv.json"); const engine = bridge(t, { passthrough: true, environment: { FAKE_CLAUDE_ARGV: argvFile } }); await engine.connect(); const argv = launches(argvFile).argvs[0]; const flag = (name) => flagIn(argv, name);
assert.equal(flag("--permission-prompt-tool"), "stdio"); assert.equal(flag("--permission-prompts"), "host"); assert.equal(flag("--permission-mode"), "manual"); assert.equal(flag("--add-dir"), directory); assert.ok(!argv.includes("--setting-sources")); assert.ok(!argv.includes("--strict-mcp-config")); assert.ok(!argv.includes("--disallowed-tools")); for (const tool of ["WebFetch", "WebSearch", "Task"]) assert.ok(!argv.includes(tool)); cleanup();});