feat: accept document uploads via unified FileHandler branch - #193
timurvafin wants to merge 1 commit into
Conversation
Add a new `"document"` type to FileHandler for binary document formats. Matching files are persisted to `<current_dir>/.uploads/<timestamp>-<name>` and Claude receives a generic prompt with the absolute path, letting the agent pick whichever tool fits the format (Read for PDF/text; Bash with pandoc, python-docx, openpyxl, unzip, etc. for Office/OpenDocument). Covered formats (added to ALLOWED_EXTENSIONS): - Binary (document branch): .pdf, .docx, .xlsx, .pptx, .odt, .ods, .odp, .rtf - UTF-8 text-compatible (existing text branch): .csv, .tsv, .log, .ics, .eml Both agentic (`agentic_document`) and classic (`handle_document`) handlers pass `current_dir` into `FileHandler.handle_document_upload`, so classic mode gains document support for free. The `.uploads/` directory sits inside APPROVED_DIRECTORY, so Claude's tools can reach it without tool_monitor boundary issues. The file survives the upload call so follow-up turns can still read or reference it. Supersedes overwirehq#192 (narrow PDF-only patch that branched directly in `agentic_document`). This PR routes the same capability through the proper abstraction and extends it to the whole Office family in one step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
220496e to
3f32a51
Compare
RichardAtCT
left a comment
There was a problem hiding this comment.
This is the right shape, and it supersedes #199. Thanks. Two things first.
1. Sanitise the filename before you save it.
In _process_document_file you build the target from document.file_name:
original_name = document.file_name or file_path.name
safe_name = f"{timestamp}-{original_name}"
target = uploads_dir / safe_name
file_name is client metadata, so it can contain ../. pathlib splits on /, so a crafted name writes outside .uploads/ and can overwrite an existing file inside the approved directory.
Fix is one line:
original_name = Path(document.file_name or file_path.name).name
Same applies to _download_file at src/bot/features/file_handler.py:165, which has the same pattern into temp_dir. That one is pre-existing, not yours — fix it here if you are happy to, otherwise I will.
2. Rebase.
.gitignore conflicts with main now. My fault: I dropped a stray hunk from #206 and left a trailing-newline change. Rebasing should resolve it.
Only an allowed user can upload, so neither is urgent. But the path fix is cheap and worth having.
Summary
Accept a family of document uploads (PDF, Office, OpenDocument, and common text-table formats) by adding a new
"document"type toFileHandler. Binary documents are persisted to<current_dir>/.uploads/<timestamp>-<name>and Claude receives a generic prompt with the absolute path — the agent picks whichever tool fits the format. Both agentic (agentic_document) and classic (handle_document) paths go through the same FileHandler method.Design
Single document branch, one prompt
The
_process_document_filemethod is format-agnostic. The prompt just tells Claude where the file is:Claude infers the format from the extension and chooses Read (for PDF/text/notebooks) or Bash with an appropriate converter (pandoc, python-docx, openpyxl, unzip, etc.) for Office/OpenDocument. This means adding a new format is one line in
document_extensions.Storage
.uploads/sits insideAPPROVED_DIRECTORY, so Claude's tools reach it without trippingToolMonitorpath boundaries. The file is not deleted after the call — follow-up turns can still read or reference it without re-uploading. Timestamp prefix (YYYYMMDD-HHMMSS-fff) prevents collisions from rapid uploads.The existing archive/code/text branches are untouched.
Formats supported
Binary (persisted, document branch):
.pdf— Read tool (native parser).docx,.xlsx,.pptx— Bash with pandoc / python-docx / openpyxl.odt,.ods,.odp— Bash with pandoc / LibreOffice.rtf— Bash with pandocUTF-8 text-compatible (existing text branch, inline in prompt):
.csv,.tsv,.log,.ics,.emlChanges
src/security/validators.py— extendALLOWED_EXTENSIONSwith the two groups abovesrc/bot/features/file_handler.py— newdocument_extensionsset,_detect_file_typereturns"document"for them, new_process_document_filepersists and builds the generic prompt,handle_document_uploadaccepts optionalcurrent_dir(defaults toapproved_directory)src/bot/orchestrator.py(agentic) — resolvecurrent_dirbefore callinghandle_document_upload, pass it throughsrc/bot/handlers/message.py(classic) — same plumbing;.pdfadded to supported-formats help text.gitignore— ignore.uploads/tests/unit/test_bot/test_file_handler.py(6 tests: parametrized detection for all binary formats, CSV → text branch, unknown binary stays binary, persist + prompt shape, end-to-end PDF, fallback-to-approved-directory); validator whitelist extended +.pdf.exeregressionKnown limitations
pip installif pandoc/python-docx/openpyxl aren't found. Supports are graceful: Claude will report back if it can't convert a given format..uploads/has no automatic cleanup. Manual housekeeping for now — a follow-up PR could add a/newhook or cron-based LRU..doc(legacy MS Word binary) and.ppt(legacy PowerPoint) intentionally excluded — they're harder to convert reliably and less common today.Relationship to #192
This PR supersedes #192, which was a narrow patch that branched on
.pdfdirectly inagentic_document. #192 has been closed. This PR:FileHandlerwhere other document types already liveTest plan
make test— 542 passed (10 new tests)black/isort/flake8cleanSMOKE-TOKEN-42uploaded, saved, Claude read via Read and answered correctlySMOKE-DOCX-99uploaded as.docx, saved to.uploads/20260424-145608-997-contract.docx, Claude converted via Bash and answered correctly.exestill blocked,.pdf.exetrap still blocked, files > 10 MB still rejected🤖 Generated with Claude Code