# v0.18.0 — Local file I/O (read into the agent, write to Downloads)

Pie can now read local files into the agent and write the agent's output back to your computer. The sidepanel gets three new tools and the composer's "+" menu becomes a unified **Attach file** that takes images, text/code, and PDFs.

## New tools

- **`save_to_downloads(filename, content, save_as?)`** — write-class. Saves text to `Downloads/pie/`. The service worker builds a `data:` URL (MV3 SWs have no `URL.createObjectURL`), so no temp files. Path is sanitized (forced under `pie/`, `..`/absolute paths stripped), capped at 5 MB, and the MIME is restricted to a text allowlist. `save_as=true` forces a Save As dialog.
- **`read_local_file(uri)`** — read-class. Reads a `file://` URI (or absolute path) and returns its text — text/code directly, PDFs via the offscreen parser. Restricted to `file://` only (no fetching arbitrary http/internal URLs). Needs Chrome's per-extension **Allow access to file URLs** toggle. Images return a guidance error (use the + menu instead).
- **`request_local_file()`** — read-class, human-in-the-loop. The agent asks the panel for a file; a card appears, you pick a text/PDF file, and its contents are returned to the agent. Mirrors the CDP-onboarding round-trip with a 120 s timeout (the Cancel button shows a live countdown).

## Attach file (+ menu)

The old image-only attach is now a unified **Attach file**:

- **Images** → existing vision attachment path (still gated on a vision-capable model).
- **Text/code & PDFs** → a chip above the composer; on send, the extracted text is injected into the message wrapped in `<untrusted_local_file>`. Sent file chips now also render in the chat history bubble.
- **Drag-drop** any supported file onto the composer. **Paste** forwards clipboard file blobs too (note: macOS Finder "Copy file" usually doesn't expose a `File` to the browser, so drag-drop is the reliable path for Finder files).
- 5 MB cap; unsupported types and no-vision images surface a toast.

## Under the hood

- Picked PDFs (no fetchable URL) are parsed via a new offscreen `pdf:parse_bytes` path. Bytes are base64-encoded over `chrome.runtime.sendMessage` — a raw `ArrayBuffer` does not survive that channel (it JSON-serializes to `{}`), which would otherwise produce `invalid PDF format`.
- All local file text reaches the LLM only inside an `<untrusted_local_file>` wrapper (attributes and body escaped, dual-list registered) — never in a system-role message.

## Manifest changes

- `permissions`: adds `"downloads"`.
- No new host permission — `file://` reads rely on the user's **Allow access to file URLs** toggle, not a manifest grant.

## Known limitations

- **`save_to_downloads` may still show a save dialog** if your browser is set to "ask where to save each file before downloading" — that's a browser setting the extension can't override; `save_as=true` always shows one.
- **Images can't be returned by `read_local_file` / `request_local_file`** (a tool result can't carry a vision block). Attach images via the + menu instead.
- **Paste of a file copied in Finder** often yields no `File` in Chrome — use drag-drop for local files.
- **PDFs** inherit the v0.17 limits (no OCR/scanned, no encrypted, text-only).

## 中文摘要

Pie 现在能读本地文件喂给 agent,也能把 agent 的产出写回本地。Sidepanel 新增三个工具,composer 的 `+` 菜单从"附加图片"升级为统一的"附加文件"(图片 / 文本代码 / PDF)。

**三个新工具**:
- `save_to_downloads(filename, content, save_as?)`(write)— 写到 `Downloads/pie/`。SW 用 `data:` URL(MV3 SW 没有 `URL.createObjectURL`),路径净化、5MB 上限、MIME 文本白名单。`save_as=true` 强制弹"另存为"。
- `read_local_file(uri)`(read)— 读 `file://` 路径,文本/代码直读、PDF 走 offscreen。**只允许 file://**(防止读任意 http/内网)。需在 `chrome://extensions` 开"允许访问文件 URL"。图片返回引导错误(改用 + 菜单)。
- `request_local_file()`(read,human-in-loop)— agent 主动要文件,弹卡片让你选文本/PDF,内容回灌给 agent。120s 超时,Cancel 按钮带实时倒计时。

**附加文件(+ 菜单)**:图片走现有 vision 通道;文本/PDF 成 chip,发送时包 `<untrusted_local_file>` 注入,且历史气泡里也会显示;支持拖拽,粘贴也转发剪贴板文件 blob(注:macOS Finder"拷贝文件"通常不会暴露 File,本地文件请用拖拽)。5MB 上限,不支持类型/无 vision 图片有 toast 提示。

**底层**:picker 选的 PDF 没有可 fetch 的 URL,走新增的 offscreen `pdf:parse_bytes`;字节用 base64 过 `chrome.runtime.sendMessage`(裸 `ArrayBuffer` 会被 JSON 压成 `{}`,否则报 `invalid PDF format`)。所有本地文本只在 `<untrusted_local_file>` 包裹下进入 LLM,绝不进 system role。

**manifest**:新增 `downloads` 权限;`file://` 读取靠用户的"允许访问文件 URL"开关,不需要新 host 权限。

**已知限制**:浏览器若开了"下载前询问保存位置",`save_to_downloads` 仍会弹框(扩展挡不住);`read_local_file`/`request_local_file` 无法返回图片(tool 结果带不了 vision block);Finder 复制的文件粘贴常拿不到 File,用拖拽;PDF 沿用 v0.17 的限制(不支持扫描件/加密/OCR)。
