Zero-dependency Discord connector under packages/discord: binding
validation, REST and gateway clients, pi engine adapter, journal with
append-only inbox, outbox, admissions and notices, and a run.lock
ownership record {pid, start, boot} whose identity is checked three ways
and whose cleanup is gated by STOP. CLI check|run|stop|unlock via
scripts/discord.sh; offline suite scripts/test-discord.sh (28 checks,
87 node tests).
Reviewed by rev-code-02 on #1509 over nine rounds; approved exact tree
4e0feb6758c0a7e4a71483912a8e0d3e3ec95aef at comment 26170. Corrections
(1) to (12) recorded in BUILD-LOG. No listener started, no token read,
no Discord write; the live pilot follows this commit per the brief.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
85 lines
4.1 KiB
JavaScript
85 lines
4.1 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { createEngine, buildPiArgs, PI_FIXED_ARGS, assistantText } from "../src/engine-pi.mjs";
|
|
import { makeRoot } from "./helpers.mjs";
|
|
|
|
const fakePi = join(import.meta.dirname, "fake-pi.mjs");
|
|
|
|
function start(root, extra = {}) {
|
|
const logPath = join(root, "commands.jsonl");
|
|
const logs = [];
|
|
const engine = createEngine({
|
|
command: process.execPath, args: [fakePi], cwd: root, env: { ...process.env, FAKE_PI_LOG: logPath },
|
|
log: (m) => logs.push(m), ...extra,
|
|
});
|
|
engine.start();
|
|
return { engine, logs, commands: () => readFileSync(logPath, "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)) };
|
|
}
|
|
|
|
test("engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file", () => {
|
|
const args = buildPiArgs({ provider: "zai", model: "glm-5.3", thinking: "high", sessionDir: "/s", appendSystemPromptFile: "/p.md", continueSession: true });
|
|
for (const f of PI_FIXED_ARGS) assert.ok(args.includes(f), f);
|
|
assert.ok(args.includes("--no-tools") && args.includes("--offline"));
|
|
assert.deepEqual(args.slice(-9), ["--provider", "zai", "--model", "glm-5.3", "--thinking", "high", "--session-dir", "/s", "--append-system-prompt", "/p.md", "--continue"].slice(-9));
|
|
assert.ok(!buildPiArgs({ provider: "p", model: "m", thinking: "off", sessionDir: "/s", appendSystemPromptFile: "/p", continueSession: false }).includes("--continue"));
|
|
assert.equal(assistantText({ content: [{ type: "thinking", thinking: "x" }, { type: "text", text: " a " }, { type: "text", text: "b" }] }), "a b".replace(" ", " "));
|
|
});
|
|
|
|
test("engine: one prompt, one turn, text and usage come back", async () => {
|
|
const { engine } = start(makeRoot());
|
|
const r = await engine.prompt("hello");
|
|
assert.equal(r.text, "echo: hello");
|
|
assert.deepEqual(r.usage, { input: 3, output: 2 });
|
|
assert.equal(engine.busy, false);
|
|
await engine.stop();
|
|
});
|
|
|
|
test("engine: a prompt while streaming is sent as a follow-up and answered in order", async () => {
|
|
const { engine, commands } = start(makeRoot());
|
|
const first = engine.prompt("slow 150");
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
assert.equal(engine.busy, true);
|
|
const second = engine.prompt("second");
|
|
const [r1, r2] = await Promise.all([first, second]);
|
|
assert.equal(r1.text, "slow reply");
|
|
assert.equal(r2.text, "echo: second");
|
|
const prompts = commands().filter((c) => c.type === "prompt");
|
|
assert.equal(prompts[0].streamingBehavior, undefined);
|
|
assert.equal(prompts[1].streamingBehavior, "followUp");
|
|
await engine.stop();
|
|
});
|
|
|
|
test("engine: timeout sends abort and fails only that turn; the process stays", async () => {
|
|
const { engine, commands, logs } = start(makeRoot());
|
|
await assert.rejects(engine.prompt("slow 5000", { timeoutMs: 100 }), (err) => err.details.code === "timeout");
|
|
assert.ok(commands().some((c) => c.type === "abort"));
|
|
assert.ok(logs.some((l) => /timed out/.test(l)));
|
|
const r = await engine.prompt("again");
|
|
assert.equal(r.text, "echo: again");
|
|
await engine.stop();
|
|
});
|
|
|
|
test("engine: a malformed JSONL line fails the turn, not the process", async () => {
|
|
const { engine, logs } = start(makeRoot());
|
|
await assert.rejects(engine.prompt("garbage"), (err) => err.details.code === "engine-protocol");
|
|
assert.ok(logs.some((l) => /malformed/.test(l)));
|
|
const r = await engine.prompt("still here");
|
|
assert.equal(r.text, "echo: still here");
|
|
await engine.stop();
|
|
});
|
|
|
|
test("engine: a turn that ends in error rejects with the error code; process exit fails pending turns", async () => {
|
|
const root = makeRoot();
|
|
let exited = null;
|
|
const { engine } = start(root, { onExit: (e) => (exited = e) });
|
|
await assert.rejects(engine.prompt("error"), (err) => err.details.code === "engine-error" && /fake provider error/.test(err.message));
|
|
const pending = engine.prompt("slow 5000");
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
await engine.stop();
|
|
await assert.rejects(pending, (err) => err.details.code === "engine-down");
|
|
assert.ok(exited);
|
|
await assert.rejects(engine.prompt("x"), /not running/);
|
|
});
|