Skip to content

Commit ed2747c

Browse files
committed
fix(server): notebook export uses project name; persist sandbox-relative artifacts; test isolation
1 parent 8939cc7 commit ed2747c

3 files changed

Lines changed: 47 additions & 3 deletions

File tree

server/src/agent/notebook.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,20 @@
1313
*/
1414
import { Type, type Static } from "typebox";
1515
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
16+
import { resolvePaths } from "../projects.ts";
1617
import { appendNotebookEntry, type NotebookEntry } from "./notebook-store.ts";
1718

19+
/** Strip an absolute sandbox-root prefix off a single artifact path so stored
20+
* entries match the sandbox-relative form the live SSE frame already shows
21+
* (see relativizeSandboxPaths in ./events.ts). Non-sandbox/relative paths
22+
* pass through unchanged. */
23+
function relativizeArtifact(value: string, sandboxRoot: string): string {
24+
if (!sandboxRoot) return value;
25+
if (value === sandboxRoot) return ".";
26+
if (value.startsWith(sandboxRoot + "/")) return value.slice(sandboxRoot.length + 1);
27+
return value;
28+
}
29+
1830
const CodeSchema = Type.Object({
1931
source: Type.String({ description: "The code/snippet text" }),
2032
lang: Type.Optional(Type.String({ description: "Language for highlighting" })),
@@ -78,9 +90,13 @@ export function makeNotebookTool(
7890
const title = (params.title ?? "").trim();
7991
if (!title) throw new Error("notebook entry needs a non-empty title");
8092

93+
const sandboxRoot = resolvePaths(projectId).sandbox;
94+
const artifacts = params.artifacts?.map((a) => relativizeArtifact(a, sandboxRoot));
95+
8196
const entry: NotebookEntry = {
8297
...params,
8398
title,
99+
...(artifacts !== undefined ? { artifacts } : {}),
84100
id: toolCallId,
85101
timestamp: Date.now(),
86102
role: "agent",

server/src/api/sessions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* frame sourced from Pi's per-session usage accounting.
88
*/
99
import type { FastifyInstance } from "fastify";
10-
import { activePaths } from "../projects.ts";
10+
import { activePaths, getProject } from "../projects.ts";
1111
import { corsResponseHeaders } from "../cors.ts";
1212
import { currentProjectId } from "../scope.ts";
1313
import { toClientFrame, type ClientFrame } from "../agent/events.ts";
@@ -137,7 +137,8 @@ export async function registerSessionRoutes(app: FastifyInstance): Promise<void>
137137
try {
138138
const projectId = currentProjectId();
139139
const entries = readNotebookEntries(req.params.id, projectId);
140-
const md = notebookToMarkdown(entries, { sessionId: req.params.id, projectName: projectId });
140+
const projectName = getProject(projectId)?.name ?? projectId;
141+
const md = notebookToMarkdown(entries, { sessionId: req.params.id, projectName });
141142
reply.header("Content-Type", "text/markdown; charset=utf-8");
142143
reply.header(
143144
"Content-Disposition",

server/test/notebook-tool.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { describe, it, expect } from "vitest";
1+
import fs from "node:fs";
2+
import { beforeEach, describe, it, expect } from "vitest";
3+
import { PROJECTS_ROOT } from "../src/config.ts";
4+
import { resolvePaths } from "../src/projects.ts";
25
import { makeNotebookTool } from "../src/agent/notebook.ts";
36
import { readNotebookEntries } from "../src/agent/notebook-store.ts";
47

58
const run = (tool: ReturnType<typeof makeNotebookTool>, id: string, params: unknown) =>
69
tool.execute(id, params as never, undefined as never);
710

11+
beforeEach(() => {
12+
fs.rmSync(PROJECTS_ROOT, { recursive: true, force: true });
13+
fs.mkdirSync(PROJECTS_ROOT, { recursive: true });
14+
});
15+
816
describe("notebook tool", () => {
917
it("persists a stamped entry and returns a non-blocking ack", async () => {
1018
const s = "sess-tool-a";
@@ -31,6 +39,25 @@ describe("notebook tool", () => {
3139
expect(typeof entries[0].timestamp).toBe("number");
3240
});
3341

42+
it("normalizes an absolute sandbox path in artifacts to sandbox-relative", async () => {
43+
const s = "sess-tool-artifacts";
44+
const projectId = "default";
45+
const tool = makeNotebookTool(projectId, () => s);
46+
const sandbox = resolvePaths(projectId).sandbox;
47+
await run(tool, "tc_art", {
48+
type: "method",
49+
title: "Ran PCA",
50+
artifacts: [`${sandbox}/figures/fig01.png`, "figures/already-relative.png"],
51+
});
52+
53+
const entries = readNotebookEntries(s, projectId);
54+
expect(entries).toHaveLength(1);
55+
expect(entries[0].artifacts).toEqual([
56+
"figures/fig01.png",
57+
"figures/already-relative.png",
58+
]);
59+
});
60+
3461
it("rejects an empty title", async () => {
3562
const tool = makeNotebookTool("default", () => "sess-tool-b");
3663
await expect(run(tool, "tc_x", { type: "note", title: " " })).rejects.toThrow(/title/i);

0 commit comments

Comments
 (0)