attach a PDF and the agent reads it; the microphone button is gone
Two requests, one file apart. ── The microphone ── Removed from the chat composer. The HOOK stays: `useAudioRecording` and `blobToWav` are also the file browser's DictateDialog, which is not what was asked to go. ── PDFs, by not extracting anything ── A PDF entry sat in the attach menu once with no `onSelect` at all — the menu offered it and did nothing, because nothing here could extract PDF text. It is back and real, and it still extracts nothing: the file is uploaded, left on disk, and the agent is given its PATH. Its own `Read` handles PDFs by page, better than any extraction this could have shipped, and it can go back for a section later without paying for it twice. The alternative was base64 in the message. That is megabytes in the turn, re-sent for the rest of the conversation, to produce worse text. ── What this uncovered ── `attachmentIds` has been on this websocket and in `types.ts` for a long time and NOTHING on the server read it. Images work because they travel separately as base64, so the ids were dead on the wire — and a PDF is exactly the case that needs them. They resolve to paths now, appended to the prompt as `Attached files (read from disk)`. Ids arrive in a websocket message and become a filesystem path, so they are checked rather than trusted: only the `<uuid>.<ext>` shape uploads actually generate, and only in the two directories uploads actually write to. Verified — a valid id resolves, an unknown one yields nothing, and `../../../../etc/passwd` and `; rm -rf /` yield nothing. Both upload directories are searched because the first message of a chat carries ids written to `tmp/` (no session yet) and later ones carry ids under the session. PDFs may be 25 MB against an image's 5: nothing carries them into a prompt, so the ceiling is storage rather than context. tsgo clean, frontend builds, 914 pass / 7 fail unchanged. `[open]` For a MEMBER the file lands under `data/<email>/attachments/`, and whether their Linux user can read it depends on the confinement ACLs. Untested — the owner path is what was verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import { sendOpenCodeStreaming } from '@@/channels/send-opencode';
|
||||
import * as sidecar from '@@/sidecar-registry';
|
||||
import { join } from 'path';
|
||||
import { getOwnerHomeDir, getEmailAccountsDir } from '../../../servers/data-path';
|
||||
import { attachedFilePaths } from './attached-files';
|
||||
import { getUserSettings, getEmailAccounts, getChatEventsSince, appendChatEvent, getUserById } from 'officerdb';
|
||||
import { resolveHomeDir } from '@@/user-home';
|
||||
import { claudeLoginState } from '@@/os-user-claude';
|
||||
@@ -360,10 +361,29 @@ async function handleChat(
|
||||
const sessionId = msg.sessionId || randomUUID();
|
||||
|
||||
// Prepend resume summary to the prompt if present
|
||||
const prompt = msg.resumeSummary
|
||||
const withSummary = msg.resumeSummary
|
||||
? `Here is a summary of a previous conversation to continue from:\n\n${msg.resumeSummary}\n\n---\n\nUser's new message: ${msg.prompt}`
|
||||
: msg.prompt;
|
||||
|
||||
/**
|
||||
* Attached files, named by PATH rather than carried in the message.
|
||||
*
|
||||
* `attachmentIds` has been on this wire and in `types.ts` since before today and NOTHING read it —
|
||||
* images work because they travel separately as base64, so the ids were dead. A PDF cannot travel that
|
||||
* way: it would be megabytes of base64 in every turn of the conversation, re-sent forever.
|
||||
*
|
||||
* So the file stays on disk and the agent is told where it is. Reading a document is something its own
|
||||
* tools already do better than a prompt can — page ranges, extraction, re-reading a section later
|
||||
* without paying for it again — and this is a machine where the agent runs as the person who uploaded
|
||||
* the file.
|
||||
*/
|
||||
const attached = await attachedFilePaths(userId, sessionId, msg.attachmentIds);
|
||||
const prompt = attached.length
|
||||
? `${withSummary}\n\nAttached ${attached.length === 1 ? 'file' : 'files'} (read from disk):\n${attached
|
||||
.map((path) => `- ${path}`)
|
||||
.join('\n')}`
|
||||
: withSummary;
|
||||
|
||||
// Use provided model, or fall back to user default, or the system default.
|
||||
const model = msg.model || (await getUserDefaultModel(userId)) || DEFAULT_MODEL;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user