Repository navigation
feat: send text files from the composer to the agent - #469
Merged
Merged
Conversation
A non-image attachment that decodes as UTF-8 is sent as `files` on say and ask. The channel stores each as its own <attachment name="..."> text block after the message in the pi transcript, so every model reads it and the message's first text block stays the message. Messages, the queue, and the timeline show attached files by name; withdrawn messages return them to the composer. Binary files are refused with a clear error. Why: packages/channel/src/room.ts#function content | the first text block of a human message is always `author: text`; files follow as <attachment> blocks that input() and human() parse back, so old transcripts read unchanged Why: packages/channel/src/room.ts#function checkAttachments | images and file text share one MAX_BYTES budget under the Durable Object 2 MB row cap
3 of 5 tasks
This was referenced Oct 16, 2026
iamnbutler
added a commit
that referenced
this pull request
Oct 16, 2026
…imeline (#479) * feat(app): send PDFs as their text and open attached files from the timeline A PDF dropped, picked, or pasted into the composer is read with pdf.js in the app and sent as a TextFile, so it is stored and reaches every model exactly like a text file from #469: no protocol, storage, or migration change. Scanned PDFs (no text layer) and unreadable PDFs are refused. A sent file's timeline block now shows its line count and opens its text in the shared FileView in a dialog. Why: apps/app/src/attachments.ts#function text | pdf.js page text must be read through the stream's reader; WebKit's ReadableStream isn't async-iterable, so getTextContent throws in the desktop webview Why: apps/app/src/attachments.ts#function read | detect PDFs by MIME type only; a withdrawn PDF returns to the composer as its extracted text typed text/plain under its .pdf name Why: packages/ui/src/components/timeline/layout.ts | a file block's cache key includes its text, since the block opens that text and names can repeat across messages * lane: sync file-uploads-2 memory * lane: sync file-uploads-2 memory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #26. Related: #105, #269.
A non-image file in the composer (dropped, picked, or pasted) no longer fails with "Only images can be attached". If it decodes as UTF-8, it is sent with the message, and every model in the chat reads it.
TextFile = { name, text }, carried asfiles?onsay,ask, themessageevent, andQueuedMessage.<attachment name="...">\n…\n</attachment>. The first text block is stillauthor: text, soinput()/human()read existing transcripts unchanged and parse files back from later blocks. The size budget is shared:checkAttachmentsreplacescheckImages, and images plus file text (UTF-8 bytes) must fit under the existing 1.5 MB limit (the Durable Object row cap), with at most 10 files. Names must be one line.images.tsbecameattachments.ts. It reads text files first, then shrinks images to fit the remaining budget. A binary file is refused with "X isn't a text file; only images and text files can be attached". Sent files appear in the timeline as the existingfilechunk/FileViewblock, and by name in the queue. A withdrawn message brings its files back into the composer as chips.Attachmentgets an optionalfile, whichuseAttachmentssets, so contents without a preview can be read. The chip UI is unchanged.Product calls:
askmessage see the file text joined onto the message text. Nothing is lost.Remaining for #26: binary/non-text files (into the lane or workspace, or as documents for providers that accept them), and opening an attached file's contents from the timeline.
Smoke-tested with a source host on isolated
ACE_HOME/ACE_CONFIG_HOMEand realanthropic/claude-haiku-4-5runs:sayandaskwith files: the model answered from the attachment, a name with quotes and contents containing</attachment>round-tripped, and replayedmessageevents carriedfiles.attachments()in Bun accepted a.mdfile and refused random bytes.Built in Ace
Built with Claude Code outside Ace, in a session already running there; no Ace gap involved.