import fs from "node:fs" ;
import os from "node:os" ;
import path from "node:path" ;
import { afterEach, describe, expect, it } from "vitest" ;
import {
TRAJECTORY_RUNTIME_EVENT_MAX_BYTES,
createTrajectoryRuntimeRecorder,
resolveTrajectoryPointerOpenFlags,
resolveTrajectoryPointerFilePath,
resolveTrajectoryFilePath,
toTrajectoryToolDefinitions,
} from "./runtime.js" ;
const tempDirs: string[] = [];
function makeTempDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-trajectory-runtime-" ));
tempDirs.push(dir);
return dir;
}
afterEach(() => {
for (const dir of tempDirs.splice(0 )) {
fs.rmSync(dir, { recursive: true , force: true });
}
});
describe("trajectory runtime" , () => {
it("resolves a session-adjacent trajectory file by default" , () => {
expect(
resolveTrajectoryFilePath({
sessionFile: "/tmp/session.jsonl" ,
sessionId: "session-1" ,
}),
).toBe("/tmp/session.trajectory.jsonl" );
});
it("sanitizes session ids when resolving an override directory" , () => {
expect(
resolveTrajectoryFilePath({
env: { OPENCLAW_TRAJECTORY_DIR: "/tmp/traces" },
sessionId: "../evil/session" ,
}),
).toBe("/tmp/traces/___evil_session.jsonl" );
});
it("records sanitized runtime events by default" , () => {
const writes: string[] = [];
const recorder = createTrajectoryRuntimeRecorder({
sessionId: "session-1" ,
sessionKey: "agent:main:session-1" ,
sessionFile: "/tmp/session.jsonl" ,
provider: "openai" ,
modelId: "gpt-5.4" ,
modelApi: "responses" ,
workspaceDir: "/tmp/workspace" ,
writer: {
filePath: "/tmp/session.trajectory.jsonl" ,
write: (line) => {
writes.push(line);
},
flush: async () => undefined,
},
});
expect(recorder).not.toBeNull();
recorder?.recordEvent("context.compiled" , {
systemPrompt: "system prompt" ,
headers: [{ name: "Authorization" , value: "Bearer sk-test-secret-token" }],
command: "curl -H 'Authorization: Bearer sk-other-secret-token'" ,
tools: toTrajectoryToolDefinitions([
{ name: "z-tool" , parameters: { z: 1 } },
{ name: "a-tool" , description: "alpha" , parameters: { a: 1 } },
{ name: " " , description: "ignored" },
]),
});
expect(writes).toHaveLength(1 );
const parsed = JSON.parse(writes[0 ]);
expect(parsed.type).toBe("context.compiled" );
expect(parsed.source).toBe("runtime" );
expect(parsed.sessionId).toBe("session-1" );
expect(parsed.data.tools).toEqual([
{ name: "a-tool" , description: "alpha" , parameters: { a: 1 } },
{ name: "z-tool" , parameters: { z: 1 } },
]);
expect(JSON.stringify(parsed.data)).not.toContain("sk-test-secret-token" );
expect(JSON.stringify(parsed.data)).not.toContain("sk-other-secret-token" );
});
it("truncates events that exceed the runtime event byte limit" , () => {
const writes: string[] = [];
const recorder = createTrajectoryRuntimeRecorder({
sessionId: "session-1" ,
sessionFile: "/tmp/session.jsonl" ,
writer: {
filePath: "/tmp/session.trajectory.jsonl" ,
write: (line) => {
writes.push(line);
},
flush: async () => undefined,
},
});
recorder?.recordEvent("context.compiled" , {
prompt: "x" .repeat(TRAJECTORY_RUNTIME_EVENT_MAX_BYTES + 1 ),
});
expect(writes).toHaveLength(1 );
const parsed = JSON.parse(writes[0 ]);
expect(parsed.data).toMatchObject({
truncated: true ,
reason: "trajectory-event-size-limit" ,
});
expect(Buffer.byteLength(writes[0 ], "utf8" )).toBeLessThanOrEqual(
TRAJECTORY_RUNTIME_EVENT_MAX_BYTES + 1 ,
);
});
it("writes a session-adjacent pointer when using an override directory" , () => {
const tmpDir = makeTempDir();
const sessionFile = path.join(tmpDir, "session.jsonl" );
const trajectoryDir = path.join(tmpDir, "traces" );
const recorder = createTrajectoryRuntimeRecorder({
env: { OPENCLAW_TRAJECTORY_DIR: trajectoryDir },
sessionId: "session-1" ,
sessionFile,
writer: {
filePath: path.join(trajectoryDir, "session-1.jsonl" ),
write: () => undefined,
flush: async () => undefined,
},
});
expect(recorder).not.toBeNull();
const pointer = JSON.parse(
fs.readFileSync(resolveTrajectoryPointerFilePath(sessionFile), "utf8" ),
) as { runtimeFile?: string };
expect(pointer.runtimeFile).toBe(path.join(trajectoryDir, "session-1.jsonl" ));
});
it("keeps pointer write flags usable when O_NOFOLLOW is unavailable" , () => {
expect(
resolveTrajectoryPointerOpenFlags({
O_CREAT: 0 x01,
O_TRUNC: 0 x02,
O_WRONLY: 0 x04,
}),
).toBe(0 x07);
});
it("does not record runtime events when explicitly disabled" , () => {
const recorder = createTrajectoryRuntimeRecorder({
env: {
OPENCLAW_TRAJECTORY: "0" ,
},
sessionId: "session-1" ,
sessionKey: "agent:main:session-1" ,
sessionFile: "/tmp/session.jsonl" ,
writer: {
filePath: "/tmp/session.trajectory.jsonl" ,
write: () => undefined,
flush: async () => undefined,
},
});
expect(recorder).toBeNull();
});
});
Messung V0.5 in Prozent C=100 H=93 G=96
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland