mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
a7775e4ba6
We want to display a nicely parsed and rendered log of events that happened over a span of an Argent session. Current log file is not designed to be machine-readable, so adding a JSONL-formatted log of events (gated by `tool-server-event-log`). I eventually want this 😋 https://x.com/j3nnings/status/2069072177584849192 When enabled, the tool server writes Bunyan-compatible records to `ARGENT_EVENT_LOG` or `~/.argent/tool-server-events.jsonl`. The log captures tool-server lifecycle events and registry lifecycle events, including tool invocations and others. To make messages nicer, added `interaction` object to tool definition. This allows tools to define a nicer message in log (print "Screenshot capture completed." instead of "Tool `screenshot` completed.") Adding these to the existing tools is a follow up PR matter I think.
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { Registry } from "@argent/registry";
|
|
import { attachRegistryEventLogger, createToolServerEventLog } from "../src/event-log";
|
|
|
|
function eventLogPath(): string {
|
|
return path.join(fs.mkdtempSync(path.join(os.tmpdir(), "argent-events-")), "events.jsonl");
|
|
}
|
|
|
|
function readEvents(filePath: string): Array<Record<string, unknown>> {
|
|
return fs
|
|
.readFileSync(filePath, "utf8")
|
|
.trim()
|
|
.split("\n")
|
|
.map((line) => JSON.parse(line) as Record<string, unknown>);
|
|
}
|
|
|
|
describe("createToolServerEventLog", () => {
|
|
it("records structured events as JSONL", async () => {
|
|
const filePath = eventLogPath();
|
|
const eventLog = createToolServerEventLog({ filePath });
|
|
|
|
eventLog.info({
|
|
type: "tool_server.started",
|
|
msg: "Tool server started on http://127.0.0.1:3001.",
|
|
origin: "http://127.0.0.1:3001",
|
|
host: "127.0.0.1",
|
|
port: 3001,
|
|
});
|
|
await eventLog.dispose();
|
|
|
|
expect(readEvents(filePath)).toEqual([
|
|
expect.objectContaining({
|
|
time: expect.any(String),
|
|
msg: "Tool server started on http://127.0.0.1:3001.",
|
|
type: "tool_server.started",
|
|
origin: "http://127.0.0.1:3001",
|
|
host: "127.0.0.1",
|
|
port: 3001,
|
|
name: "argent-tool-server",
|
|
hostname: expect.any(String),
|
|
pid: process.pid,
|
|
level: 30,
|
|
v: 0,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("starts a fresh event log file", async () => {
|
|
const filePath = eventLogPath();
|
|
fs.writeFileSync(filePath, "stale\n");
|
|
|
|
const eventLog = createToolServerEventLog({ filePath });
|
|
eventLog.info({ type: "tool_server.started", msg: "Tool server started." });
|
|
await eventLog.dispose();
|
|
|
|
expect(fs.readFileSync(filePath, "utf8")).not.toContain("stale");
|
|
expect(readEvents(filePath)).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("attachRegistryEventLogger", () => {
|
|
it("records registry lifecycle events into the tool-server event log", async () => {
|
|
const registry = new Registry();
|
|
const filePath = eventLogPath();
|
|
const eventLog = createToolServerEventLog({ filePath });
|
|
attachRegistryEventLogger(registry, eventLog);
|
|
|
|
registry.events.emit("toolInvoked", "screenshot", "call-1", "Capturing screenshot.");
|
|
registry.events.emit("toolCompleted", "screenshot", "call-1", 12.34, "Captured screenshot.");
|
|
await eventLog.dispose();
|
|
|
|
expect(readEvents(filePath)).toEqual([
|
|
expect.objectContaining({
|
|
time: expect.any(String),
|
|
msg: "Capturing screenshot.",
|
|
level: 30,
|
|
type: "tool.invoked",
|
|
toolId: "screenshot",
|
|
toolInvocationId: "call-1",
|
|
}),
|
|
expect.objectContaining({
|
|
time: expect.any(String),
|
|
msg: "Captured screenshot.",
|
|
level: 30,
|
|
type: "tool.completed",
|
|
toolId: "screenshot",
|
|
toolInvocationId: "call-1",
|
|
durationMs: 12.34,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("serializes failed tool errors with nested causes", async () => {
|
|
const registry = new Registry();
|
|
const filePath = eventLogPath();
|
|
const eventLog = createToolServerEventLog({ filePath });
|
|
attachRegistryEventLogger(registry, eventLog);
|
|
|
|
const cause = new Error("socket closed");
|
|
registry.events.emit(
|
|
"toolFailed",
|
|
"screenshot",
|
|
"call-2",
|
|
new Error("evaluate failed", { cause }),
|
|
undefined,
|
|
"Failed to capture screenshot."
|
|
);
|
|
await eventLog.dispose();
|
|
|
|
const [event] = readEvents(filePath);
|
|
expect(event).toMatchObject({
|
|
msg: "Failed to capture screenshot.",
|
|
level: 50,
|
|
type: "tool.failed",
|
|
toolId: "screenshot",
|
|
toolInvocationId: "call-2",
|
|
failureSignal: {
|
|
error_code: "REGISTRY_TOOL_FAILURE_UNCLASSIFIED",
|
|
failure_stage: "registry_tool_failed_event",
|
|
failure_area: "registry",
|
|
error_kind: "unknown",
|
|
},
|
|
});
|
|
expect(JSON.stringify(event)).not.toContain("evaluate failed");
|
|
expect(JSON.stringify(event)).not.toContain("socket closed");
|
|
});
|
|
});
|