feat(web): attach PDFs, ZIPs, and other files to a turn - #8236
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
There was a problem hiding this comment.
Reviewed the new/changed Effect-adjacent code (packages/client-runtime/src/state/attachments.ts, apps/web/src/state/attachments.ts, and the web upload-queue call sites) against the service conventions. The new createAttachmentEnvironmentAtoms factory correctly mirrors createAssetEnvironmentAtoms (runtime passed in, no hidden globals, no ManagedRuntime/runPromise outside the existing atom-command boundary), and no service tags/layers were moved or redefined. One predicate convention issue below.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This change introduces a substantial production workflow for uploading, persisting, transferring, deleting, and downloading user files alongside turns. Its broad runtime surface and server-side file lifecycle warrant human review despite extensive tests and backward-compatible persistence. You can add or adjust custom eligibility rules. Learn more. |
48369dc to
264d9bb
Compare
There was a problem hiding this comment.
Reviewed the web composer/timeline attachment UI. Two interaction findings on changed lines, both about the new needs-reattach / capacity paths giving the user no working path or feedback.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding: the new file-attachment download button in MessagesTimeline renders its filename center-aligned, unlike the visually identical anchor and inert rows next to it.
Posted via Macroscope — UI Consistency
c88f3df to
33d67f9
Compare
7abf833 to
1dd3cc0
Compare
There was a problem hiding this comment.
One finding on the new send-block wording in ChatComposer. The rest of the attachment UI (file rows, paperclip trigger, stash menu file badge, timeline file rows) reuses the existing primitives and row treatments consistently, and the earlier findings on the timeline download row alignment, the marker re-pick path, and the swallowed paste are resolved.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding: the needs-reattach file row label ignores the canReattachFiles guard that the send-block reason uses, so on a server without file-attachment support (or before the capability arrives) the row tells the user to attach again while no attach control is rendered.
Posted via Macroscope — UI Consistency
Track attachment presence with a granular composer selector and hide plan follow-up actions while an image or file is staged. Keep the existing plan actions for attachment-free drafts.
Round-16 rebase onto main (5 commits, headlined by #8236 file attachments in the client). Main's upload-aware send path — capability probes, upload await/retry gating, uploaded-ref turn attachments with dataUrl fallback, draft release on success — is woven into the v2 dispatch flow, timeline user rows render file attachments as download links with the ChatView download handler, and the provider settings editor cleanup keeps the branch's environment-field rows. The codex feedback client flow stays unported, replay testkit configs gained main's environmentThemesDir, and the rpc/settings/docs unions carry both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What's Changed * feat(web): attach PDFs, ZIPs, and other files to a turn by @t3dotgg in pingdotgg/t3code#8236 * feat(web): keybinding settings as settings rows by @StiensWout in pingdotgg/t3code#8532 * feat: let an environment publish themes as a file by @ryanrhughes in pingdotgg/t3code#8569 * fix(web): clean up provider settings list and editor by @StiensWout in pingdotgg/t3code#8504 * fix(web): keep project picker popup inside the sidebar by @SunkenInTime in pingdotgg/t3code#8627 * fix(mobile): prevent header overflow and back-button artifacts by @juliusmarminge in pingdotgg/t3code#8624 ## New Contributors * @ryanrhughes made their first contribution in pingdotgg/t3code#8569 **Full Changelog**: pingdotgg/t3code@v0.0.37-nightly.20260829.1218...v0.0.37-nightly.20260829.1219 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.37-nightly.20260829.1219
Intent: Expose the generic-file backend shipped in v0.0.36 through the web and desktop composer while following the official client implementation closely enough for its upstream PR to replace this patch cleanly. Behavior: - Accept images and generic files through the paperclip button, paste, and drag-and-drop; share the eight-attachment limit and use the server-advertised generic-file limit, currently 50 MB. - Upload while the user composes, show progress/retry/reattach states, and render sent files as signed downloads with their original names. - Preserve file drafts and stashes as server upload references or explicit needs-reattach markers instead of storing generic-file bytes in localStorage. - Keep upload verification, cleanup, retry, environment moves, and unknown attachment rendering explicit. - Show file names in the current mobile feed and include attachment names in transcript bootstrap context. Design constraints: - Backport official upstream PR pingdotgg#8236 at 129f2ad, rebased onto v0.0.36, rather than maintaining a parallel client upload architecture. - Use packages/client-runtime for the shared upload lifecycle and clamp file sizes to the server capability and contract maximum. - Require direct-upload support for generic files. Do not retain the fork inline-file protocol or serialize generic-file bytes into drafts and stashes. - Upload local workspace drops like the official client; do not retain the competing desktop-only drop-to-mention behavior. - Keep images on their established compression and compatibility path. Integration: - Shared client-runtime upload lifecycle and web environment atoms. - Web composer, drafts, prompt stashes, upload queue, message timeline, signed asset downloads, sidebar counts, project cleanup, and user documentation. - Minimal current mobile feed visibility and transcript bootstrap naming remain fork-only until the official follow-up covers them. Verification: - Official attachment-focused web and client-runtime tests pass (7 files, 196 tests). - Targeted client-runtime, web, and mobile typechecks pass; the existing client-runtime Effect diagnostic remains advisory. Rebase notes: - Upstream PR pingdotgg#8236 should supersede the web/client-runtime implementation wholesale once merged. - Upstream PR pingdotgg#8237 should supersede the temporary mobile filename row with signed mobile downloads and full mobile file picking/sharing. - Recheck only the transcript-bootstrap filename summary after both PRs land. - The v0.0.36 stashShortcutLabel test fixture is the only adaptation to the rebased official PR tree.
Range f6f2be3..053affb, v0.0.36. 411 upstream files, 632 fork files, 81 touched by both, 19 conflicted. Competing implementations resolved toward the fork's superset: - Claude context usage (pingdotgg#8610). Upstream deleted `queryCurrentContextUsage` and `normalizeClaudeContextUsageApiSnapshot` outright. `getContextUsage` is the ONLY source of the compaction facts (`autocompactSource`, `autoCompactThreshold`, `isAutoCompactEnabled`) the Vitals gauge's compaction note and marker render from, and they travel on the wire contract, so the call is kept. Upstream's `latestAssistantUsage` tracking and its post-compaction guard are adopted BELOW it as the next-best fallback: `contextUsageSnapshot ?? latestAssistantSnapshot ?? ...`. Upstream removed `getContextUsage` from `ClaudeQueryRuntime`, the `SDKControlGetContextUsageResponse` import and the `Option` import outside every conflict marker; all three restored. - Provider settings editor (pingdotgg#8472). Upstream's `useEffect` + `lastPublishedEnvironmentRef` re-seed is the superset of the fork's render-phase `seededRef` (8fe3190, one of four silent-data-loss fixes). The merge had kept BOTH mechanisms; collapsed to upstream's, with the fork's half-typed-row null guard on the publish path. `environmentKey` deleted with the code it served. - Sidebar project combobox (pingdotgg#5931). Upstream replaced the menu-based project filter, whose `MenuRadioItem` carried the fork's project-actions button. Rebuilt on upstream's `ComboboxItem` with the ellipsis button grafted back (invariant 6). The dead `Menu*` import is gone. - Composer attachments (pingdotgg#8236). Upstream's `addComposerAttachments` pipeline adopted; the fork's drop split (image -> attach, other -> absolute path with upload fallback) kept and repointed at the new handler. - Mid-turn steer. Upstream's `steeringTurnState` branch rejected again (invariant 5); its side of that hunk also opened an `if` whose brace lives below the hunk. - Thread deletion drain (pingdotgg#8226). Upstream replaced `drain` with `drainThrough(sequence)`; the fork's test retargeted. Its new drain test observed `ProviderService.stopSession`, which this reactor never calls (it dispatches `thread.session.stop`), so it now counts engine dispatches and provides the two services the fork's reactor needs. Three defects the resolutions introduced, each caught by a check that is not the suite: - `shouldShowBranchMismatchBanner` was resurrected into two import lists by resolving them toward upstream. Fork commit cfdf255 replaced that function; the import had no definition. Found by the RESURRECTED sweep. - `scriptPath` ended up declared twice in CodexCollabRuntime.integration.test.ts: `both` reintroduced upstream's fixture-dir copy that the fork deliberately moved to a temp dir. Found by the RESURRECTED sweep. - `setProjectScopeMenuOpen` survived in the fork's `handleProjectActions` after upstream replaced that state with a reducer, outside every marker. Found by typecheck. Upstream regressions of fork fixes, kept fixed: - Raw NUL bytes returned to ChatComposer.tsx. A raw NUL renders as nothing in grep and aborts BSD sed mid-file; the fork had converted all six to `\0` escapes and carries a guard test for it. The guard failed, and the offending line reads as space-separated in any normal diff. Converted back to escapes. - `defaultTheme` / `defaultThemeSetAt` (pingdotgg#8569) are absent from `ServerSettingsPatch`; the fork's patch-parity guard caught it. They are owned by `t3 theme set`, which rewrites settings.json directly and which clients only read, so they are recorded as deliberately unpatchable rather than mirrored. - The fork's mobile held-messages strip used `dark:` variants that upstream's new Uniwind theme rule (pingdotgg#7327) forbids; moved to the `text-foreground-muted` semantic token rather than added to the rule's allowlist. Invariants: all re-probed against the merged tree. 1 (49 migrations, ids unique and monotonic, max 50, 34 burned), 2 (the fork-only @effect/platform-node patch survived the Expo 57 rewrite of patchedDependencies, is pinned, resolved and applied on disk; effect stays at beta.103 so no re-pin), 3, 4, 4b (4 `isSendBlocked=`), 5, 5b, 5c, 6, 7, 8, 9, 10, 11 all hold. Sweeps: resurrected 3, dropped 21, fork-loss 45, both-kept 0. Every entry named: the 3 resurrected and the fork-loss in ProviderInstanceCard / ChatComposer / Sidebar are the decisions above; the OpenCode fork-loss is 11 comment lines whose guard upstream extracted into `deleteContextIfCurrent` (the rationale is restored on the helper); pnpm-lock churn is the install. Test declarations 18,691 -> 19,081; one file lost one, upstream's own pingdotgg#8484. Gate: pnpm run verify green, exit 0 read from the captured log line. 14 blocks, 10,744 passed, 20 skipped, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Intent: Expose the generic-file backend shipped in v0.0.36 through the web and desktop composer while following the official client implementation closely enough for its upstream PR to replace this patch cleanly. Behavior: - Accept images and generic files through the paperclip button, paste, and drag-and-drop; share the eight-attachment limit and use the server-advertised generic-file limit, currently 50 MB. - Upload while the user composes, show progress/retry/reattach states, and render sent files as signed downloads with their original names. - Preserve file drafts and stashes as server upload references or explicit needs-reattach markers instead of storing generic-file bytes in localStorage. - Keep upload verification, cleanup, retry, environment moves, and unknown attachment rendering explicit. - Show file names in the current mobile feed and include attachment names in transcript bootstrap context. Design constraints: - Backport official upstream PR pingdotgg#8236 at 129f2ad, rebased onto v0.0.36, rather than maintaining a parallel client upload architecture. - Use packages/client-runtime for the shared upload lifecycle and clamp file sizes to the server capability and contract maximum. - Require direct-upload support for generic files. Do not retain the fork inline-file protocol or serialize generic-file bytes into drafts and stashes. - Upload local workspace drops like the official client; do not retain the competing desktop-only drop-to-mention behavior. - Keep images on their established compression and compatibility path. Integration: - Shared client-runtime upload lifecycle and web environment atoms. - Web composer, drafts, prompt stashes, upload queue, message timeline, signed asset downloads, sidebar counts, project cleanup, and user documentation. - Minimal current mobile feed visibility and transcript bootstrap naming remain fork-only until the official follow-up covers them. Verification: - Official attachment-focused web and client-runtime tests pass (7 files, 196 tests). - Targeted client-runtime, web, and mobile typechecks pass; the existing client-runtime Effect diagnostic remains advisory. Rebase notes: - Upstream PR pingdotgg#8236 should supersede the web/client-runtime implementation wholesale once merged. - Upstream PR pingdotgg#8237 should supersede the temporary mobile filename row with signed mobile downloads and full mobile file picking/sharing. - Recheck only the transcript-bootstrap filename summary after both PRs land. - The v0.0.36 stashShortcutLabel test fixture is the only adaptation to the rebased official PR tree.
Round-16 rebase onto main (5 commits, headlined by #8236 file attachments in the client). Main's upload-aware send path — capability probes, upload await/retry gating, uploaded-ref turn attachments with dataUrl fallback, draft release on success — is woven into the v2 dispatch flow, timeline user rows render file attachments as download links with the ChatView download handler, and the provider settings editor cleanup keeps the branch's environment-field rows. The codex feedback client flow stays unported, replay testkit configs gained main's environmentThemesDir, and the rpc/settings/docs unions carry both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit bcb855a)
PRs pingdotgg#8437 and pingdotgg#8236 crossed: one made stashShortcutLabel a required ComposerStashMenu prop, the other added a test case without it, so main fails web typecheck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit d22709f)
Round-16 rebase onto main (5 commits, headlined by #8236 file attachments in the client). Main's upload-aware send path — capability probes, upload await/retry gating, uploaded-ref turn attachments with dataUrl fallback, draft release on success — is woven into the v2 dispatch flow, timeline user rows render file attachments as download links with the ChatView download handler, and the provider settings editor cleanup keeps the branch's environment-field rows. The codex feedback client flow stays unported, replay testkit configs gained main's environmentThemesDir, and the rpc/settings/docs unions carry both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit bcb855a)
Intent: Keep transcript bootstrap context aware of generic file attachments now that upstream web and mobile clients support their full upload and rendering lifecycle. Behavior: - Count every message attachment in transcript bootstrap context, not only images. - Include up to three attachment names plus an additional-count suffix. - Preserve image/images wording when every attachment is an image; otherwise use file/files. Design constraints: - Rely entirely on upstream attachment upload, draft, stash, rendering, download, and mobile behavior. - Keep this patch limited to the text supplied to transcript bootstrap. Integration: - Web transcript bootstrap summary only. No client upload, server, contract, mobile, or storage changes. Verification: - Upstream PRs pingdotgg#8236 and pingdotgg#8237 were audited as superseding the former attachment backport except for this summary behavior. - Targeted web verification is rerun after the complete patch stack rebases. Rebase notes: - Drop this patch only when upstream transcript bootstrap includes generic attachment names rather than filtering to images.
## What's Changed * Remove Messages Glass Lab experiment by @juliusmarminge in pingdotgg/t3code#8599 * Require human review for pull requests changing product defaults by @juliusmarminge in pingdotgg/t3code#8603 * fix(codex): avoid quadratic app-server input buffering by @juliusmarminge in pingdotgg/t3code#8605 * fix(mobile): stabilize iOS header item transitions by @juliusmarminge in pingdotgg/t3code#8607 * chore(mobile): upgrade to Expo SDK 57 by @juliusmarminge in pingdotgg/t3code#8609 * fix(mobile): harden native header toolbar items by @juliusmarminge in pingdotgg/t3code#8611 * fix(server): stop querying Claude context usage after turns by @t3dotgg in pingdotgg/t3code#8610 * chore: vouch ryanrhughes by @t3dotgg in pingdotgg/t3code#8613 * feat(web): attach PDFs, ZIPs, and other files to a turn by @t3dotgg in pingdotgg/t3code#8236 * feat(web): keybinding settings as settings rows by @StiensWout in pingdotgg/t3code#8532 * feat: let an environment publish themes as a file by @ryanrhughes in pingdotgg/t3code#8569 * fix(web): clean up provider settings list and editor by @StiensWout in pingdotgg/t3code#8504 * fix(web): keep project picker popup inside the sidebar by @SunkenInTime in pingdotgg/t3code#8627 * fix(mobile): prevent header overflow and back-button artifacts by @juliusmarminge in pingdotgg/t3code#8624 * fix(server): retry automatic thread title generation by @Bil0000 in pingdotgg/t3code#8087 * fix(client-runtime): refresh edited pull request comments by @Bil0000 in pingdotgg/t3code#8094 * fix(web): four composer spacing defects by @Bil0000 in pingdotgg/t3code#8090 * perf(desktop): skip duplicate browser updates by @Bil0000 in pingdotgg/t3code#8018 * fix(web): render nested markdown images correctly by @flamboh in pingdotgg/t3code#8501 * fix(web): unify activity logs and composer banners by @juliusmarminge in pingdotgg/t3code#8693 * fix(mobile): reduce dev-client reload and Metro startup cost by @juliusmarminge in pingdotgg/t3code#8694 * revert(web): restore previous composer banners by @t3dotgg in pingdotgg/t3code#8733 * test(web): remove tests for unreachable helpers by @t3-code[bot] in pingdotgg/t3code#8738 * feat(mobile): update tool summaries and chat transitions by @juliusmarminge in pingdotgg/t3code#8793 * feat(web): play video attachments in chat by @Bil0000 in pingdotgg/t3code#8688 * fix(web,mobile): snooze menu no longer offers the same wake time twice by @vitalyiegorov in pingdotgg/t3code#8741 * fix(grok): allow model changes in existing threads by @ahmed-besic in pingdotgg/t3code#8392 * feat(mobile): pick, share, and receive files in threads by @t3dotgg in pingdotgg/t3code#8237 * fix(web): reduce title bar scroll fade height by @maria-rcks in pingdotgg/t3code#8799 * fix(windows): strip quotes from repaired PATH by @UtkarshUsername in pingdotgg/t3code#8746 * fix(web): open agent images in expanded preview by @maria-rcks in pingdotgg/t3code#8807 * fix(git): follow repository instructions in generated source control text by @maria-rcks in pingdotgg/t3code#8804 * fix(server): stop overpricing cached Claude tokens by @SunkenInTime in pingdotgg/t3code#8806 * fix(web): keep image preview above sidebar control by @maria-rcks in pingdotgg/t3code#8811 * fix(web): keep right panel synced with agent edits by @maria-rcks in pingdotgg/t3code#8803 * fix(web,mobile): render Codex citations and artifact templates by @Yash-Singh1 in pingdotgg/t3code#8584 * chore: add Windows setup script to t3.json by @UtkarshUsername in pingdotgg/t3code#8814 * fix(web): fold interim turn responses by @maria-rcks in pingdotgg/t3code#8828 * fix(web): use circle alert for failed tool calls by @maria-rcks in pingdotgg/t3code#8840 * feat(mobile): add offline iPhone voice input by @t3dotgg in pingdotgg/t3code#8614 * fix(web): prevent pull request metadata overlap by @MatthewFeroz in pingdotgg/t3code#8790 ## New Contributors * @ryanrhughes made their first contribution in pingdotgg/t3code#8569 * @ahmed-besic made their first contribution in pingdotgg/t3code#8392 * @MatthewFeroz made their first contribution in pingdotgg/t3code#8790 **Full Changelog**: pingdotgg/t3code@v0.0.36...v0.0.37 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.37
Upstream shipped its own arbitrary file attachments (pingdotgg#8235/pingdotgg#8236/pingdotgg#8237), a strict superset of the fork's, so that divergence is retired rather than re-applied. Upstream also reverted its turn-plan chips, restructured provider settings into list+editor, made the project picker a searchable combobox, and replaced the mobile CSS-variable theme hook with useUniwindTheme. Fork features preserved: thread forking, queued-prompt steering, in-chat find, answered user-input rows, machine-scoped environments, provider health-check cache, provider "Default traits", live quota meter, thread export/pinning, payload hot-update channel, and the build-only CI surface. See FORK_NOTES.md for the per-file migration notes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round-16 rebase onto main (5 commits, headlined by #8236 file attachments in the client). Main's upload-aware send path — capability probes, upload await/retry gating, uploaded-ref turn attachments with dataUrl fallback, draft release on success — is woven into the v2 dispatch flow, timeline user rows render file attachments as download links with the ChatView download handler, and the provider settings editor cleanup keeps the branch's environment-field rows. The codex feedback client flow stays unported, replay testkit configs gained main's environmentThemesDir, and the rpc/settings/docs unions carry both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Upstream tip (bcb855a, pingdotgg#8236) rewrote the third render's entries and dropped the required stashShortcutLabel prop, leaving upstream's own web typecheck red. Completed the fixture; candidate for an upstream report.
Round-16 rebase onto main (5 commits, headlined by #8236 file attachments in the client). Main's upload-aware send path — capability probes, upload await/retry gating, uploaded-ref turn attachments with dataUrl fallback, draft release on success — is woven into the v2 dispatch flow, timeline user rows render file attachments as download links with the ChatView download handler, and the provider settings editor cleanup keeps the branch's environment-field rows. The codex feedback client flow stays unported, replay testkit configs gained main's environmentThemesDir, and the rpc/settings/docs unions carry both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The file upload APIs and tolerant attachment contracts from #8235 are now on
main. This PR adds the web client for them.The composer accepts PDFs, ZIPs, and other files up to the server's advertised limit through the paperclip button or paste. Uploads start while the user types, survive draft and stash moves, and render as rows that download with the original filename. Unknown attachment types render as inert rows instead of crashing the thread.
One shared upload flow in
packages/client-runtimeowns verification, transfer, release, and size limits. Draft discard, project delete, stash, file removal, and send cleanup all use that flow. Interrupted uploads return as "Attach again" markers instead of disappearing.Release warning: clients with the old image-only attachment contract cannot decode threads that contain file attachments. The compatibility work in #8235 makes the merged server contract tolerant and keeps existing mobile code from treating files as images. Hold the web release until a compatible mobile build is available. #8237 adds mobile file support.
Original work by Theo Browne with Claude Fable 5 in the Claude Code harness. Modernized by GPT-5.6 Sol in the Codex harness.
Note
Medium Risk
Touches the turn send/upload pipeline and draft persistence; threads with file attachments require a tolerant client contract, so release coordination with older mobile builds matters.
Overview
Adds generic file attachments to the chat composer (paperclip, paste, drag) alongside images, with images and files sharing the per-turn attachment cap and server-advertised size limits.
Send and upload path:
ChatViewsnapshots images and files together, runs shared upload/capability checks before and after upload, builds optimistic file rows, and wires timeline on-demand downloads via signed asset URLs. Annotation screenshots respect the combined attachment limit so prompts do not claim crops that were not sent.Composer UX:
ChatComposerstages file rows (upload progress, retry, “attach again” after interrupted uploads), blocks send when capabilities or limits fail, and extends stash restore/save to persisted file uploads with environment checks and expiry verification.Persistence:
composerDraftStorepersists file metadata (upload ids or needs-reattach markers), enforces combined limits, and handles moves across threads/environments. Plan follow-up actions hide while any composer attachment is staged (shouldShowPlanFollowUpPrompt).Timeline: User messages render non-image attachments as download links or inert rows for unknown types. Project/sidebar draft counts include files; project delete passes thread refs when releasing draft uploads.
Reviewed by Cursor Bugbot for commit 129f2ad. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add PDF, ZIP, and generic file attachment support to chat turns
composerDraftStore,attachmentUploadQueue, and prompt stash store to persist file metadata, deduplicate attachments, manage upload lifecycles, and handle cross-environment movesMacroscope summarized 129f2ad.