Files

231 lines
14 KiB
JavaScript

// Row 65 (REQ-PLUG-2, Q156): the two harness modules load through the loader with every export
// the kind names, answer the questions core used to answer by name, and no core source names a
// harness outside the exceptions the brief lists.
import test from "node:test";
import assert from "node:assert/strict";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";
import { createLoader } from "../src/loader.mjs";
import { KINDS } from "../src/kinds.mjs";
const REPO = resolve(fileURLToPath(new URL("../../../", import.meta.url)));
async function load(name) {
const logged = [];
const loader = createLoader({ log: entry => logged.push(entry) });
const result = await loader.load({ harness: [name] });
assert.deepEqual(result.refused, [], name);
assert.deepEqual(logged, [], name);
const [module] = loader.modules("harness");
return module;
}
function scratch(t) {
const root = mkdtempSync(join(tmpdir(), "mosaic-harness-plugin-"));
t.after(() => rmSync(root, { recursive: true, force: true }));
return root;
}
test("row 65 both harness modules load through the loader with every export the kind names", async () => {
for (const name of ["pi", "claude-code"]) {
const module = await load(name);
for (const key of KINDS.harness.exports) assert.equal(typeof module[key], "function", `${name} ${key}`);
assert.equal(module.interfaceVersion, 1);
}
});
test("row 65 each module answers for its own harness: adapter, levels, account, web, turn end and session files", async () => {
const pi = await load("pi"), claude = await load("claude-code");
assert.equal(pi.adapter(), join(REPO, "adapters", "pi", "adapter.sh"));
assert.equal(claude.adapter(), join(REPO, "adapters", "claude", "adapter.sh"));
// pi 0.85.1 --thinking and claude 2.1.302 --effort, as probed (BUILD.md); claude has no off or minimal.
assert.deepEqual([...pi.levels()], ["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
assert.deepEqual([...claude.levels()], ["low", "medium", "high", "xhigh", "max"]);
assert.deepEqual([pi.countsAccount(), claude.countsAccount()], [false, true]);
assert.deepEqual([pi.builtinWeb(), claude.builtinWeb()], [false, true]);
assert.deepEqual([pi.turnEnd(), claude.turnEnd()], ["marker", "exit"]);
assert.deepEqual([pi.outputPolicy({ proxy: true }), pi.outputPolicy({ proxy: false }), claude.outputPolicy({ proxy: true }), claude.outputPolicy({ proxy: false })], ["non-newline-stdout", "stdout", "adapter", "adapter"]);
assert.deepEqual([pi.typedName("list_tasks"), claude.typedName("list_tasks")], ["list_tasks", "mcp__mosaic__list_tasks"]);
assert.equal(pi.gateTimeout(), null);
assert.deepEqual(claude.gateTimeout(), { n: 10, k: 2, hook: 20 });
assert.equal(pi.transcript({ runDir: "/r" }), "/r/session");
assert.equal(claude.transcript({ runDir: "/r" }), "/r/home/.claude/projects");
assert.equal(pi.transcript({ runDir: "/r", store: { session: "/s", projects: "/p" } }), "/s");
assert.equal(claude.transcript({ runDir: "/r", store: { session: "/s", projects: "/p" } }), "/p");
assert.equal(pi.reliesOn().length, 5);
assert.equal(claude.reliesOn().length, 5);
});
test("row 65 claude-code preflight: a missing credential, then a missing or relative binary, each refuse with their code", async t => {
const claude = await load("claude-code");
const root = scratch(t);
const bin = join(root, "claude");
writeFileSync(bin, "#!/bin/sh\necho 2.1.302\n");
chmodSync(bin, 0o755);
const token = { CLAUDE_CODE_OAUTH_TOKEN: "fixture" };
assert.equal(claude.preflight({ env: { MOSAIC_CLAUDE_BIN: bin } }).code, "no-session-credential");
assert.equal(claude.preflight({ env: { ...token, PATH: join(root, "none") } }).code, "harness-binary-missing");
assert.equal(claude.preflight({ env: { ...token, MOSAIC_CLAUDE_BIN: "claude" } }).code, "harness-binary-missing");
assert.equal(claude.preflight({ env: { ...token, MOSAIC_CLAUDE_BIN: bin } }), null);
assert.equal(claude.preflight({ env: { ...token, PATH: root } }), null);
assert.equal(claude.version({ env: { MOSAIC_CLAUDE_BIN: bin, PATH: process.env.PATH } }), "2.1.302");
// The pinned binary outside /usr is mounted where the adapter looks for it, and the unslop hook's
// files where the hook commands name them (row 66).
const unslop = ["claude-hook.js", "unslop-check.js", "lists.json"].map(f => [join(REPO, "tools", "unslop-hook", f), undefined]);
assert.deepEqual(claude.mounts({ env: { MOSAIC_CLAUDE_BIN: bin } }).map(m => [m.src, m.dest]), [[bin, bin], ...unslop, [join(REPO, "adapters/claude/stream.mjs"), undefined]]);
assert.deepEqual(claude.mounts({ env: {} }).map(m => [m.src, m.dest]), [...unslop, [join(REPO, "adapters/claude/stream.mjs"), undefined]]);
});
// Row 68's claudeVersion precedence, moved here with the version (row 65): the manifest records
// what the binary sessions run answers.
test("row 65 claude-code version asks MOSAIC_CLAUDE_BIN when set, else claude on PATH, else null", async t => {
const claude = await load("claude-code");
const dir = scratch(t);
mkdirSync(join(dir, "bin"));
const stand = (p, v) => {
writeFileSync(p, `#!/bin/sh\necho "${v} (Claude Code)"\n`);
chmodSync(p, 0o755);
return p;
};
stand(join(dir, "bin", "claude"), "1.0.0");
const pinned = stand(join(dir, "pinned"), "9.9.9");
const PATH = join(dir, "bin");
assert.equal(claude.version({ env: { PATH, MOSAIC_CLAUDE_BIN: pinned } }), "9.9.9 (Claude Code)");
assert.equal(claude.version({ env: { PATH } }), "1.0.0 (Claude Code)");
assert.equal(claude.version({ env: { PATH: join(dir, "none") } }), null);
});
test("row 65 pi preflight refuses missing credentials, and its version is the package pin", async () => {
const pi = await load("pi");
assert.equal(pi.preflight({ env: {} }).code, "no-session-credential");
const pkg = JSON.parse(readFileSync(join(REPO, "package.json"), "utf8"));
assert.equal(pi.version({ env: {} }), pkg.dependencies?.["@earendil-works/pi-coding-agent"] ?? pkg.devDependencies?.["@earendil-works/pi-coding-agent"]);
// Row 66: the unslop extension's files, bound read-only at their own paths.
assert.deepEqual(pi.mounts({ env: {} }).map(m => m.dest ?? m.src), [join(REPO, "packages/plugins/harness/pi/model-stream.mjs"), join(REPO, "node_modules"), ...["extension.ts", "unslop-check.js", "lists.json"].map(f => join(REPO, "tools", "unslop-hook", f))]);
});
test("row 65 env passes the thinking level to both adapters, and claude-code's bundle writes its hook and MCP files", async t => {
const pi = await load("pi"), claude = await load("claude-code");
const session = { provider: "fixture", model: "m", thinking: "high", toolSocket: "/run/tools.sock" };
const piEnv = pi.env({ policy: { tools: ["read"] }, tools: [{ name: "list_tasks" }], session });
assert.equal(piEnv.PI_OFFLINE, "1", "pi avoids startup network operations");
assert.deepEqual([piEnv.MOSAIC_THINKING, piEnv.PI_MODEL, piEnv.MOSAIC_TOOLS], ["high", "m", "read,list_tasks"]);
assert.equal(piEnv.MOSAIC_MODEL_ACTIVITY_SOURCE, undefined, "Pi retains stdout fallback for paths without streaming activity");
// Row 66: pi loads the unslop check as its second extension.
assert.deepEqual(piEnv.MOSAIC_EXTENSIONS.split(","), [join(REPO, "packages", "harness", "src", "pi-extension.mjs"), join(REPO, "tools", "unslop-hook", "extension.ts")]);
const dir = scratch(t);
const written = [];
const write = (name, text) => { writeFileSync(join(dir, name), text); written.push(name); return [name, "0".repeat(64)]; };
const quote = path => { assert.ok(!path.includes("'")); return `'${path}'`; };
const out = claude.bundle({ dir, files: { policy: join(dir, "policy.json"), tools: join(dir, "tools.json") }, session, write, quote });
assert.deepEqual(written, ["settings.json", "mcp.json"]);
const settings = JSON.parse(readFileSync(join(dir, "settings.json"), "utf8"));
assert.match(settings.hooks.PreToolUse[0].hooks[0].command, /^timeout -k 2 10 .* \|\| exit 2$/);
assert.equal(settings.hooks.PreToolUse[0].hooks[0].timeout, 20);
// Row 66: the unslop hooks can't block: each command ends with `; exit 0`.
assert.match(settings.hooks.Stop[0].hooks[0].command, /^timeout -k 2 10 .*claude-hook\.js' stop; exit 0$/);
assert.match(settings.hooks.UserPromptSubmit[0].hooks[0].command, /^timeout -k 2 10 .*claude-hook\.js' prompt; exit 0$/);
assert.equal(JSON.parse(readFileSync(join(dir, "mcp.json"), "utf8")).mcpServers.mosaic.args.at(-1), "/run/tools.sock");
const claudeEnv = claude.env({ files: out.files, policy: { tools: ["read"], typed: [] }, session });
assert.equal(claudeEnv.MOSAIC_MODEL_ACTIVITY_SOURCE, "adapter");
assert.deepEqual([claudeEnv.MOSAIC_THINKING, claudeEnv.MOSAIC_CLAUDE_SETTINGS, claudeEnv.MOSAIC_CLAUDE_MCP_CONFIG], ["high", out.files.settings, out.files.mcp]);
});
// The level a route names reaches the harness binary as its flag: each module's env() feeds the
// real adapter, and a stand-in pi or claude records the argv it was given (dewey B3).
test("row 65 a route's thinking reaches pi as --thinking and claude as --effort, through each module's env and adapter", async t => {
const pi = await load("pi"), claude = await load("claude-code");
const dir = scratch(t);
mkdirSync(join(dir, "bin"));
for (const name of ["pi", "claude"]) {
writeFileSync(join(dir, "bin", name), '#!/bin/sh\nprintf "%s\\n" "$@" > "$ARGV_OUT"\necho \'{"type":"result","subtype":"success","result":"ok"}\'\n');
chmodSync(join(dir, "bin", name), 0o755);
}
for (const f of ["prompt.md", "settings.json", "mcp.json"]) writeFileSync(join(dir, f), "{}");
const run = (adapter, moduleEnv) => {
const out = join(dir, "argv");
rmSync(out, { force: true });
const r = spawnSync("/bin/sh", [join(REPO, "adapters", adapter, "adapter.sh")], {
encoding: "utf8",
env: {
PATH: `${join(dir, "bin")}:/usr/bin:/bin`, ARGV_OUT: out, MOSAIC_SYSTEM_PROMPT_FILE: join(dir, "prompt.md"),
MOSAIC_REQUEST: "x", MOSAIC_WORKSPACE: join(dir, "ws"), MOSAIC_SESSION_DIR: join(dir, "session"), MOSAIC_MODEL: "m",
...moduleEnv,
},
});
assert.equal(r.status, 0, r.stderr);
return readFileSync(out, "utf8").split("\n");
};
const flag = (argv, name) => {
assert.equal(argv.filter(a => a === name).length, 1, argv.join(" "));
return argv[argv.indexOf(name) + 1];
};
for (const thinking of ["xhigh", "low"]) {
const session = { provider: "fixture", model: "m", thinking, toolSocket: "/run/tools.sock" };
assert.equal(flag(run("pi", pi.env({ policy: { tools: ["read"] }, tools: [], session })), "--thinking"), thinking);
const files = { settings: join(dir, "settings.json"), mcp: join(dir, "mcp.json") };
assert.equal(flag(run("claude", claude.env({ files, policy: { tools: ["read"], typed: [] }, session })), "--effort"), thinking);
}
});
// A harness name in core: a quoted "pi" or "claude-code", or either inside a regex alternation.
const NAMED = /["'`](?:pi|claude-code)["'`]|[(|](?:pi|claude-code)[|)]/;
// The brief's Out of scope list: the tool-policy gate, the human-proof check, the engines that
// start pi directly, and the process scan. Each must still name a harness, so the list stays true.
const EXCEPT = [
"packages/harness/src/gate.mjs",
"packages/harness/src/claude-gate.mjs",
"packages/harness/src/pi-extension.mjs",
"packages/bus/src/human.mjs",
"packages/conversation/src/",
"packages/discord/src/",
"packages/control-board/src/scan.mjs",
];
function sources(dir, out = []) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) { if (entry.name !== "node_modules" && entry.name !== "third_party") sources(path, out); }
else if (/\.(?:mjs|js)$/.test(entry.name)) out.push(path);
}
return out;
}
test("row 65 no core source outside packages/plugins names a harness, apart from the listed exceptions", () => {
const named = [];
const hit = new Set();
for (const pkg of readdirSync(join(REPO, "packages"))) {
if (pkg === "plugins") continue;
let files;
try { files = sources(join(REPO, "packages", pkg, "src")); } catch (e) { if (e.code === "ENOENT") continue; throw e; }
for (const file of files) {
const rel = relative(REPO, file);
readFileSync(file, "utf8").split("\n").forEach((line, i) => {
if (!NAMED.test(line)) return;
const exception = EXCEPT.find(e => (e.endsWith("/") ? rel.startsWith(e) : rel === e));
if (exception) hit.add(exception);
else named.push(`${rel}:${i + 1}: ${line.trim()}`);
});
}
}
assert.deepEqual(named, []);
assert.deepEqual([...hit].sort(), [...EXCEPT].sort(), "every exception still names a harness");
// human.mjs keeps one: the human-proof regex that refuses any agent harness binary.
const human = readFileSync(join(REPO, "packages", "bus", "src", "human.mjs"), "utf8").split("\n").filter(line => NAMED.test(line));
assert.deepEqual(human.map(line => line.trim()), ["/^(pi|claude|claude-code|codex)(?:\\.js)?$/.test(command) ||"]);
});
test("row 65 the grep's pattern catches a quoted name and a regex alternation, and not a word in prose", () => {
for (const line of ['if (harness === "pi")', "x = 'claude-code';", "/^(pi|codex)$/", "/^(codex|claude-code)$/", "if (harness !== 'pi')", "['pi', 'claude-code'].includes(harness)"]) assert.ok(NAMED.test(line), line);
for (const line of ["// pi runs the bundle", "const piModel = 1;", "pipeline"]) assert.ok(!NAMED.test(line), line);
});
test('pi materializes module-declared compatibility without a provider-specific switch', async t => {
const pi=await load('pi'),root=scratch(t),dir=join(root,'bundle');mkdirSync(dir);
const entry={provider:'fixture',model:'fixture-id'},compat={thinkingFormat:'openai',zaiToolStream:false};
const out=pi.bundle({dir,models:{fixture:entry},proxy:{baseUrl:'http://127.0.0.1:9999',key:'fixture-secret'},transports:{fixture:{piProvider:'fixture',endpoint:'v1',api:'openai-completions',compat}}});
assert.deepEqual(JSON.parse(readFileSync(out.files.proxyModels,'utf8')).providers.fixture.compat,compat);
});