feat(chat): support zip uploads as virtual folders in the copilot VFS#5252
feat(chat): support zip uploads as virtual folders in the copilot VFS#5252waleedlatif1 wants to merge 7 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Archive handling is centralized in Upload I/O moves from flat Reviewed by Cursor Bugbot for commit 99f272e. Configure here. |
Greptile SummaryThis PR adds zip uploads as virtual folders in the copilot VFS. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (6): Last reviewed commit: "harden(chat): stream-cap archive downloa..." | Re-trigger Greptile |
Accept .zip chat attachments and present each archive as a virtual folder the agent lists and reads entry-by-entry. The archive is stored once; entries are extracted lazily on read, reusing the existing file-parsers and zip-bomb / zip-slip guards. No changes to the Go copilot service. - allow zip in the attachment allowlist + chat accept attribute - shared lib/uploads/archive.ts (factored from the file-manage decompress route) - split readFileRecord into a pure renderFileBuffer reused for in-zip entries - single-resolve readChatUploadPath/grepChatUploadPath dispatchers + VFS routing - inline file tree in the upload context message
9face6c to
a43b956
Compare
…ntries Address review findings on the zip-upload feature: - guard archive list/read/grep on record.size > MAX_ARCHIVE_BYTES before downloading, so an oversized zip is never buffered into memory - a not-found archive entry now returns the file-tree manifest with a note (handles a stray /content habit suffix and typos) instead of failing - de-duplicate archive entries that sanitize to the same path (./a/b vs a/b)
A name like test%2A.zip is exposed double-encoded by glob/upload-context (test%252A.zip) but canonicalUploadKey decodes the input first, so a literal %2A is indistinguishable from an encoded * and the lookup misses. Add an encoded-form fallback (encode the stored name, compare to the raw input) which recovers the row without affecting the U+202F normalization path.
|
@greptile |
|
@cursor review |
…encoding
Address round-2 review:
- filter expanded archive entries through the same micromatch matcher as the
VFS map (new matchesVfsGlob), so uploads/<zip>/data/* and /** are scoped
correctly instead of returning every entry
- build archive vfsPaths/manifest/grep labels with the per-segment encoder
(encodeUploadName) so the zip segment matches the broad glob's spelling for
literal-% names; canonicalUploadKey stays for resolution only
- upload-context now hints glob("uploads/<zip>/**") to list all entries
|
@greptile |
|
@cursor review |
…t-found A nested archive read/grep ran findArchiveEntryRawPath outside the ArchiveError catch, so an invalid/too-many-entries archive escaped to the generic handler and showed as "Upload not found" (read) or a generic grep failure, while a bare archive read already surfaced the real reason. Widen the catch so both paths report the actual ArchiveError message.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8eb5503. Configure here.
Greptile: dedup keyed on the raw sanitized path, but archive paths are exposed through VFS per-segment encoding that NFC-normalizes, so visually-identical NFC/NFD entries (e.g. café.txt) kept both while emitting one shared vfsPath — shadowing the second on read. Move dedup to listChatUploadArchiveEntries / buildArchiveManifest, keyed on the same encodeEntryPath the resolver matches on; listArchiveEntries now returns raw paths (dedup is a VFS-presentation concern).
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 74feedf. Configure here.
Final-audit hardening for the zip-upload feature: - fetchWorkspaceFileBuffer gains an optional maxBytes that flows to downloadFile, enforced on the actual byte stream. The three archive fetches pass MAX_ARCHIVE_BYTES, so a stored object larger than its client-declared record.size can no longer be buffered fully into memory (record.size stays as a cheap early-out). Fixes a comment that overclaimed download-cap parity. - grepping a bare archive (no entry) now throws a guiding WorkspaceFileGrepError pointing at an entry/manifest, instead of grepping the binary placeholder.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 99f272e. Configure here.
Summary
.zipchat attachments (previously rejected with "Unsupported file type: zip")glob("uploads/x.zip/*"), reads them withread("uploads/x.zip/<path>"), and greps inside themfile-parsersand the zip-bomb / zip-slip / symlink guards (factored out of the file-manage decompress route intolib/uploads/archive.ts)Type of Change
Testing
archive,validation,file-reader(renderFileBuffer+ binary no-fetch), and the VFS read/glob/grep routing (incl. an NFD-unicode entry round-trip)tsc,biome, andcheck:api-validationall cleanmanage/route.tsdecompress refactor is behavior-identical (shared primitives match the removed local copies exactly)Checklist