refactor: hide legacy trajectory paths from runtime

This commit is contained in:
Peter Steinberger
2026-05-09 08:53:20 +01:00
parent 4da2108cab
commit be48a0f198
4 changed files with 47 additions and 48 deletions

View File

@@ -797,7 +797,9 @@ Move these into agent databases:
- Trajectory sidecars when they are not explicit export files. Done for runtime
writes: trajectory capture writes agent-database `trajectory_runtime_events`
rows and mirrors run-scoped artifacts into SQLite. Legacy sidecars remain
readable only as export/migration compatibility input.
readable only as export/migration compatibility input. Runtime trajectory
capture exposes a SQLite runtime locator; JSONL path helpers are isolated to
legacy export/debug support and are not re-exported from the runtime module.
Keep these file-backed for now:

View File

@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { resolveTrajectoryFilePath, resolveTrajectoryPointerOpenFlags } from "./paths.js";
describe("trajectory legacy path helpers", () => {
it("resolves a session-adjacent trajectory file by default", () => {
expect(
resolveTrajectoryFilePath({
transcriptLocator: "/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("keeps pointer write flags usable when O_NOFOLLOW is unavailable", () => {
expect(
resolveTrajectoryPointerOpenFlags({
O_CREAT: 0x01,
O_TRUNC: 0x02,
O_WRONLY: 0x04,
}),
).toBe(0x07);
});
});

View File

@@ -14,8 +14,6 @@ import { listTrajectoryRuntimeEvents } from "./runtime-store.sqlite.js";
import {
TRAJECTORY_RUNTIME_EVENT_MAX_BYTES,
createTrajectoryRuntimeRecorder,
resolveTrajectoryPointerOpenFlags,
resolveTrajectoryFilePath,
toTrajectoryToolDefinitions,
} from "./runtime.js";
@@ -46,10 +44,10 @@ afterEach(() => {
function expectTrajectoryRuntimeRecorder(
recorder: ReturnType<typeof createTrajectoryRuntimeRecorder>,
): TrajectoryRuntimeRecorder {
expect(recorder).toEqual(expect.objectContaining({ recordEvent: expect.any(Function) }));
if (recorder === null) {
throw new Error("Expected trajectory runtime recorder");
}
expect(typeof recorder.recordEvent).toBe("function");
return recorder;
}
@@ -86,30 +84,12 @@ function useTempStateDir(): void {
}
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 into the agent database by default", () => {
useTempStateDir();
const recorder = createTrajectoryRuntimeRecorder({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: "/tmp/session.jsonl",
transcriptLocator: "/tmp/session.jsonl",
provider: "openai",
modelId: "gpt-5.4",
modelApi: "responses",
@@ -149,7 +129,7 @@ describe("trajectory runtime", () => {
sessionId: "session-1",
sessionKey: "agent:main:session-1",
runId: "run-1",
sessionFile: "/tmp/session.jsonl",
transcriptLocator: "/tmp/session.jsonl",
provider: "openai",
modelId: "gpt-5.4",
modelApi: "responses",
@@ -176,7 +156,7 @@ describe("trajectory runtime", () => {
modelId: "gpt-5.4",
modelApi: "responses",
workspaceDir: "/tmp/workspace",
runtimeFile: "sqlite:main:trajectory:session-1",
runtimeLocator: "sqlite:main:trajectory:session-1",
eventCount: 2,
},
});
@@ -190,7 +170,7 @@ describe("trajectory runtime", () => {
useTempStateDir();
const recorder = createTrajectoryRuntimeRecorder({
sessionId: "session-1",
sessionFile: "/tmp/session.jsonl",
transcriptLocator: "/tmp/session.jsonl",
});
const runtimeRecorder = expectTrajectoryRuntimeRecorder(recorder);
@@ -214,7 +194,7 @@ describe("trajectory runtime", () => {
useTempStateDir();
const recorder = createTrajectoryRuntimeRecorder({
sessionId: "session-1",
sessionFile: "/tmp/session.jsonl",
transcriptLocator: "/tmp/session.jsonl",
maxRuntimeFileBytes: 900,
});
@@ -242,16 +222,6 @@ describe("trajectory runtime", () => {
expect(truncated?.data?.droppedEvents).toBeGreaterThan(0);
});
it("keeps pointer write flags usable when O_NOFOLLOW is unavailable", () => {
expect(
resolveTrajectoryPointerOpenFlags({
O_CREAT: 0x01,
O_TRUNC: 0x02,
O_WRONLY: 0x04,
}),
).toBe(0x07);
});
it("does not record runtime events when explicitly disabled", () => {
useTempStateDir();
const recorder = createTrajectoryRuntimeRecorder({
@@ -260,7 +230,7 @@ describe("trajectory runtime", () => {
},
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: "/tmp/session.jsonl",
transcriptLocator: "/tmp/session.jsonl",
});
expect(recorder).toBeNull();

View File

@@ -14,11 +14,6 @@ import type { TrajectoryEvent, TrajectoryToolDefinition } from "./types.js";
export {
TRAJECTORY_RUNTIME_CAPTURE_MAX_BYTES,
TRAJECTORY_RUNTIME_EVENT_MAX_BYTES,
TRAJECTORY_RUNTIME_FILE_MAX_BYTES,
resolveTrajectoryFilePath,
resolveTrajectoryPointerFilePath,
resolveTrajectoryPointerOpenFlags,
safeTrajectorySessionFileName,
} from "./paths.js";
type TrajectoryRuntimeInit = {
@@ -28,7 +23,7 @@ type TrajectoryRuntimeInit = {
runId?: string;
sessionId: string;
sessionKey?: string;
sessionFile?: string;
transcriptLocator?: string;
provider?: string;
modelId?: string;
modelApi?: string | null;
@@ -38,7 +33,7 @@ type TrajectoryRuntimeInit = {
type TrajectoryRuntimeRecorder = {
enabled: true;
filePath: string;
runtimeLocator: string;
recordEvent: (type: string, data?: Record<string, unknown>) => void;
flush: () => Promise<void>;
};
@@ -173,7 +168,7 @@ export function createTrajectoryRuntimeRecorder(
}
const agentId = resolveAgentIdFromSessionKey(params.sessionKey);
const filePath = `sqlite:${agentId}:trajectory:${params.sessionId}`;
const runtimeLocator = `sqlite:${agentId}:trajectory:${params.sessionId}`;
const maxRuntimeFileBytes = Math.max(
1,
Math.floor(params.maxRuntimeFileBytes ?? TRAJECTORY_RUNTIME_CAPTURE_MAX_BYTES),
@@ -240,7 +235,7 @@ export function createTrajectoryRuntimeRecorder(
...(params.modelId ? { modelId: params.modelId } : {}),
...(params.modelApi ? { modelApi: params.modelApi } : {}),
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
runtimeFile: filePath,
runtimeLocator,
eventCount: artifactEventCount,
bytes: Buffer.byteLength(artifactLines.join(""), "utf8"),
},
@@ -285,7 +280,7 @@ export function createTrajectoryRuntimeRecorder(
return {
enabled: true,
filePath,
runtimeLocator,
recordEvent: (type, data) => {
if (captureStopped) {
droppedEvents += 1;