feat(web,server,core): attach files to a message from the composer (#121)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* splitImageAttachments unit tests: splitting "[attached image: …]" attachment
|
||||
* lines out of user text — scratchpad paths map to the session file endpoint,
|
||||
* http(s) URLs are quoted as-is, and unrecognized lines stay in the text.
|
||||
* splitAttachments unit tests: splitting "[attached image: …]" / "[attached file: …]"
|
||||
* attachment lines out of user text — image scratchpad paths map to the session file
|
||||
* endpoint, http(s) URLs are quoted as-is, file lines yield their paths, and unrecognized
|
||||
* lines stay in the text.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { splitImageAttachments } from "../src/lib/attachments";
|
||||
import { attachmentFileName, splitAttachments } from "../src/lib/attachments";
|
||||
|
||||
describe("splitImageAttachments", () => {
|
||||
describe("splitAttachments", () => {
|
||||
it("scratchpad path lines → session file endpoint, body kept", () => {
|
||||
const { text, images } = splitImageAttachments(
|
||||
const { text, images } = splitAttachments(
|
||||
"Look at this image\n\n[attached image: /home/u/.penguin/data/p1/agents/a1/scratchpad/session-20260712-abc/upload-1752300000000-0.png]",
|
||||
);
|
||||
expect(text).toBe("Look at this image");
|
||||
@@ -18,7 +19,7 @@ describe("splitImageAttachments", () => {
|
||||
});
|
||||
|
||||
it("http(s) URLs quoted as-is; multiple attachments in order", () => {
|
||||
const { text, images } = splitImageAttachments(
|
||||
const { text, images } = splitAttachments(
|
||||
"[attached image: https://example.com/a.png]\n[attached image: /x/scratchpad/s1/b.png]",
|
||||
);
|
||||
expect(text).toBe("");
|
||||
@@ -26,7 +27,7 @@ describe("splitImageAttachments", () => {
|
||||
});
|
||||
|
||||
it("unrecognized lines stay in the text (notice lines / paths outside this system)", () => {
|
||||
const { text, images } = splitImageAttachments(
|
||||
const { text, images } = splitAttachments(
|
||||
"hi\n\n[an attached image could not be saved and was dropped]\n[attached image: /etc/passwd]",
|
||||
);
|
||||
expect(images).toEqual([]);
|
||||
@@ -35,8 +36,50 @@ describe("splitImageAttachments", () => {
|
||||
});
|
||||
|
||||
it("no attachment lines returns unchanged", () => {
|
||||
const { text, images } = splitImageAttachments("plain message");
|
||||
const { text, images, files } = splitAttachments("plain message");
|
||||
expect(text).toBe("plain message");
|
||||
expect(images).toEqual([]);
|
||||
expect(files).toEqual([]);
|
||||
});
|
||||
|
||||
it("file lines yield their paths, body kept", () => {
|
||||
const { text, images, files } = splitAttachments(
|
||||
"Review these\n\n[attached file: /home/u/.penguin/data/p1/agents/a1/scratchpad/s1/report.pdf]\n[attached file: /home/u/.penguin/data/p1/agents/a1/scratchpad/s1/rows.csv]",
|
||||
);
|
||||
expect(text).toBe("Review these");
|
||||
expect(images).toEqual([]);
|
||||
expect(files).toEqual([
|
||||
"/home/u/.penguin/data/p1/agents/a1/scratchpad/s1/report.pdf",
|
||||
"/home/u/.penguin/data/p1/agents/a1/scratchpad/s1/rows.csv",
|
||||
]);
|
||||
});
|
||||
|
||||
it("a file path outside the scratchpad stays plain text (a typed marker must not become a notice)", () => {
|
||||
// Gated exactly like an image line: only the server writes real file attachments, and only
|
||||
// ever into a scratchpad directory. Anything else in the body is something a person typed —
|
||||
// rendering it inside the system-notice chrome would be spoofing.
|
||||
const { text, files } = splitAttachments("check\n\n[attached file: /etc/hosts]");
|
||||
expect(files).toEqual([]);
|
||||
expect(text).toBe("check\n\n[attached file: /etc/hosts]");
|
||||
const typed = splitAttachments("[attached file: reset your password at evil.example.com]");
|
||||
expect(typed.files).toEqual([]);
|
||||
expect(typed.text).toBe("[attached file: reset your password at evil.example.com]");
|
||||
});
|
||||
|
||||
it("images and files mixed in one message: each kind collected in order", () => {
|
||||
const { text, images, files } = splitAttachments(
|
||||
"both\n\n[attached image: /x/scratchpad/s1/a.png]\n[attached file: /x/scratchpad/s1/b.csv]\n[attached image: https://example.com/c.png]",
|
||||
);
|
||||
expect(text).toBe("both");
|
||||
expect(images).toEqual(["/api/sessions/s1/scratchpad/a.png", "https://example.com/c.png"]);
|
||||
expect(files).toEqual(["/x/scratchpad/s1/b.csv"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("attachmentFileName", () => {
|
||||
it("takes the last segment of POSIX and Windows paths", () => {
|
||||
expect(attachmentFileName("/x/scratchpad/s1/report.pdf")).toBe("report.pdf");
|
||||
expect(attachmentFileName("C:\\data\\scratchpad\\s1\\rows.csv")).toBe("rows.csv");
|
||||
expect(attachmentFileName("report.pdf")).toBe("report.pdf");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user