Add Beryl Agent and harness source

This commit is contained in:
2026-09-08 20:10:03 +08:00
parent 06d72d001c
commit 47cfbcd66a
70 changed files with 12558 additions and 0 deletions
@@ -0,0 +1,175 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { mkdtemp, mkdir, readFile, symlink } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import {
ApprovalBroker,
MemoryEventStore,
RunIdleWatchdog,
SafeWorkspace,
adsMetrics,
boundSwadsArguments,
compactSwadsText,
createTools,
validateBaseUrl,
} from "../src/index.js";
let roots: string[] = [];
afterEach(async () => {
for (const root of roots)
await import("node:fs/promises").then((f) =>
f.rm(root, { recursive: true, force: true }),
);
roots = [];
});
async function ws() {
const root = await mkdtemp(path.join(tmpdir(), "asm-"));
roots.push(root);
const sessions = path.join(root, "sessions");
await mkdir(sessions);
return SafeWorkspace.create(sessions, "s1");
}
describe("harness core", () => {
it("assigns monotonic event sequence", () => {
const s = new MemoryEventStore();
expect(s.append("x", { type: "run.started", payload: {} }).sequence).toBe(
1,
);
expect(s.append("x", { type: "run.completed", payload: {} }).sequence).toBe(
2,
);
});
it("approves and is idempotent", async () => {
const b = new ApprovalBroker(1000);
const p = b.request(
{
sessionId: "s",
runId: "r",
toolCallId: "t",
toolName: "save_report_draft",
arguments: {},
riskLevel: "medium",
},
new AbortController().signal,
);
const id = b.get(
[...(b as unknown as { all: Map<string, unknown> }).all.keys()][0]!,
)!.approvalId;
b.resolve(id, "approved");
expect((await p).decision).toBe("approved");
expect(b.resolve(id, "approved").status).toBe("approved");
});
it("times out approval", async () => {
vi.useFakeTimers();
const b = new ApprovalBroker(10);
const p = b.request(
{
sessionId: "s",
runId: "r",
toolCallId: "t",
toolName: "save_report_draft",
arguments: {},
riskLevel: "medium",
},
new AbortController().signal,
);
await vi.advanceTimersByTimeAsync(11);
expect((await p).decision).toBe("expired");
vi.useRealTimers();
});
it("uses an activity-based watchdog and pauses while waiting", async () => {
vi.useFakeTimers();
const onTimeout = vi.fn();
const watchdog = new RunIdleWatchdog(100, onTimeout);
watchdog.touch();
await vi.advanceTimersByTimeAsync(75);
watchdog.touch();
await vi.advanceTimersByTimeAsync(75);
expect(onTimeout).not.toHaveBeenCalled();
watchdog.pause();
await vi.advanceTimersByTimeAsync(500);
expect(onTimeout).not.toHaveBeenCalled();
watchdog.touch();
await vi.advanceTimersByTimeAsync(101);
expect(onTimeout).toHaveBeenCalledTimes(1);
watchdog.stop();
vi.useRealTimers();
});
it("writes only report.md", async () => {
const w = await ws();
await w.writeReport("hello", "Demo");
expect(await readFile(path.join(w.root, "report.md"), "utf8")).toContain(
"hello",
);
expect(() => w.resolve("../escape")).toThrow("PATH_ESCAPE");
});
it("renders daily and weekly reports as PNG with a three-stage funnel", async () => {
const w = await ws();
const fixture = path.resolve("../../fixtures/ads-account.json");
const imageTool = createTools(fixture, w).find(
(tool) => tool.name === "save_report_image",
)!;
const result = await imageTool.execute(
"tool-1",
{
reportType: "weekly",
accountName: "Lemonz Home",
period: "2026-08-31 2026-09-06",
timezone: "Asia/Kuala_Lumpur",
currency: "MYR",
freshness: "2026-09-07 10:39",
outcome: "成交增长,素材效率出现分化。",
kpis: [
{ label: "花费", value: "RM 100" },
{ label: "成交", value: "RM 700" },
{ label: "订单", value: "40" },
{ label: "ROI", value: "7.00" },
],
funnel: [
{ label: "商品曝光", value: "10,000" },
{ label: "商品点击", value: "500", rate: "曝光→点击 5.00%" },
{ label: "订单", value: "40", rate: "点击→购买 8.00%" },
],
funnelCaption: "同一商品指标族与归因窗口。",
highlights: ["高效商品贡献主要成交。"],
recommendations: ["复核低效素材。", "验证高效方向。", "继续观察低量样本。"],
limitations: ["平台归因不是因果增量。"],
},
new AbortController().signal,
undefined,
{} as never,
);
const png = await readFile(path.join(w.root, "swads-weekly-report.png"));
expect(png.subarray(1, 4).toString()).toBe("PNG");
expect((result.details as { funnelIncluded: boolean }).funnelIncluded).toBe(true);
});
it("rejects symlink report", async () => {
const w = await ws();
await symlink("/tmp/outside", path.join(w.root, "report.md"));
await expect(w.writeReport("x")).rejects.toThrow("SYMLINK_DENIED");
});
it("computes safe metrics", async () => {
const fixture = path.resolve("../../fixtures/ads-account.json");
const x = await adsMetrics(fixture, "demo-account", 7);
expect(x.roas).toBe(3);
expect(x.days).toBe(7);
});
it("rejects private model URLs", async () =>
await expect(validateBaseUrl("https://127.0.0.1/v1")).rejects.toThrow(
"BASE_URL_PRIVATE_DENIED",
));
it("bounds SW Ads semantic queries", () => {
const result = boundSwadsArguments("metrics_semantic_query", {
query: { limit: 1000, metrics: ["spend"] },
});
expect((result.query as { limit: number }).limit).toBe(50);
});
it("compacts large SW Ads row results", () => {
const result = JSON.parse(
compactSwadsText(
JSON.stringify({ rows: Array.from({ length: 80 }, (_, i) => [i]) }),
),
) as { rows: unknown[]; response_compaction: { original_rows: number } };
expect(result.rows).toHaveLength(50);
expect(result.response_compaction.original_rows).toBe(80);
});
});