Rename attachment folder from .pi-web/paste to .pi-web/attachments

Attachments can be pasted, dropped, or uploaded, so 'paste' was too
narrow. Rename the default folder and filename prefix accordingly and
update the changeset reference.
This commit is contained in:
Federico Jaramillo Martinez
2026-06-17 01:02:33 +02:00
parent 81dba2abc9
commit 32d11de38c
6 changed files with 10 additions and 10 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
"@jmfederico/pi-web": minor
---
Add image attachments to the chat composer. You can now paste (Ctrl/Cmd+V), drag-and-drop, or use the new Attach button to add PNG, JPEG, GIF, and WebP images to a message, with thumbnail previews and multi-image support. Attachments are delivered to the session using pi's native image format (images are auto-resized to pi's inline limits for full compatibility), and image content now renders inline in the transcript. A per-message delivery toggle also lets you instead save attachments into the workspace `.pi-web/paste` folder and reference them so the agent reads them with its own tools. The accepted HTTP upload size is now configurable via `PI_WEB_MAX_UPLOAD_BYTES` or the `maxUploadBytes` config value.
Add image attachments to the chat composer. You can now paste (Ctrl/Cmd+V), drag-and-drop, or use the new Attach button to add PNG, JPEG, GIF, and WebP images to a message, with thumbnail previews and multi-image support. Attachments are delivered to the session using pi's native image format (images are auto-resized to pi's inline limits for full compatibility), and image content now renders inline in the transcript. A per-message delivery toggle also lets you instead save attachments into the workspace `.pi-web/attachments` folder and reference them so the agent reads them with its own tools. The accepted HTTP upload size is now configurable via `PI_WEB_MAX_UPLOAD_BYTES` or the `maxUploadBytes` config value.
+1 -1
View File
@@ -141,7 +141,7 @@ export class PromptEditor extends LitElement {
<label class="attachment-delivery" title="How attachments are delivered to the agent">
<select .value=${this.attachmentDelivery} @change=${(event: Event) => { this.changeDelivery(event); }}>
<option value="inline">Attach to message</option>
<option value="folder">Save to .pi-web/paste</option>
<option value="folder">Save to .pi-web/attachments</option>
</select>
</label>
` : null}
@@ -269,7 +269,7 @@ describe("SessionController", () => {
const attachments: PromptAttachment[] = [{ kind: "image", mimeType: "image/png", data: "QUJD", name: "shot.png" }];
const api: typeof defaultApi = {
...defaultApi,
saveAttachments: (_session, sent) => { savedCalledWith = sent; return Promise.resolve([{ path: ".pi-web/paste/shot.png", mimeType: "image/png", size: 3 }]); },
saveAttachments: (_session, sent) => { savedCalledWith = sent; return Promise.resolve([{ path: ".pi-web/attachments/shot.png", mimeType: "image/png", size: 3 }]); },
prompt: (_session, text, _behavior, _machineId, sentAttachments) => { promptText = text; promptAttachments = sentAttachments; return Promise.resolve({ accepted: true }); },
};
const controller = new SessionController(
@@ -283,7 +283,7 @@ describe("SessionController", () => {
await controller.send("check this", undefined, attachments, "folder");
expect(savedCalledWith).toEqual(attachments);
expect(promptText).toBe("check this\n\[email protected]/paste/shot.png");
expect(promptText).toBe("check this\n\[email protected]/attachments/shot.png");
expect(promptAttachments).toBeUndefined();
expect(state.sendingPrompts).toEqual({});
});
@@ -30,12 +30,12 @@ describe("saveAttachmentsToWorkspace", () => {
);
expect(saved).toHaveLength(2);
expect(saved[0]?.path.startsWith(`${DEFAULT_ATTACHMENT_FOLDER}/paste-`)).toBe(true);
expect(saved[0]?.path.startsWith(`${DEFAULT_ATTACHMENT_FOLDER}/attachment-`)).toBe(true);
expect(saved[0]?.path.endsWith(".png")).toBe(true);
expect(saved[1]?.path.endsWith(".webp")).toBe(true);
expect(saved[0]?.size).toBe(pngBytes.byteLength);
const folderEntries = await readdir(join(workspace, ".pi-web", "paste"));
const folderEntries = await readdir(join(workspace, ".pi-web", "attachments"));
expect(folderEntries).toHaveLength(2);
const firstPath = saved[0]?.path ?? "";
+3 -3
View File
@@ -10,7 +10,7 @@ import { resolveParentInsideWorkspace } from "../workspaces/pathSafety.js";
* Default workspace-relative folder used when saving pasted/dropped
* attachments for the agent to read with its own tools.
*/
export const DEFAULT_ATTACHMENT_FOLDER = ".pi-web/paste";
export const DEFAULT_ATTACHMENT_FOLDER = ".pi-web/attachments";
export interface InlineImage {
image: ImageContent;
@@ -42,7 +42,7 @@ export async function attachmentsToInlineImages(attachments: PromptAttachment[])
}
export interface SaveAttachmentsOptions {
/** Workspace-relative folder to write into. Defaults to `.pi-web/paste`. */
/** Workspace-relative folder to write into. Defaults to `.pi-web/attachments`. */
folder?: string;
/** Clock injection for deterministic tests. */
now?: () => Date;
@@ -66,7 +66,7 @@ export async function saveAttachmentsToWorkspace(
const saved: SavedPromptAttachment[] = [];
for (const [index, attachment] of attachments.entries()) {
const bytes = Buffer.from(attachment.data, "base64");
const filename = `paste-${stamp}-${String(index + 1)}.${extensionForImageMimeType(attachment.mimeType)}`;
const filename = `attachment-${stamp}-${String(index + 1)}.${extensionForImageMimeType(attachment.mimeType)}`;
const relativePath = `${folder}/${filename}`;
await writeFile(join(folderTarget, filename), bytes);
saved.push({ path: relativePath, mimeType: attachment.mimeType, size: bytes.byteLength });
+1 -1
View File
@@ -175,7 +175,7 @@ class CapturingRouteSessionService extends PiSessionService {
override saveAttachments(_lookup: string | PiSessionRef, attachments: unknown, folder?: string) {
const list = Array.isArray(attachments) ? attachments : [];
return Promise.resolve(list.map((attachment: { mimeType: string; data: string; name?: string }) => ({
path: `${folder ?? ".pi-web/paste"}/${attachment.name ?? "file.png"}`,
path: `${folder ?? ".pi-web/attachments"}/${attachment.name ?? "file.png"}`,
mimeType: attachment.mimeType,
size: Buffer.from(attachment.data, "base64").byteLength,
})));