// @effect-diagnostics nodeBuiltinImport:off - test builds fixtures via Node fs/path directly. // @effect-diagnostics preferSchemaOverJson:off - fixtures serialize plain JSON journal records. import * as NodeFSP from "node:fs/promises"; import * as NodePath from "node:path"; import { assert, describe, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as WorkflowInspectionService from "./WorkflowInspectionService.ts"; interface Layout { readonly root: string; readonly transcriptDir: string; readonly scriptsDir: string; readonly scriptPath: string; } const makeLayout = (fs: FileSystem.FileSystem, root: string) => Effect.gen(function* () { const sessionDir = NodePath.join(root, "proj", "sess"); const transcriptDir = NodePath.join(sessionDir, "subagents", "workflows", "wf_abc"); const scriptsDir = NodePath.join(sessionDir, "workflows", "scripts"); yield* fs.makeDirectory(transcriptDir, { recursive: true }); yield* fs.makeDirectory(scriptsDir, { recursive: true }); return { root, transcriptDir, scriptsDir, scriptPath: NodePath.join(scriptsDir, "spec.js"), } satisfies Layout; }); /** Build a service instance whose projects root is an isolated temp dir. */ const setup = Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-inspection-" }); const layout = yield* makeLayout(fs, root); const service = yield* WorkflowInspectionService.make({ projectsRoot: root }); return { fs, service, layout }; }); describe("WorkflowInspectionService", () => { describe("readScript", () => { it.effect("reads a contained script and reports it untruncated", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; yield* fs.writeFileString(layout.scriptPath, "export const run = () => 1;\n"); const result = yield* service.readScript({ scriptPath: layout.scriptPath }); assert.equal(result.source, "export const run = () => 1;\n"); assert.isFalse(result.truncated); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("clips scripts larger than the cap and marks them truncated", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const big = "a".repeat(512 * 1024 + 128); yield* fs.writeFileString(layout.scriptPath, big); const result = yield* service.readScript({ scriptPath: layout.scriptPath }); assert.isTrue(result.truncated); assert.equal(result.source.length, 512 * 1024); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a relative path as invalid-path", () => Effect.gen(function* () { const { service } = yield* setup; const error = yield* service .readScript({ scriptPath: "relative/spec.js" }) .pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a path outside the projects root as invalid-path", () => Effect.gen(function* () { const { fs, service } = yield* setup; const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" }); const outsideScript = NodePath.join(outside, "escape.js"); yield* fs.writeFileString(outsideScript, "export const x = 1;"); const error = yield* service.readScript({ scriptPath: outsideScript }).pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a symlink inside the root that escapes it as invalid-path", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" }); const outsideScript = NodePath.join(outside, "real.js"); yield* fs.writeFileString(outsideScript, "export const x = 1;"); const linkPath = NodePath.join(layout.scriptsDir, "link.js"); yield* Effect.promise(() => NodeFSP.symlink(outsideScript, linkPath)); const error = yield* service.readScript({ scriptPath: linkPath }).pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a non-script extension as invalid-path", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const badPath = NodePath.join(layout.scriptsDir, "spec.txt"); yield* fs.writeFileString(badPath, "not a script"); const error = yield* service.readScript({ scriptPath: badPath }).pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("reports a missing script as not-found", () => Effect.gen(function* () { const { service, layout } = yield* setup; const missing = NodePath.join(layout.scriptsDir, "missing.js"); const error = yield* service.readScript({ scriptPath: missing }).pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "not-found"); }).pipe(Effect.provide(NodeServices.layer)), ); }); describe("readJournal", () => { it.effect("summarizes started and result records with clipping", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const bigResult = "z".repeat(64 * 1024); const lines = [ JSON.stringify({ type: "started", key: "k1", agentId: "a1" }), JSON.stringify({ type: "result", key: "k1", agentId: "a1", result: { ok: true, value: 42 }, }), JSON.stringify({ type: "started", key: "k2", agentId: "a2" }), "this-is-not-json{", JSON.stringify({ type: "result", key: "k3", agentId: "a3", result: bigResult }), ]; yield* fs.writeFileString( NodePath.join(layout.transcriptDir, "journal.jsonl"), `${lines.join("\n")}\n`, ); const result = yield* service.readJournal({ transcriptDir: layout.transcriptDir }); assert.isFalse(result.truncated); assert.deepEqual( result.entries.map((entry) => entry.agentId), ["a1", "a2", "a3"], ); const a1 = result.entries[0]; assert.isTrue(a1?.hasResult); assert.equal(a1?.resultJson, JSON.stringify({ ok: true, value: 42 })); assert.isUndefined(a1?.resultTruncated); const a2 = result.entries[1]; assert.isFalse(a2?.hasResult); assert.isUndefined(a2?.resultJson); const a3 = result.entries[2]; assert.isTrue(a3?.hasResult); assert.isTrue(a3?.resultTruncated); assert.equal(a3?.resultJson?.length, 32 * 1024); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("reports a missing journal as not-found", () => Effect.gen(function* () { const { service, layout } = yield* setup; const error = yield* service .readJournal({ transcriptDir: layout.transcriptDir }) .pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "not-found"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a journal.jsonl symlink that escapes the root as invalid-path", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" }); const secret = NodePath.join(outside, "secret.jsonl"); yield* fs.writeFileString(secret, JSON.stringify({ type: "started", agentId: "leak" })); const escapeDir = NodePath.join(layout.transcriptDir, "..", "wf_journal_escape"); yield* fs.makeDirectory(escapeDir, { recursive: true }); yield* Effect.promise(() => NodeFSP.symlink(secret, NodePath.join(escapeDir, "journal.jsonl")), ); const error = yield* service.readJournal({ transcriptDir: escapeDir }).pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a transcript dir outside the root as invalid-path", () => Effect.gen(function* () { const { fs, service } = yield* setup; const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" }); yield* fs.writeFileString(NodePath.join(outside, "journal.jsonl"), ""); const error = yield* service.readJournal({ transcriptDir: outside }).pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); }); describe("readAgentTranscript", () => { it.effect("reads the full transcript and reports completion", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; yield* fs.writeFileString( NodePath.join(layout.transcriptDir, "agent-a1.jsonl"), "l0\nl1\nl2\n", ); const result = yield* service.readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "a1", }); assert.deepEqual(result.lines, ["l0", "l1", "l2"]); assert.equal(result.nextLine, 3); assert.isTrue(result.complete); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("returns the remainder from a mid-file cursor", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; yield* fs.writeFileString( NodePath.join(layout.transcriptDir, "agent-a1.jsonl"), "l0\nl1\nl2\n", ); const result = yield* service.readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "a1", afterLine: 1, }); assert.deepEqual(result.lines, ["l1", "l2"]); assert.equal(result.nextLine, 3); assert.isTrue(result.complete); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("returns empty and complete when the cursor is past end-of-file", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; yield* fs.writeFileString( NodePath.join(layout.transcriptDir, "agent-a1.jsonl"), "l0\nl1\nl2\n", ); const result = yield* service.readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "a1", afterLine: 10, }); assert.deepEqual(result.lines, []); assert.equal(result.nextLine, 10); assert.isTrue(result.complete); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects a traversal agent id as invalid-path", () => Effect.gen(function* () { const { service, layout } = yield* setup; const error = yield* service .readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "../journal" }) .pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("pages a long transcript at the line cap", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const lines = Array.from({ length: 500 }, (_unused, index) => `line-${index}`); yield* fs.writeFileString( NodePath.join(layout.transcriptDir, "agent-a1.jsonl"), lines.join("\n"), ); const first = yield* service.readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "a1", }); assert.equal(first.lines.length, 400); assert.equal(first.nextLine, 400); assert.isFalse(first.complete); const second = yield* service.readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "a1", afterLine: first.nextLine, }); assert.equal(second.lines.length, 100); assert.equal(second.nextLine, 500); assert.isTrue(second.complete); assert.equal(second.lines[99], "line-499"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("rejects an agent transcript symlink that escapes the root as invalid-path", () => Effect.gen(function* () { const { fs, service, layout } = yield* setup; const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" }); const secret = NodePath.join(outside, "secret.txt"); yield* fs.writeFileString(secret, "root:x:0:0::/root:/bin/bash"); const escapeDir = NodePath.join(layout.transcriptDir, "..", "wf_transcript_escape"); yield* fs.makeDirectory(escapeDir, { recursive: true }); yield* Effect.promise(() => NodeFSP.symlink(secret, NodePath.join(escapeDir, "agent-leak.jsonl")), ); const error = yield* service .readAgentTranscript({ transcriptDir: escapeDir, agentId: "leak" }) .pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "invalid-path"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("reports a missing transcript as not-found", () => Effect.gen(function* () { const { service, layout } = yield* setup; const error = yield* service .readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "missing" }) .pipe(Effect.flip); assert.equal(error._tag, "WorkflowInspectionError"); assert.equal(error.reason, "not-found"); }).pipe(Effect.provide(NodeServices.layer)), ); }); });