From 07b80f55bba0d5ec435ae12e1fe3ba8f72adcf15 Mon Sep 17 00:00:00 2001 From: Amad Salmon Date: Fri, 25 Sep 2026 16:10:13 +0000 Subject: [PATCH] feat(files): add drag-and-drop and picker upload to the Files panel Add POST /api/directory-upload so the Files panel's drag-and-drop, drop overlay and "Upload files..." action can write a file into a directory the panel already shows. Reuses api_upload_file's admission gates (extension allowlist, size cap, magic-byte content check) and the dashboard path gate, with platform-aware ancestor pinning. Upload is create-only. On POSIX the file is created through pinned_fs.write_new_file_pinned, the shared O_EXCL create under the pinned directory with an identity-checked rollback; on Windows the same O_EXCL create runs by name under the held pin_directory handle. An existing name is never replaced: a name collision, including one lost to a concurrent upload, answers 409 name_collision, and the panel shows " already exists in this folder. Rename your file, or remove the existing one, then upload again." A failed write removes the partly written file and answers 500 upload_failed. A new file gets atomic_write's default mode. The handler opens with the dashboard owner gate (require_owner_dashboard_request), matching the sibling /api/file-write route: a non-owner holding a channel-created dashboard session cannot use it to plant steering docs, SKILL.md or the MCP config. The gate runs before the query parse, path probe and multipart read, so a non-owner also cannot learn whether a path exists. Tests: non-owner refused (403 owner_only) before the probe; owner positive control; an existing name answers 409 name_collision with the original bytes unchanged, including for a request that sends overwrite=1; a rival created just before the create wins and the upload gets 409; a write that fails mid-body leaves no file on both the pinned and the by-name branch. --- docs/feature-map/README.md | 2 +- .../modules/learn-cron-dashboard.md | 5 +- .../modules/memory-skills-hooks.md | 7 + src/kiro_crew/dashboard/file_api/uploads.py | 584 +++++++- src/kiro_crew/dashboard/handlers/__init__.py | 1 + src/kiro_crew/dashboard/handlers/files.py | 8 +- src/kiro_crew/dashboard/routes/taskrunner.py | 1 + src/kiro_crew/pinned_fs_files.py | 6 +- src/kiro_crew/platform_compat_links.py | 11 +- .../files-panel-upload/drop-overlay.png | Bin 0 -> 34218 bytes .../files-panel-upload/upload-exists.png | Bin 0 -> 111736 bytes .../files-panel-upload/upload-progress.png | Bin 0 -> 90555 bytes .../files-panel-upload/upload-success.png | Bin 0 -> 90962 bytes test/link_screen_sites.py | 1 + ...st_dashboard_files_composition_contract.py | 4 +- test/test_handlers_files_directory_upload.py | 1177 +++++++++++++++++ test/test_platform_compat_filesystem.py | 68 + website/src/api/client/files.ts | 31 +- website/src/components/ChatDropOverlay.tsx | 6 +- website/src/i18n/locales/bn.json | 12 +- website/src/i18n/locales/de.json | 12 +- website/src/i18n/locales/en-XA.json | 12 +- website/src/i18n/locales/en.manual.json | 12 +- website/src/i18n/locales/es.json | 14 +- website/src/i18n/locales/fr.json | 14 +- website/src/i18n/locales/hi.json | 12 +- website/src/i18n/locales/it.json | 14 +- website/src/i18n/locales/ja.json | 10 +- website/src/i18n/locales/ko.json | 10 +- website/src/i18n/locales/pt.json | 14 +- website/src/i18n/locales/ru.json | 16 +- website/src/i18n/locales/zh-CN.json | 10 +- website/src/i18n/pluralKeys.json | 4 +- website/src/pages/chat/FileBrowserRail.tsx | 54 +- website/src/pages/chat/useDirectoryUpload.tsx | 221 ++++ .../src/pierre/PierreWorkspaceTreeImpl.tsx | 81 +- website/src/pierre/tree.tsx | 7 +- .../test/ApiClient.refactor.surface.test.ts | 5 +- website/src/test/FileBrowserRail.test.tsx | 127 ++ ...erreWorkspaceTreeImpl.contextMenu.test.tsx | 97 ++ website/src/test/useDirectoryUpload.test.tsx | 435 ++++++ 41 files changed, 3053 insertions(+), 52 deletions(-) create mode 100644 temp-screenshots/files-panel-upload/drop-overlay.png create mode 100644 temp-screenshots/files-panel-upload/upload-exists.png create mode 100644 temp-screenshots/files-panel-upload/upload-progress.png create mode 100644 temp-screenshots/files-panel-upload/upload-success.png create mode 100644 test/test_handlers_files_directory_upload.py create mode 100644 website/src/pages/chat/useDirectoryUpload.tsx create mode 100644 website/src/test/useDirectoryUpload.test.tsx diff --git a/docs/feature-map/README.md b/docs/feature-map/README.md index 5cee41a5b35..c2e5bd87c27 100644 --- a/docs/feature-map/README.md +++ b/docs/feature-map/README.md @@ -144,7 +144,7 @@ this area is reached from inside it unless stated otherwise. | Tool approvals | Approve or deny a tool call the agent proposes | Inline card in the transcript | `components/ApprovalCard.tsx` | `handlers/sessions_parts/live_turn.py` | `GET /api/approvals`, `POST /api/approvals/{id}/{action}` | | MCP elicitation | Answer a question an MCP server itself asks mid tool call: a small form built from the server's schema, or a link to visit and confirm. No trust setting answers it; the person does, or the wait runs out and the ask is cancelled | Inline card in the transcript | `pages/chat/ElicitationCard.tsx`, `pages/chat/page/transcriptRendering.tsx` | `dashboard/handlers/chat_elicitation.py`, `dashboard/chat_turn/elicitation_prompt.py`, `acp/elicitation.py` | `POST /api/chat/slots/{slot}/elicitation` | | Question cards | Agent asks a multiple-choice question in chat and waits; the answers return as the tool result and show as a collapsible "N questions answered" card under the tool row | Pending: card above the composer. Answered: \"N questions answered\" card under the `ask_question` tool row in the transcript | `components/PendingQuestionCard.tsx`, `pages/chat/AskAnswersCard.tsx` | `handlers/ask_question.py` | `POST /api/ask-question`, `GET /api/ask-question/pending`, `POST /api/ask-question/dismiss`, `POST /api/ask-question/{ask_id}/answer`, `POST /api/agent-ask/open`, `POST /api/agent-ask/{ask_id}/wait`, `POST /api/agent-ask/{ask_id}/withdraw` | -| Files in chat | Browse, attach, and upload workspace files, including signature-verified audio containers; search the rail by file NAME or by file CONTENTS, including the text inside Word/PowerPoint/Excel documents; preview one in the side panel — a .docx renders its headings, lists, tables and bold/italic runs, falling back to extracted plaintext; right-click a file row → Download saves it to the machine running the browser through the credential-gated download endpoint; drag a file or folder row onto the composer to mention it where it is dropped, the same `@` mention the row's **Add to chat** inserts at the caret (a folder whose path holds a space or `@` has no folder reference, so the drop is refused with a note and the row menu shows **Add to chat** disabled with the same reason); complete a relative path in the composer by typing `./` or `../`, which lists one directory level of the project (never above its root); the header carries the per-project actions — reveal the project directory, and open a terminal already in it; the side-panel preview's ⋯ menu offers **Open in Files**, which opens the same file in the Files app's own tabbed tree through its `?path=` deep link (`/file-explorer?path=`): the file lands in the tab whose root already contains it with the folders between expanded, or in a new tab rooted at its folder, and the Files backend — not the link — decides whether the path may be shown; the row is offered only while the Files app is enabled at its native route and the path is absolute; a path named in the transcript becomes a clickable chip (and a diff block or tool line offers **Open**) only once the gateway confirms it is a file or folder on disk, every path a view shows being asked about in one request | Chat right panel → Files tab, Name/Content toggle below the search field; right-click a file row → **Download**; drag a tree row onto the composer; composer attach; composer `./` / `../` path token; Files tab header → reveal button and **Project actions** → **Open terminal in the project directory**, whose row carries a one-line note saying what a terminal is; side-panel file preview → ⋯ → **Open in Files**; a path chip, diff-block header or tool-call line in the transcript | `pages/chat/FileBrowserRail.tsx`, `pages/chat/FilesHomePanel.tsx`, `api/fileGrep.ts`, `components/FileRenderers.tsx`, `components/MarkdownPanelHeader.tsx` (`OverflowMenu` — the **Open in Files** row), `apps/file-explorer/deepLink.ts` + `apps/file-explorer/FileExplorerPage.tsx` (the `?path=` deep link's URL shape and its consumption), `pierre/PierreWorkspaceTreeImpl.tsx`, `lib/treeEntryDrag.ts`, `components/composerTreeDrop.ts`, `components/ChatInput.tsx`, `components/FilePickerMenu.tsx`, `components/composerTokens.ts`, `hooks/usePathKind.ts` (the shared existence cache and batch behind transcript path chips, `components/DiffBlock.tsx` and `pages/chat/ToolCallLine.tsx`) | `handlers/files.py`, `dashboard/file_api/pinned_io.py` (`api_file_kinds`), `src/kiro_crew/apps/routes_proxy.py` (`handle_app_api_proxy`), `src/kiro_crew/apps/builtins/file_explorer/server.py` (`_h_resolve`) | `GET /api/file-read`, `POST /api/file-kinds` (the batched path-kind probe), `GET /api/file-office-preview`, `GET /api/file-download`, `POST /api/file-grep`, `GET /api/file-search`, `GET /api/path-complete`, `POST /api/upload`, `POST /api/upload/file`, `GET /apps/file-explorer/api/resolve` (the deep link's one probe) | +| Files in chat | Browse, attach, and upload workspace files, including signature-verified audio containers; search the rail by file NAME or by file CONTENTS, including the text inside Word/PowerPoint/Excel documents; preview one in the side panel — a .docx renders its headings, lists, tables and bold/italic runs, falling back to extracted plaintext; right-click a file row → Download saves it to the machine running the browser through the credential-gated download endpoint; drag a file or folder row onto the composer to mention it where it is dropped, the same `@` mention the row's **Add to chat** inserts at the caret (a folder whose path holds a space or `@` has no folder reference, so the drop is refused with a note and the row menu shows **Add to chat** disabled with the same reason); complete a relative path in the composer by typing `./` or `../`, which lists one directory level of the project (never above its root); the header carries the per-project actions — reveal the project directory, and open a terminal already in it; the side-panel preview's ⋯ menu offers **Open in Files**, which opens the same file in the Files app's own tabbed tree through its `?path=` deep link (`/file-explorer?path=`): the file lands in the tab whose root already contains it with the folders between expanded, or in a new tab rooted at its folder, and the Files backend — not the link — decides whether the path may be shown; the row is offered only while the Files app is enabled at its native route and the path is absolute; an OS file dropped anywhere on the Files rail uploads into the project root, which the drop overlay names, and a directory row's menu → **Upload files…** picks files to upload into that directory; either refuses a name that already exists there with a notice instead of replacing the file; a path named in the transcript becomes a clickable chip (and a diff block or tool line offers **Open**) only once the gateway confirms it is a file or folder on disk, every path a view shows being asked about in one request | Chat right panel → Files tab, Name/Content toggle below the search field; right-click a file row → **Download**; drag a tree row onto the composer; composer attach; composer `./` / `../` path token; Files tab header → reveal button and **Project actions** → **Open terminal in the project directory**, whose row carries a one-line note saying what a terminal is; side-panel file preview → ⋯ → **Open in Files**; a path chip, diff-block header or tool-call line in the transcript; drag an OS file onto the Files rail (the drop overlay), or a directory row's menu → **Upload files…** | `pages/chat/FileBrowserRail.tsx`, `pages/chat/FilesHomePanel.tsx`, `api/fileGrep.ts`, `components/FileRenderers.tsx`, `components/MarkdownPanelHeader.tsx` (`OverflowMenu` — the **Open in Files** row), `apps/file-explorer/deepLink.ts` + `apps/file-explorer/FileExplorerPage.tsx` (the `?path=` deep link's URL shape and its consumption), `pierre/PierreWorkspaceTreeImpl.tsx`, `lib/treeEntryDrag.ts`, `components/composerTreeDrop.ts`, `components/ChatInput.tsx`, `components/FilePickerMenu.tsx`, `components/composerTokens.ts`, `pages/chat/useDirectoryUpload.tsx` (the upload and error-notice hook shared by the drop overlay and the picker; no new user-facing entry point), `hooks/usePathKind.ts` (the shared existence cache and batch behind transcript path chips, `components/DiffBlock.tsx` and `pages/chat/ToolCallLine.tsx`) | `handlers/files.py`, `dashboard/file_api/pinned_io.py` (`api_file_kinds`), `src/kiro_crew/apps/routes_proxy.py` (`handle_app_api_proxy`), `src/kiro_crew/apps/builtins/file_explorer/server.py` (`_h_resolve`) | `GET /api/file-read`, `POST /api/file-kinds` (the batched path-kind probe), `GET /api/file-office-preview`, `GET /api/file-download`, `POST /api/file-grep`, `GET /api/file-search`, `GET /api/path-complete`, `POST /api/upload`, `POST /api/upload/file`, `POST /api/directory-upload`, `GET /apps/file-explorer/api/resolve` (the deep link's one probe) | | Git tab | Side-panel tab listing every Git repository the chat works in, one collapsible section per repository headed by its path: the chat's project folder first (tagged Project) when it is a repository, then each repository the agent's tool calls edited or ran commands in, most recent first; each section shows branch, uncommitted changes and recent commits | Chat right panel → **Git** tab | `pages/chat/ActivityViewer.tsx`, `components/GitReposPanel.tsx`, `components/GitPanel.tsx` | `dashboard/git_repo_touches.py`, `file_api/project_dirs.py`, `file_api/git_panel.py` | `GET /api/project/git/repos`, `GET /api/project/git/status`, `GET /api/project/git/log` | | Slide preview for decks | Shows a `.pptx` / `.ppt` opened in the side panel as rendered slides (pager + thumbstrip) when LibreOffice is on the gateway host; without it, a row naming the install command (with an agent hand-off) over the `.pptx` text outline, or over the download card for `.ppt`, which has no text to extract; a deck whose visible text the credential screen would redact is not rendered and shows the redacted outline under a row saying so; on Windows (no descriptor-relative file operations to pin the render's staging) slides are not rendered and the outline is shown with the reason | Side panel → Files tab → click a deck; any deck chip in chat | `components/FileRenderers.tsx` (`OfficeSlidesRenderer`) | `handlers/office_slides.py` | `GET /api/file-office-slides`, `GET /api/file-office-slide`, `GET /api/file-office-preview` | | Terminal panel | Two shells on the gateway host: an app-wide docked PTY, and a per-chat terminal whose tab lives in that chat's panel state (opens on the chat's working dir; switches with the session). A shell that exits on its own closes its tab in the window attached to it, and the docked panel with its last tab (another window still holding that tab is not told, and its next dial opens a fresh shell); a known non-zero exit leaves a Terminal note in the bell feed, Windows included. Both tab strips, the docked terminal's and the chat right panel's, carry a close menu — **Close**, **Close other tabs**, **Close tabs to the right** and **Close all tabs**, which the terminal strip names **Close other terminals**, **Close terminals to the right** and **Close all terminals** and the workspace strip follows with a note that the Changes, Artifacts and Files views stay open; closing a terminal tab stops its shell, a batch that stops more than one shell asks first, counting and naming the shells, and one that would discard unsaved file edits names the files before closing. With `dashboard.terminal.reuse_current` on, Run in terminal always COPIES the command to the clipboard for a manual paste and never runs it — so the confirm dialog, which promises a copy, cannot mislead. When a settled terminal tab exists it is also revealed (the docked panel opens and that tab is made active, and a popped-out terminal window is raised) so the shell to paste into is in front; when none is (no tab, or a restored tab still unconfirmed during hydration) the raw command is copied verbatim with no tab minted and nothing executed | Header terminal toggle (docked); chat right panel → **+** menu → **Terminal**, or Files tab header → **Project actions** → **Open terminal in the project directory** — both go through one `openProjectTerminal` callback, so they cannot disagree about the cwd (per-chat); workspace chat composer (main chat or shared pane) → `! ` → **Review terminal command** → review → **Run** (fresh docked terminal); right-click a tab, or on touch hold it and lift without moving, for the close menu | `components/BottomTerminalPanel.tsx`, `components/TabCloseMenu.tsx`, `pages/chat/SidePanel.tsx`, `pages/chat/SidePanelTabChip.tsx` (the right panel's tab chips, their close menu and a terminal tab's live title), `pages/chat/SidePanelTabBodies.tsx` (the per-chat terminal's tab body), `pages/chat/FilesHomePanel.tsx`, `pages/ChatPage.tsx` (run-in-terminal reuse), `components/ChatPane.tsx`, `components/ChatInput.tsx`, `components/RunInTerminalConfirm.tsx`, `hooks/useTerminalCommand.ts`, `hooks/useBottomTerminal.ts` (`reuseCurrentTab`), `utils/terminalExitClose.ts` | `handlers/terminal.py` | `POST /api/terminal/sessions`, `GET /api/ws/terminal/{session_id}` | diff --git a/docs/system-specs/modules/learn-cron-dashboard.md b/docs/system-specs/modules/learn-cron-dashboard.md index 38268c89e5f..9cfd8edc26a 100644 --- a/docs/system-specs/modules/learn-cron-dashboard.md +++ b/docs/system-specs/modules/learn-cron-dashboard.md @@ -3737,6 +3737,9 @@ transaction lease remains unchanged. **Project git**: GET `/api/project/git?path=` (checked-out branch label for the activity panel). `path` is matched against the gateway's own known project directories (every live chat slot's `project` plus the recorded recent-projects list) using pure string normalisation (`expanduser` + `normpath`), with no filesystem access on the untrusted value; an unrecognised directory is refused with 403 plus an SEL audit record, and only the matched server-held string is stat'd. Response is `{"repo": false}`, `{"repo": true, "repoRoot", "isRepoRoot", "branch"}`, or the detached form `{"repo": true, "repoRoot", "isRepoRoot", "detached": true, "head"}`, plus `path` on the envelope. `isRepoRoot` is true only when the project directory IS the work-tree root, compared on the unredacted realpaths; a project nested inside a repository rooted higher up answers false, which is what the follow-up card reads to withhold its worktree action (`POST /api/worktree/create` refuses a toplevel above the session's project unless another session is scoped to that toplevel, and the card does not offer branching a whole enclosing repository from one folder of it). No `git` subprocess is spawned; repository discovery is the dashboard's one in-process walk, `git_repo_touches.find_git_root` (shared with the Git tab's repository list below: a linked worktree's `.git` file counts, `GIT_CEILING_DIRECTORIES` stops it as it stops git -- so a project under a ceiling reads as not a repository here exactly as the status and log routes' `git rev-parse --git-dir` reports it -- and the depth is bounded by `_GIT_ROOT_WALK_LIMIT`), and the branch comes from reading `.git/HEAD` directly (a `git` invocation parses repo-local config, which can carry `[include] path = ` and read an arbitrary file, so reading HEAD ourselves removes that class), and a linked worktree's `.git` pointer file (`gitdir: `) is followed to the real HEAD. Both metadata reads go through `hooks.safe_read_prefix` (realpath canonicalisation, sensitive-target rejection, `O_NOFOLLOW`, bounded read). `branch` and `head` pass through `redact` before being returned; `path` and `repoRoot` use the same redactor with one narrow path-specific exception, applied only on Darwin, for the fixed-shape `/private/var/folders/<2>/<30>/T` temp prefix, whose slash-inclusive entropy otherwise looks like a bare credential. The split sits at the END of the OS-generated `<2>/<30>` id: only that id is withheld from the scan, while the fixed literal `/T` and the complete user-controlled suffix still pass through `redact` together — handing `/T` to the redactor is what keeps the 40-byte credential window that would start on that `/` in scope. The SEL audit keeps the real path. Every failure mode degrades to "no branch" rather than erroring, since the only consumer is a decorative label. A detached HEAD gets a fixed 7-char prefix, not git's dynamic abbreviation. GET `/api/project/git/status?path=` and GET `/api/project/git/log?path=` use the same known-project boundary, widened by `_slot_git_repo_snapshot` to the git roots each live slot's tool calls touched (see **Project git repos** below). The status route first checks `os.path.isdir` and repeats that check after any subprocess failure; a missing or non-directory project returns `200 {"repo": false, "files": []}`. It performs no in-process inspection of `.git` or anything `.git` points to. Instead it runs sandboxed Git with `LC_ALL=C` and `LANGUAGE=C`: a bounded `git rev-parse --git-dir` probe owns repository discovery. A nonzero probe whose bounded stderr contains Git's `not a git repository` verdict is confirmed absence and returns `200 {"repo": false, "files": []}`. Symlink markers, gitdir files, parent-repository discovery, ceiling and cross-filesystem rules therefore follow Git. Any other probe failure, including sandbox refusal, dubious ownership, permission failure, timeout, kill, or missing executable, returns 503 with `code: "git_status_unavailable"` unless the project directory disappeared. After a successful probe, a nonzero `git branch --show-current` (which validates corrupt symbolic HEAD while accepting detached and unborn states), a failed or empty `git rev-parse --show-toplevel`, or a nonzero `git status` is also 503. **The filter-driver refusal is 503 too, not an empty listing, and it carries its OWN code.** A repository whose own config names a `filter.*.{clean,smudge,process}` driver -- anything `git lfs install --local` touched -- is refused because `git status` re-hashes modified files through a `clean` driver on every 5 s poll, and the probe refuses under its own separate cause when it cannot prove a driver absent. The match is deliberately WIDER than execution: `smudge` fires on checkout rather than on the status re-hash, a driver that no `.gitattributes` path maps to never runs at all, and the probe-failure branch refuses without knowing a driver exists. Refusing all three is right, since none of them can be proven safe -- but the user-facing message therefore says the config DECLARES a driver and that policy refuses the check, and never claims a program ran. Spelling that refusal `200 {"repo": true, "files": []}` makes it byte-identical to a genuinely clean repository, so the panel draws its green clean badge over a working tree whose status was never read; the panel's one job is surfacing uncommitted changes, and a confident "none" is worse there than no answer at all. The code is `git_status_filter_refused`, distinct from the `git_status_unavailable` its four siblings use, because this condition is not an outage: the cause is knowable, and for the `declared` cause it is permanent for that repository with no retry clearing it while the config stands (the `unreadable` cause is the one that can clear on its own, which is why only the declared copy promises permanence). One code for both would tell an LFS user their repository is broken on every poll forever. The panel renders it as a SINGLE `ErrorNotice` whose message names the cause, rather than the two generic notices the outage path shows: both routes refuse together because the cause is repo-level, so two boxes would report one condition twice. The refusal notice is TITLED and the outage notice is deliberately untitled -- titling both put two red boxes opening on the same word beside each other while they carry opposite retry semantics, so the difference between permanent and transient was a body-read away; titled against untitled separates them at a glance, and the outage title was restating its own first clause anyway. The refusal title names the OUTCOME ("Kiro can't check this repository for changes") rather than the mechanism: "status checks are off" collided with the outage string's "couldn't read the repository status", so one panel used the word status for two conditions of different severity. A coded status refusal reaching the non-coalesced branch renders that same title and localized copy, because the backend's English sentence under a localized heading is mixed-language copy in the other twelve catalogs. The header REFRESH control is also inert while BOTH routes refuse, and stays live for an outage: the refusal copy promises that retrying will not help, a re-fetch returns the same 503 every time, and a live control over that sentence contradicts it. It is disabled rather than hidden so the header does not reflow, and the glyph itself switches to the slashed variant, because dimming plus a hover tooltip did not tell a reader the control was dead. It is keyed on both routes rather than on the coalescing predicate deliberately: the refusal is permanent only while the repository config stands, and the notice's own agent hand-off invites changing it in the same window. The status query then recovers on its own 5 s interval while the log query -- no interval, 30 s `staleTime`, and a branch-change effect that cannot fire on the FIRST successful status because it needs a previous marker -- still holds its cached refusal. Keying the inert state on the coalescing predicate would therefore disable the one control able to clear that, leaving a notice claiming history cannot be shown while the changes list renders beneath it. The panel also refetches the log automatically as soon as the status route comes back CLEAN (not merely stops refusing: a status route failing for its own reason proves nothing about the config). The 503 body carries a `cause` discriminator alongside the code -- `declared` when repo config names a driver, `unreadable` when the probe could not prove one absent -- and the panel renders one localized sentence per cause. One code, because it is ONE condition (refused by policy) with two causes; a disjunction in the copy ("declares a driver, or could not be read") made the common case, an LFS repository on every visit, read a question the guard had already answered, and a reader could only partly follow it. Naming a driver as fact in the probe-failure branch would still be wrong, which is why that branch answers its own cause rather than borrowing this one. It stays an `ErrorNotice` with the `askAgent` hand-off because the string originates in a `{error, code}` body, which is what the `errors-use-error-notice` rule scopes on, and because a repo-supplied filter driver is precisely what the agent can explain or change -- routing it around that surface would strip the one affordance that helps here. HEAD is still validated BEFORE the filter guard, which `test_status_checks_corrupt_head_before_filter_refusal` asserts by spying that the guard is never reached, and the two codes now witness the ordering as well. A successful listing is capped at 500 entries and carries `truncated: true` when entries were dropped; the panel renders that as a capped count (`500+ uncommitted`) plus a `showing first 500` note on the Changes header, because without it the cap read as the total and an undercount was presented as a count. `/api/project/git/log` shares the status route's discovery boundary through the same `_probe_git_dir` probe (`LC_ALL=C`, `LANGUAGE=C`, bounded stderr) and the same verdict predicate, `_is_not_a_repo_verdict`, which is the one documented classification contract for both routes: a nonzero probe carrying Git's `not a git repository` verdict, or a project directory that disappeared, is `200 {"repo": false, "commits": []}`; every other probe failure (sandbox refusal, spawn failure, dubious ownership, permission failure, timeout, kill, corrupt repository metadata) is 503 with `code: "git_log_unavailable"`. The log route also applies the same filter-driver refusal and answers it with 503 `code: "git_log_filter_refused"`, since an empty commit list is what a repository with no commits legitimately returns. A nonzero `git log` AFTER a successful probe deliberately stays `200 {"repo": true, "commits": []}`: an unborn repository exits 128 there, so that path is genuinely ambiguous and separating it would need a stderr match of its own. The panel renders `git_log_unavailable` as the log-side outage notice -- untitled, with the localized `log_failed` copy rather than the backend's English sentence, mirroring `git_status_unavailable` -- and suppresses the clean-repository empty state while the log route has failed, because "no commits to display" beside a notice saying history could not be read presents an outage as an empty history. The Git panel treats only `repo: false` without a simultaneous status error as a terminal no-repository state: it replaces the loading label, hides the clean badge and clean-repository empty copy, and tells the user to click the folder name below the message box. The other two `['git-status', …]` consumers now receive 503s on operational failure where they got 200s, and each was checked rather than assumed. `FileBrowserRail` renders an inline `ErrorNotice` with the agent hand-off on an error in All mode, and deliberately not in Changed mode because the Pierre tree owns the same query failure there. `PierreWorkspaceTreeImpl` treats a status error as terminal for a Changed-mode load and renders that notice with a structured report. Both of those notices RECOGNISE the refusal codes and render the panel's own refusal copy rather than their generic failed copy, because the refusal is permanent for an LFS-configured repository: generic copy there would say "failed" on every 5 s poll, forever, with no cause and no statement that retrying cannot help — the exact outage spelling this refusal code exists to end. The recognition lives once in `website/src/utils/gitStatusError.ts` (`gitErrorCode`, `isGitFilterRefusal`, `gitFilterRefusalCause`, `gitFilterRefusalCopyKey`) and all three components read it, so one condition has one wording rather than three to keep in step. Neither of those two carries the panel's TITLE, deliberately: `ErrorNotice`'s `inline` variant lays title and message out as flex siblings, so at the rail's 300-520px and the tree's width the title wraps into a stack of two-word fragments beside a narrow column of message — visible in `capture-git-filter-refusal-surfaces.mjs`'s frames, which is what settled it. The title exists in the panel to separate a refusal from a coexisting outage notice, and neither of these surfaces renders a second notice beside this one. The status outage string also has a variant used when the log route failed too, which names the half it covers ("Couldn't read the changes list."): the default wording promises a commit list that is still on screen, so beside a notice saying history cannot be shown the two boxes first contradicted each other and then, once the clause was merely dropped, read as one failure reported twice. Naming each half is what separates them. Neither of the two reads non-200 as "not a repository"; both read `repo` only from a 200 body, so a refusal reaches them as absent data rather than as a negative answer. **Project git repos**: GET `/api/project/git/repos?slot=` lists the repositories the chat side panel's Git tab shows for one session, because most sessions start in the shared workspace (not a repository) and work in repositories elsewhere. `kiro_crew.dashboard.git_repo_touches` notices them from the session's own tool calls in `chat_runner` (both `tool_call` and its `tool_call_update` refinement): an edit call's target paths (`edit_target_strings`: the params walk the edit gates run, `target_paths`, plus patch headers, with no path expansion), a shell call's `working_dir`/`cwd`, and a leading `cd `/`pushd ` or `git -C ` in its command (absolute or `~` only; a relative edit path resolves against the session's project and is dropped without one). Each candidate walks up to the nearest `.git` entry through `find_git_root`, the same walk the branch label above uses (a linked worktree's `.git` file counts; `GIT_CEILING_DIRECTORIES` stops the walk as it stops git), and the real root is kept on the slot (`_ChatSlot._git_repos`, oldest to newest, capped at `MAX_SLOT_GIT_REPOS`) unless it is the home directory, the filesystem root or a sensitive path. The walk never runs on the runner's event path: `note_tool_call` snapshots bounded raw candidate strings and a per-call `truncated` bit on the loop, retaining them only until `finish_tool_call` confirms successful execution. Refused, failed, cancelled and unfinished calls discard their candidates and loss bit; turn cleanup also clears proposal-eviction uncertainty. Executed calls queue their candidates on the slot (`_git_repo_walk_pending`, distinct, a retouched path moved to the newest end, bounded by `_MAX_PENDING_CANDIDATES_PER_SLOT` with the oldest dropped first) and returns; one walker task per slot (`_git_repo_walk_task`, held in `state._background_tasks` like every other dashboard fire-and-forget task) resolves the whole pending list in a worker thread and loops until nothing is queued, so a `stat` that hangs on a dead mount holds that task and never the turn's stream, the runner's next event is handled at once, calls land in call order (two concurrent walks could list an older touch as the newest), and a burst of calls is one thread hop. A slot closed mid-walk receives its roots on an object nothing reads; a loop shut down mid-walk cancels the task and asyncio drops the thread's late result. No broadcast follows a walk: the Git tab polls the listing (`refetchInterval` 10 s), so a root recorded after the turn's event shows on the next poll. Each listed repository is a collapsible section whose status query polls every 5 s only while it is open, and only the `MAX_POLLING_SECTIONS` (3) most recently opened sections poll at once: repositories the agent edited open by themselves, so without the cap every dirty repository would run its git subprocesses on every interval. A section the user opens becomes the newest and always polls; an open section past the cap fetched once and refreshes on window focus and on its Refresh control. The list is in memory only; the first listing after a restore folds in the roots the transcript's `file_changes` rows resolve to (`_git_repos_seeded`), so edited repositories come back while a repository the agent only ran commands in reappears on its next touch. The response is `{repos: [{path, source}], omitted: , listed: , incomplete: }`: `incomplete` is always present and reflects the sticky, slot-local `_git_repos_incomplete` flag, or a row refused for redaction in this response. Candidate-count, edit-traversal, patch-parser, command-directory, command-text/argv, refinement-merge and path-length cuts carry their loss through the call's snapshot, including an empty snapshot, and publish it only on execution. Evicting executed paths from the walker backlog also sets the flag. Pending-proposal eviction retains only one turn-local uncertainty bit, not an unbounded tombstone list: a later successful result with no retained proposal conservatively marks the list incomplete; a refusal or turn cleanup does not. Evicting the oldest terminal receipt marks nothing incomplete: the refinement a receipt waits for is read from the same wire update as its result, so it has arrived long before 64 later results are recorded, and most receipts belong to calls that name no path. Worker-thread expansion/canonicalization path bounds and the ancestor-walk depth budget return loss alongside roots, as do transcript-seed count/path bounds, and the listing folds those flags back onto the slot even when no roots survived. Home, filesystem-root, sensitive, unanchored and not-a-repository exclusions do not themselves mean truncation. The flag is not a missing-repository count and never inflates `omitted`; the Git tab renders it once per snapshot as a localized muted informational line, even on an empty list: "Some repositories the agent worked in may not be listed. Each one appears here once the agent works in it again." No path expansion or filesystem access is added to loop-side bookkeeping. `omitted` is always present and counts distinct repositories this session worked in that the list leaves out because only the `MAX_SLOT_GIT_REPOS` most recent are kept, including transcript seeding: evicted roots are remembered by path (`_ChatSlot._git_repos_evicted`, capped at `MAX_SLOT_EVICTED_GIT_REPOS`) and a root touched again leaves that list, so revisits never inflate it; older evicted roots are kept only as a count (`_git_repos_evicted_overflow`), so past that bound the number is an upper estimate (a forgotten root that returns can be counted twice). Deleted repositories filtered from the response do not count. `listed` counts the agent-touched repositories the response actually lists (the project's own included only when the agent worked in it). The Git tab's capacity notice reads its numbers from `listed` and `omitted` ("The agent worked in repositories; the most recent are listed."), so an untouched project row or a deleted repository never skews them; the notice is a localized informational line below the list, shown when the count is positive, and names no action because the server keeps nothing it could reveal. When the list is non-empty the tab opens with a one-line caption saying the rows are the repositories the agent worked in during this chat, with a second sentence appended only when the chat's own project is among them to say the project folder is listed first, so the Project tag's meaning is visible without its tooltip; the empty state says how the list fills (the agent editing files or running commands in a repository); the listing-failed notice carries a Retry that refetches beside the agent hand-off. The session's project comes first when it sits in a repository (`source: "project"`, `path` the project directory itself), then every touched root that still has a `.git`, most recent first (`source: "agent"`), each `path` accepted verbatim by the status and log routes. A row whose path the credential redactor (`_redact_project_path`) would mask is left out of `repos` and of `listed` and sets `incomplete` for that response: the masked string is not a path those routes accept, so the row could only show an error, and the unmasked path never reaches the client. The touched roots join those routes' allow-list (and only theirs: the tree, path completion and branch label keep the project-only boundary) because they are server-derived from the session's own tool calls, never from a request. An app token reads only a slot it owns on its own session and transcript (`deny_app_slot_session_access`, audited when the slot exists); any other slot is the same 404 as a missing one, and for an app token the status and log routes admit only the touched roots of the slots it owns (`app_owns_slot_session`). **Project tree**: GET `/api/project/tree?path=` (workspace file listing for the Files tab's Pierre tree, and for a chat side-panel `folder` tab whose path IS the chat's project root — `FolderPanel` renders the same `PierreWorkspaceTree` there so descending expands in place instead of re-targeting the tab, and falls back to its one-level `browseFiles` listing for every other directory and whenever this endpoint does not answer; while the payload is `truncated` the tab keeps the tree but sends its search box to the recursive server search (`/api/file-search`), whose matches stand in for the tree until the query is cleared (the tree stays mounted, hidden, so the folders the user expanded are still open when it returns), because the tree's own filter can only find what the capped tree lists; the matches are the server's merged with the tree's own paths that contain the query (case-insensitive, de-duplicated, the server's first), because the server search runs under its own scan budget and can miss a file the tree is already showing; a line above them (`search_beyond_tree`) says results can include files past the 10,000-item limit -- not that it covers the whole workspace, which that budget cannot promise -- since on a whole tree the same box narrows the rows in place, and the tab's Refresh re-runs that search too, as the listing body's Refresh does, because it is the retry beside a failed search -- because a directory path chip can name any directory on the gateway while this allow-list deliberately does not). File tabs start with their optional project-tree rail hidden so the document receives the full panel width; the toolbar toggle and persisted `mc-files-rail-open` preference keep it available on demand. Same known-project allow-list, sensitive-path refusal, and SEL auditing as `/api/project/git`. Inside a git repository the listing is `git ls-files -z --cached --others --exclude-standard` with `cwd=` (tracked + untracked, `.gitignore` honored; a project dir that is a repo subdirectory lists only its own subtree); outside one -- or when that listing fails (a nonzero exit: its 8 MiB output cap, its 15 s timeout, or no sandbox backend) -- it is ONE bounded breadth-first pass (`_project_tree_walk`, `os.scandir` per folder, never following a link) that skips tool and heavy directories (`_PROJECT_TREE_SKIP_DIRS`), walks every other dot-directory (e.g. `.worktrees`), drops every entry, directory or file, that is dot-named or sits under a dot-path (judged on the read folder's real path, ancestors above the project root included) when `is_sensitive_resolved_path` fences its real path (`_project_tree_fence`), and stops reading after `_PROJECT_TREE_SCAN_LIMIT` (20 × the row cap, 200,000) directory entries, so its cost is a function of that number rather than of the size of the tree (about half a microsecond an entry). The budget is shared so one large folder cannot starve its siblings, or their subfolders, wherever it sorts, without cutting a tree that fits: each folder of a depth, and the depths below as one more claimant, is guaranteed a floor of half the even split the depth starts with, and the folders are read in order, each up to everything the claimants after it do not need for their floors -- capped at the row cap, more entries than one folder could show -- and never less than an even split of what is left. So a tree of at most about half the scan limit whose folders each hold no more than the row cap is read whole, wherever its large folder sorts. A folder holding more than its share is cut there and named in `truncatedDirectories`; the entries it did not read, subfolders included, are unknown. Telling a full folder from a cut one costs one look-ahead entry, drawn from the same budget, so no listing reads more than the limit. A folder whose row falls past the cap is never read, and a read cut by the budget is never judged hidden-only. A subfolder is queued when its parent is listed and read one level later, so each queued folder carries the `(st_dev, st_ino)` its parent's listing recorded, and is read only if it is still that folder (`_project_tree_scandir`): on POSIX the name is opened `O_DIRECTORY | O_NOFOLLOW`, the descriptor `fstat`ed and compared, and the descriptor itself listed; on Windows, which lists only by name, the name is `lstat`ed, compared and refused as a link (a name-surrogate reparse point) just before the `scandir`, leaving the window between those two calls; a reparse folder that holds its own contents, such as a cloud-files placeholder, is walked like any other folder. A folder swapped in between -- renamed away with another directory or a link put at its name -- is shown as a row, named in `unreadableDirectories` (a row that claims nothing about its contents, never `truncatedDirectories`, which every consumer renders as the item limit), and nothing behind it is listed; a root swapped for a link after the handler resolved it fails its `O_NOFOLLOW` open the same way and is named `.`. Response `{root, paths: [], directories: [], repo, truncated?, truncatedDirectories: [], hiddenOnlyDirectories: [], unreadableDirectories: [], linkedDirectories: []}`. `paths` and `directories` TOGETHER carry at most `_PROJECT_TREE_MAX_ENTRIES` (10,000) rows on both branches (`_project_tree_allot`): directory rows are spent first, shallowest first (the walk's breadth-first discovery order; the git branch orders its derived folders by depth, then segment by segment, which is the same order), because a folder row is what lets the user navigate to the rest -- but never past half the cap while there are files to show, so a tree with more folders than the cap still lists its root files (`README.md` included) -- and the files share their rows round-robin across their direct parent directories, the root first, so no one folder takes every row; half is a floor for each side, not a split, and rows the files cannot use go back to folder rows without those folders' files then sharing the file rows; a file whose folder lost its row is not listed either. Empty directories on the filesystem-walk path are rows like any other. Folder rows are capped together with files rather than kept as an uncapped skeleton, because uncapped folder rows make the walk, the per-row redaction and the serialization linear in the size of the tree, and the tree is refetched every 10 s while it is open (paused while the read is failing), so a large non-git project dir would stall the gateway. The client bounds each read: 15 s for the file search and the tree (`FILE_SEARCH_TIMEOUT_MS`), 10 s for the browse and recent-project listings (`BROWSE_FILES_TIMEOUT_MS`); a timed-out or failed read gets a cause-keyed notice, with Refresh/Retry offered only for those two causes. On the walk the file fairness is over what the walk READ: a folder it never reached contributes no file and, having a row, is named in `truncatedDirectories`. `hiddenOnlyDirectories` is walk-only: it names the directories the walk leaves childless although they are not empty on disk (no file or directory the listing keeps remains, and every entry is a `_PROJECT_TREE_SKIP_DIRS` directory or a fenced entry, so a `.docker/` holding only a fenced `config.json` is hidden-only), so the tree can tell them from genuinely empty ones (`_bg/` holding only `.kiro/` is the reported case); a directory with a listed file or a kept subfolder is never in it even when it also holds hidden entries; the root is judged by the same rule and, when its top level holds only such entries, is named as `.` (it is no directory row of its own), because the payload would otherwise be the empty-workspace shape and the dashboard would call the workspace empty — the frontend then shows a root-specific notice in the tree's place instead of the empty-workspace one (`This workspace contains only hidden items (skipped tool and cache folders, protected entries)`, `root_hidden_only` — not the per-folder row's wording, which with no folder row above it read as being inside a folder), with no action row since there is nothing to act on; and the git branch returns `[]` by construction, because a directory row exists there only as an ancestor of a listed file, so an ignored-only folder is absent rather than childless and the only childless directory that branch can produce is a truncated one. `linkedDirectories` is walk-only too: a symlink to a directory is a visible, navigable entry (`ls` shows it), so it does NOT make its folder hidden-only; the walk never follows a link (`_project_tree_is_link`: a symlink or a name-surrogate reparse point such as a Windows junction, `platform_compat.lstat_is_name_surrogate`; against link cycles) and so never reads beneath it, so the walk lists it as a directory row of its own and names it here — nothing beneath it is listed, and the frontend puts `Link to another folder: contents not listed` (`row_linked`) beneath its row instead of calling the link, or the folder holding only links, empty; links are told apart among the entries the hidden/skip filter KEPT, because what a folder shows must be predictable from the name alone — a `node_modules` or `.cache` that is a link to a directory elsewhere is filtered exactly like its real twin and counts as a hidden entry (a folder holding nothing else is hidden-only), so link-ness the user cannot see never decides visibility; a link the filter keeps is a row, and a folder holding only kept links is not hidden-only; inside a repository the list is empty by construction (git lists a symlink as a file). `unreadableDirectories` is walk-only too: a folder is a row as soon as its parent's read names it, so a kept, non-link child the process cannot read (permission denied is the usual cause) is still a row of its own, and when its own read fails -- `scandir` refusing it, its listing failing part-way, in which case what it yielded first is discarded rather than shown as if it were the folder, or the `lstat` that records its identity failing after its parent listed it (a parent readable but not searchable) -- it is named here rather than in `truncatedDirectories`, so the tree shows the folder while its parent is not childless at all; any read failure counts, not only `EACCES`, because the parent listed the entry and a row that makes no claim about its contents is the honest rendering whatever stopped the read. An unreadable folder whose row falls past the row cap is neither a row nor named here (its parent is in `truncatedDirectories` instead): this list names only folders the payload shows, so the notice below never names a folder the tree has no row for, and the same holds for `hiddenOnlyDirectories` and `linkedDirectories`. Because the value behind that entry is a failed read, it is an error and reaches the user only through `ErrorNotice`: the frontend renders one above the tree naming the folders (`Folders not readable: `, `unreadable_folders`, hand-off on since the listing holds no draft; always the alert in the danger tone — an error is never toned down to a status or a muted register, only shown or, at the user's word, not shown. Its ✕ DISMISSES it: the control is named `dismiss_unreadable` (`Dismiss: hide this notice until the unreadable folders change`), carried as its `aria-label` AND its `title`, so the promise a remembered dismissal makes is read before the click by hover as well as by a screen reader; the click leaves, where the notice stood, a one-line `role="status"` "Notice hidden — N folders still unreadable (shown again when they change) — Undo" (`unreadable_dismissed`, a plural key on the count of folders the payload still names, + `undo_dismiss_unreadable`; muted, saying what the ✕ did and did not do — hidden, not fixed, how many folders are still unreadable and that the notice returns when they change — but never which folders: a count is not a toned-down copy of the error's text) for the product's one undo window (`MOVE_UNDO_MS` on `useHeldWindow`: held while the pointer is over that spot or focus is inside it, retired for good when the notice returns on its own, never carried across a remount; the focus hold is cleared at every swap of the spot's content, because the control that was activated had focus and a removed element fires no `focusout` — left alone, the hold would pin the offer open for the life of the mount — and when that control had focus, focus is handed to its counterpart, Undo after the ✕ and the ✕ after Undo, so a keyboard dismissal lands on the way back rather than at `` with the window running) whose Undo restores the alert and CLEARS the remembered set, so a mis-click on a ✕ that sits beside "Ask the agent" at the same weight has a visible way back; the dismissal remembers the folders the notice named for that project — per project directory, in the expansion memory's own store under its own key (`treeUnreadableDismissals.ts`, one `createProjectPathMemory` instance: module map for the remount, `localStorage` mirror for the reload, the same caps) — and while every folder the payload names is remembered the notice is not rendered, so a folder that stays unreadable by design is not red alarm chrome on every visit; a folder outside the remembered set brings the alert back with the whole list, and a remembered folder that drops out of the payload is forgotten (the set is pruned to each landed payload, never to a mount that has not heard from the server yet, and in a `truncated` payload a remembered folder absent from it is kept while the cut could be what hid it -- its parent is absent too or is itself in `truncatedDirectories` -- and forgotten when its parent is listed whole), so the same folder failing anew alerts again rather than inheriting its old dismissal), plans no state row beneath such a folder (nothing beneath it is known, and a status line inside Pierre's shadow root can carry neither the hand-off nor an error), and gives the folder's own row a lock marker whose accessible label points at that notice (`Not readable — see the notice above`, `row_unreadable_marker`, Pierre's decoration `title`: the tooltip and the accessible name of the decoration span; once the notice is dismissed the label says so instead, `Not readable — notice dismissed`, `row_unreadable_marker_dismissed`, so it never sends the reader to a notice that is not there) — a signpost to where the failure is reported, not a report of it: it states no failure, carries no hand-off of its own, and is fed by the same `unreadableDirectories` list the notice renders, so the reader can tell that folder from the ones that show nothing for another reason and can never see a marker the notice does not name; the folder is never called empty, and the notice and the markers follow the payload and go when the folders read again. Its files are never listed (nothing read them); the root itself failing is recorded as `.` — the walk then yields nothing, so without it the payload would be indistinguishable from a workspace with no files in it, and the root is no directory row of its own, so it is named only here (the frontend shows, in the tree's place, an `ErrorNotice` whose lead is `No permission to read the workspace folder` (`root_unreadable` — it names its cause, so it cannot be read as the host's own `Couldn't load the file tree`, which is a failed request where this listing answered; permission is the failure a directory that passed the `isdir` check produces, and a root removed between that check and the walk shows the same notice for one refresh) and whose message is the redacted `root` path in a span of its own, with a zero-width break hint after each `/` so a narrow rail wraps it at a segment boundary rather than mid-word (a Windows-shaped root — drive letter or UNC — is shown in its forward-slash form first, `normalizeWindowsPath`, since its backslash segments would get no break point; any other path is untouched) — display only: the notice's `report` carries the sentence and the real path to the agent — followed by the hand-off and a Refresh that re-asks the listing, the same two actions as the host's failed-listing notice, instead of the empty-workspace notice; one level down the row sits under the folder it qualifies, here no folder row exists to anchor it); and the git branch returns `[]` by construction for the same reason as `hiddenOnlyDirectories`: `--others` cannot scan a directory git cannot read, so it contributes no untracked file and, with no indexed file beneath it, is absent rather than childless, while an indexed path beneath it still comes from the index (`--cached` reads no directory) and makes it an ordinary populated row. The frontend passes those directories to Pierre as explicit trailing-slash paths, keeps the workspace-level truncation notice, and appends one synthetic state row beneath every folder the payload leaves childless (`website/src/pierre/treeStateRows.ts`): `Some items not shown: limit of 10,000 items reached` for a `truncatedDirectories` entry (checked first, since a folder that lost its contents to the cap is not empty; the workspace-level notice, `workspace_truncated`, names the same `limit of 10,000 items reached`, so the two are read as one fact; neither promises that every folder is listed, because the cap counts folder rows too), `Link to another folder: contents not listed` for a `linkedDirectories` entry (checked before hidden-only: a link is never walked, so it is neither truncated nor hidden-only), `Contains only hidden items (skipped tool and cache folders, protected entries)` (`row_hidden_only`) for a `hiddenOnlyDirectories` entry (the cause, as `row_truncated` names its own — the parenthetical says what "hidden" means here, items the listing hides by nature, not items the app cut off), and `Empty folder` otherwise (an `unreadableDirectories` entry is skipped before the choice, whatever else names it, so a folder nothing read is never called empty; the order only fixes what a malformed payload shows). A truncated folder's row also carries the `some items not shown` badge (`workspace_directory_truncated`; the same "not shown" vocabulary as its state row, so "hidden" is reserved for what the listing hides), and the badge yields while the folder is expanded over its own state row, so the cut is stated once at a time (except under an `Open in folder tab` row, which does not state the cut, so the badge stays). `@pierre/trees` renders rows for paths and has no slot for a status line, so each state row is fed to the model as one synthetic child path whose basename is the localized label followed by a trailing zero-width space (U+200B, `STATE_ROW_MARKER`) — the row's identity in the plan, and only that: a real file can end in U+200B too, so the styling never keys on the path. The wrapper's `renderRowDecoration` gives a planned row (a path in the plan's set, the same set its selection and context-menu guards read) a decoration icon `STATE_ROW_ICON` — an `` with no symbol behind it — and `PIERRE_TREE_STATE_ROW_CSS` selects the row carrying that decoration (`:has()`), never the path suffix and never the label, so a real file named `x` plus U+200B, or one named "Empty folder", keeps its icon, its pointer and its open; the wrapper keeps the planned row inert (no open, no selection, no context menu) with one exception: where the host passes `onOpenFolder` (the Files rail, in the side panel and the pinned Files tab), a `truncatedDirectories` row reads `Open in folder tab` (`row_truncated_open`), carries its own decoration (`STATE_ROW_ACTION_ICON`, link-styled with the pointer) and, when selected, is deselected and hands its project-relative folder to the host, which opens that folder's tab (`FolderPanel`, the per-folder listing and name search with a growing page); that row still has no open, drag or context menu, and the folder's `some items not shown` badge stays on beside it because the row no longer names the cut, and a `/` in a translated label is rewritten to the division slash (U+2215) so a label can never mint a subfolder. While the panel's filter is active only the state rows whose FOLDER matches the query are fed to the model (`stateRowsMatchingFilter`: the folder's listed `dir/` path, lower-cased, contains the query as Pierre normalizes it — trimmed, backslashes read as slashes, lower-cased), because two things hold at once: Pierre matches every listed path, so a row fed on a match of its label alone ("hidden", "folder") would surface a status line as a search result; and Pierre force-expands every matching directory in its hide-non-matches mode, re-applying that on every store event, so a matched childless folder cannot be kept closed and, with its row withheld, would open over nothing — the down-chevron with no row beneath it that the state row exists to prevent. A fed row matches through the same folder prefix and sits under its open folder, the badge yielding to it as outside a filter; a blank query feeds the whole plan. Pierre focuses the first match, and the fed row sorts first under its folder, so when that focus lands on a state row the wrapper hands it to the folder the row qualifies (`focusPath`, which raises no store event) — a focus ring never sits on a status line. The two resets the filter brackets (filter on, filter off) carry the model's expansion in a host that does not persist it, because Pierre snapshots the expansion when a filter opens and restores it when it closes. No "loading" or "error" state row exists: whether a folder has children is decided by the payload the moment it lands and never by a fetch in flight; Changed mode plans no state rows at all, since every folder there is the parent of a changed file. `truncated` is true exactly when `truncatedDirectories` is non-empty -- files or folder rows were omitted. A truncated payload with no row at all (the walk stopped before it reached anything it lists, such as a root whose first entries are all hidden folders) shows its own `root_truncated` sentence, `Workspace too large to list: limit of 10,000 items reached`, in the empty-workspace notice's place (`workspace-tree-root-truncated`), since nothing there is known to be empty. Pierre's `flattenEmptyDirectories` folds a folder holding one listed subfolder and no file into one chain row whose path names its LAST folder, so the badge is looked up for every folder in the row's `flattenedSegments`, and yields to the state row only when the chain's last folder is the one truncated folder in it. Older clients may ignore the five additive arrays. `root`, every file path, every directory path, every truncation marker, every hidden-only marker, every linked marker, and every unreadable marker pass through the same segment-aware redaction before egress -- in the same worker thread as the listing and the JSON serialization (`_project_tree_body`), because each is linear in the size of the listing and none may run on the event loop; `root` is an absolute project path like `/api/project/git`'s `repoRoot`, so it takes the same Darwin temp-prefix exception described above, while the project-relative paths carry no OS-owned prefix and stay on the canonical redactor. **Folders past the cap**: `?dir=` lists one project-relative folder instead of the whole tree, under the same row cap, its paths still project-relative and the folder echoed as `dir`. Every check on `path` (the known-project allow-list, the sensitive-root refusal, the audit record) runs first and unchanged; `dir` must then be a plain relative path (no empty, `.` or `..` segment, no leading `/` or NUL, and on Windows no drive, `\` or `:`) whose real path is exactly its plain join under the root, so any link along it is refused, and strictly inside the root and not sensitive (`_project_tree_subdir`; `dir_refused` 403, `dir_not_found` 404, each audited as denied). The folder is then pinned: `_project_tree_pin` reaches it through `pinned_fs.open_pinned_descendant_dir` (a descriptor walk refusing a link at every component; on Windows that helper's `lstat` per prefix and its documented by-name residual) and records its `(st_dev, st_ino)`; the walk reads it only while it is still that folder (`_project_tree_walk_pinned`), and git lists it from the project root with the folder as a literal pathspec (`GIT_LITERAL_PATHSPECS`) rather than as its working directory, so an ancestor swapped for a link after the check lists nothing outside the project. On a platform that cannot pin by descriptor (Windows, where git walks into a junction) a folder listing uses the identity-checked walk instead of git, so it does not honour `.gitignore` there (the walk still skips `_PROJECT_TREE_SKIP_DIRS`); a git listing that names nothing is not shown as an empty folder row. The tree (`PierreWorkspaceTreeImpl`) asks for a folder's own listing when the user expands a folder named in `truncatedDirectories`, or when a host reveals one, under `['project-tree', projectDir, 'dir', folder]` so the host's Refresh re-reads it (not polled on their own: each whole-tree poll that lands re-reads at most three of the loaded folders that are open, in turn, since the whole listing cannot see a change inside a folder past the cap; a listing whose latest read fails is named in a notice above the tree, `folder_listing_failed`, whether or not earlier rows are still shown, with its own Retry button beside it, `folder_listing_retry`, that re-reads those folders); at most 20 folder listings, and at most 40,000 rows across them (`MAX_FOLDER_LISTING_ROWS`, the newest listing always kept), are kept per mount (a folder whose listing that bound drops while it is open is not reloaded until it is closed, so the bound never cycles), and `mergeFolderListings` (`pierre/treeFolderListings.ts`) folds them into the rendered payload, a loaded folder and every folder beneath it leaving `truncatedDirectories` unless its own listing cuts them again; a loaded folder whose listing answers 404 (deleted since) or 403 (refused, e.g. a link) is dropped and, like one the bounds drop, not reloaded until it is closed, while a notice of its own naming it, `folder_gone` for a 404 or `folder_refused` (a link or a protected folder) for a 403, with no retry since none can change it (a 404 also re-reads the whole listing once, which still named the folder), stays until the user dismisses it or a later load of that folder succeeds; a revealed folder is expanded, focused and selected. While the whole payload is truncated the Files rail (`FileBrowserRail`) sends its name filter to `/api/file-search` (`NameSearchResults`, files and folders, 50 rows) instead of the tree's local filter, under the folder tab's own note (`search_beyond_tree`; the same budgeted walk, the same caveat), its failure copy the folder tab's own (`SEARCH_FAILURE_KEYS`, `lib/searchErrorCause.ts`), its hits joined by the cut tree's own matching files through the folder tab's own `mergeTreeMatches` (`lib/treeSearchMatches.ts`), also when the search fails, the tree kept mounted behind it; a folder hit reveals that folder in the tree and a file hit opens it. +**Directory upload**: POST `/api/directory-upload?dir=` (multipart, one `file` part) — writes an OS file into a directory the Files panel already shows: a drop anywhere on the Files rail's drop overlay uploads into the project root (the tree's `root`, which the overlay names), and a directory row's menu → "Upload files…" uploads into that directory; both POST here. The handler opens with the dashboard owner gate (`require_owner_dashboard_request`, the same gate `/api/file-write` requires) before any query parsing, path probe or multipart read, so a non-owner holding a channel-created dashboard session cannot plant files into the workspace. `dir` goes through the same `_validate_dashboard_path`/`is_sensitive_path` gate `/api/file-write` trusts for writing INTO the workspace (no fixed allow-list root — any non-sensitive directory on disk), then must already exist as a directory; both that check and the destination re-check below run through `_run_path_probe` on the dedicated bounded path-probe pool, while the final write uses its path-transfer pool, never the event loop's shared `asyncio.to_thread` executor (AUTOSDE's `no-blocking-call-on-event-loop` rule is `blocking: true` — a stalled network-mounted `dir` resolving synchronously would otherwise freeze every other request and the heartbeat with it). **The destination, not just the directory, is gated**: the crew-home root is deliberately not itself sensitive (only its leaves — `security_policy.json`, `profiles/`, `admission_policy.json`, `computer_use.json` — are), so the full joined path (directory plus the uploaded filename) is re-validated through the identical gate before the extension check or the body is read, closing the case where a permitted directory plus a keystone-leaf filename would otherwise reach the keystone. Reuses `api_upload_file`'s admission gates rather than inventing new ones: the same extension allowlist (`_ALLOWED_IMAGE_EXT`/`_ALLOWED_TEXT_EXT`/`_ALLOWED_DOC_EXT`/`_ALLOWED_VIDEO_EXT`/`_ALLOWED_AUDIO_EXT`) and the same magic-byte content-signature check (`_content_matches_ext`, CWE-434). Its per-file cap is `dashboard.upload_max_mb` (read through `upload_max_bytes`) for every admitted type, video included, where `api_upload_file` lets a video run up to `_MAX_VIDEO_UPLOAD_BYTES`; a larger file answers 413 `file_too_large`. Deliberately does NOT run `api_upload_file`'s aggressive `[^\w.\-]` filename mangling (harmless there — every scratch upload is UUID-prefixed and never user-facing by name) or the outbound `file_send` gate's credential/secret content scan (`_gate_upload_file`'s `redact()` check exists for content LEAVING the workspace toward a third party; this endpoint writes a user's OWN file INTO their OWN workspace, the opposite trust direction, so porting that scan here would be inventing a gate rather than reusing one). Only the filename's directory components are stripped (`Path.name`) and the rest is kept as typed; an empty name, `.`, `..` or a name with a control character answers 400 `code: "invalid_filename"` on every platform, and on a Windows host so does a name Windows cannot store (`<>:"\|?*`, a trailing dot or space, a device name such as `CON`), before any write. One file per request by design (the frontend loops, one multipart POST per file, mirroring the Knowledge Base upload) — this is what makes per-file collision handling possible. Upload is create-only: an existing file at the destination name is never replaced, and the request answers 409 `code: "name_collision"` (a request that still sends `overwrite=1` gets the same 409), so the frontend tells the user the file already exists in that folder. A write the filesystem refuses (an unwritable destination, a full disk) answers a JSON 500 `{"error": "failed to write file", "code": "upload_failed"}`, never aiohttp's bare text 500. + +Ancestor and create safety, by platform. On POSIX, the write pins the destination directory's ancestor chain component-by-component (`pinned_fs.pin_parent`, walking the already-canonicalized directory rather than re-resolving it, so a directory swapped for a symlink after validation is refused). On Windows — which has no `O_NOFOLLOW` + `dir_fd` combination to pin a parent CHAIN with — the same window is closed a different way: `platform_compat.pin_directory(target_dir)` opens a handle WITHOUT `FILE_SHARE_DELETE`, and for as long as it stays open Windows refuses to rename or delete that directory OR any directory above it, so an ancestor cannot be swapped for a junction while the classify-then-create below runs; a link or junction already sitting at `target_dir`'s own name is refused at that same open (403 `symlink_refused`), a reparse folder that holds its own contents, such as a OneDrive cloud-files folder, is pinned like any other folder (`links_only=True`), and any other refusal of that open answers 500 `upload_failed`. Directory upload therefore stays AVAILABLE on Windows rather than being disabled there — the fail-closed `ancestor_pin_unsupported` (501) response only fires on a platform with neither primitive, which no host in the supported CI matrix (Linux, macOS, Windows) reaches. On POSIX the file is created by `pinned_fs.write_new_file_pinned`: an `O_EXCL` create under the pinned directory descriptor, so an existing name is never replaced and of two concurrent same-name uploads exactly one creates the file while the other gets 409 `name_collision`, with an identity-checked rollback when a write, flush or close fails. On Windows the same `O_EXCL` create, write and close run by name under the held `pin_directory` handle, and the rollback goes through `pinned_fs.unlink_verified_by_name`, which pins the folder by the same rule. The bytes are written under the final name, so a reader can see a partially written file while the upload runs, and a failed write removes it. A new file gets the mode `atomic_write` gives one by default: owner-only inside the data home, `0o666` under the umask elsewhere. A pre-existing symlink, junction, FIFO, socket or device at the destination name is refused rather than followed, checked through the pinned descriptor on POSIX and by name via `platform_compat.is_link_or_junction` on Windows. Response `{"ok": true, "path", "name"}` on success. **Theme / display**: GET `/api/theme/boot` (**unauthenticated** — same boundary as `/api/health`; returns `{mode, color, language, onboarded, import_onboarded, privacy_acked, crewmates_onboarded}` so the SPA can apply the workspace theme, pick its UI language, and resolve onboarding gates before the token flow completes; no secrets), GET/PUT `/api/config/theme` (read/persist workspace display prefs `{mode?, color?, language?, onboarded?, import_onboarded?, privacy_acked?, crewmates_onboarded?}`; `mode` restricted to `""`/`dark`/`light`/`system`; `language` is `""` (auto-detect from the browser) or a BCP-47 tag validated for **shape only** via `_LANGUAGE_TAG_RE`, so adding a shipped language stays a frontend-only data change; the PUT validates the whole body before writing and persists ONLY the named `dashboard.*` keys through `run_config_write(update_config_locked, ...)`, never a whole-document `save()`, and answers an unreadable `config.json` with `500 {"code": "config_unreadable"}` leaving the file untouched, a write the config guard refuses with `400 {"code": "config_write_refused"}`, and a failed write with `500 {"code": "config_write_failed"}` — see `config.md` → "`KiroCrewConfig.save() -> None`"). All four response sites are built by one helper (`handlers/core.py::_theme_payload`) so a newly-added display pref cannot be surfaced by some and omitted by others — see `config.md` → "Dashboard UI language". **Foreign-agent import**: authenticated GET `/api/onboarding/import/scan`, POST `/api/onboarding/import/apply`, PUT `/api/onboarding/import/state`; see "Foreign-Agent First-Run Import". **Memory**: GET/PUT `/api/memory/preferences`, GET/PUT `/api/memory/projects`, GET/PUT `/api/memory/history`, GET/PUT `/api/memory/settings` (consolidation config: `history_idle_hours`, `history_max_days`; writes to config.json, applies immediately to running consolidator). The three markdown pairs, the semantic and episodic routes, `stats`, `events` and `carve` also take an optional `?store=`: absent, the route answers from the global store regardless of `X-Session-Key`; present, reads of it are owner-gated (writes are owner-gated either way, see the memory-mutation gate in [memory-skills-hooks.md](memory-skills-hooks.md)), an undeclared name is 404 `unknown_memory_store` and a silo whose vector tier cannot be stood up is 503 `store_unavailable`. `settings` is install-wide and takes no store. @@ -4323,7 +4326,7 @@ GET `/api/file-office-slides?path=...` is the rendered-slides manifest for a `.p **Side-panel file tabs** (`MarkdownPanel.tsx`; the reads, the watch and the missing-file retry live in `useMarkdownPanelDisk.ts`): only the visible tab holds an `/api/file-watch` stream, and only while it is not being edited, not dirty and not missing; mounting, becoming visible and leaving edit mode each do a fresh read instead. A 404 keeps the last copy on screen under a missing-file banner and re-reads on a backoff from 1 s up to 30 s until the file is back or the tab is hidden. A close of a live-watched tab asks the disk first, dirty or not, and only a clean tab is re-targeted in place. A save leaves the tab dirty if it was edited again while the save was in flight. For a whole buffer, diff mode compares `/api/file-diff`'s `original` with the buffer read with universal newlines (CRLF and a lone CR as LF), the way `original` itself is read, so a CRLF file does not diff as every line changed; the editor's live diff, whose new side is the buffer, draws `original` in the line ending most of the editor's opening text uses (`PierreEditorImpl.tsx`), never the live buffer's, because a new baseline rebuilds the editor's document from that opening text. Neither changes the buffer or what a save writes. For a partial buffer, every successful read makes git's patch owed unless a diff fetch started after the read began; an owed patch is fetched only while diff mode is on and only once no fetch is out (one `/api/file-diff` in flight per tab, the next started when it settles), and every stream frame after the first re-reads even when its text equals the buffer: `/api/file-watch` caps its read too, and sends a frame on every write to a file that fills the cap, since a write past the cut leaves the capped text unchanged; such a frame carries an empty `content` when the capped text did not change, as a change signal only. Side-panel artifact tabs (`ArtifactPanel.tsx`) follow the same visible-tab rule through the shared `useVisibleTabCatchUp` hook: only the visible one holds a stream, and becoming visible refetches the artifact (see [artifacts.md](artifacts.md) → "Live refresh — file-backed artifacts"). **File diffs**: GET `/api/file-diff?path=...` returns the working-tree diff and HEAD content for the requested file, plus a `status` field — `not_git` (the repository preflight failed, so the file is outside any git work tree and there is no baseline; only the preflight may claim this), `untracked` (inside a repo, all-added `--no-index` diff — including every file of a freshly-initialized repo with no commits yet), `modified`, `clean`, or `error` (a git invocation past the preflight failed or timed out; distinct from `clean` so a git failure is never reported as "no changes"). Every reachable `status` value is served with HTTP 200; a sensitive path is 403 `{"error": "Access denied"}`, and the empty-path / file-not-found early returns reply `{"diff": "", "original": ""}` with no `status` key. Snapshot reads and every Git subprocess decode text explicitly as UTF-8 with replacement for malformed bytes, so Windows locale code pages cannot corrupt non-ASCII file content. Both text fields are credential-redacted through `redact_via_context`, the same pass `/api/file-read` applies to the panel's buffer, because the file panel diffs those two strings against each other for a whole buffer (and draws `diff` itself for a partial one) and one raw side renders an untouched credential line as a hunk. The pass sees whole field text and neither field is truncated before it, because slicing first can cut the tail a credential pattern needs and serve the surviving prefix as real bytes; that is also why the pass runs off the event loop. The served `diff` is never git's patch of the raw file: the HEAD body and the whole working file are each redacted first, and `diff` is `git diff --no-index` of those two masked bodies (in a private temp dir, header rewritten to the file's path). A patch shows lines out of their surrounding text, so the pass runs on whole bodies only. If that masked diff fails, `status` is `error` and `diff` is empty; the raw patch is never a fallback. `status` is computed from the unredacted `git diff`, so it still reports `modified` when the only change is inside a redacted span (`diff` is then empty). For a partial buffer the panel reads that pair as a change the patch cannot draw (a masked credential, the file mode or line endings), so its notice says git reports a change the diff view can't show (`components.markdownPanel.diff_change_not_shown`), never "no changes"; it names no cause and no line, since a line would point at the credential that changed. A whole buffer is compared line by line, so when its text matches HEAD it still reads as no changes. Binary changes without text hunks use a summary with a redacted filename without opening the working file; the working file read for a masked text diff has a 32 MiB limit (HEAD is redacted whole, as before), with overflow returning `status: "error"` and an empty diff rather than truncated text. -**Dashboard file API owners**: `dashboard/handlers/files.py` is the facade every file route, the handlers package and tests import and patch; `_facade_compose.compose` runs its owners on the facade's globals, so a patch of `files.` reaches them. New work goes to the owner of its family: `uploads.py` (composer uploads), `workspaces.py`, `pinned_io.py` (descriptor-checked open, read and pinned write, and the batched kind probe `POST /api/file-kinds`), `transfer.py` (file watch, download, stream), `office_preview.py`, `sheet.py`, `search.py`, `path_complete.py`, `grep.py` (the grep route and its content-search engines), `browse.py` (the directory-picker listings), `project_dirs.py` (known project dirs, branch label), `git_panel.py` (bounded git, status, log), `project_tree.py`, `dashboard_config.py`, `path_probe.py` (the `_run_path_probe` chokepoint), `outbox.py` (the outbox routes), `channel_upload.py` (the Slack and channel uploads and their admission gate), `desktop.py` (reveal, the native picker and screenshot) and `editor.py` (file read, raw, write and diff). The facade keeps the module state, the imports its owners read, and the seams they share (`_sel`, `_audit_file_send`, `_subagent_parent_session_key`, `_body_err_code`, `_walk_monotonic` and the viewer's `_owner_view_bypasses_credential_pass`). The two directory-picker routes (`api_browse_dirs`, `api_browse_files`) stay in the facade too: their skip sets hold a literal the internal content scan refuses on an added line. `_PathProbeBusy`, defined in `path_probe.py`, reports `kiro_crew.dashboard.handlers.files` as its module, the path callers catch it by, so a traceback names that path. Each construct a repository guard reads by path stays in its named homes, and in no other file of the family: the SVG policy literal, the diff route's git argv and the owner-view seam in `editor.py` (the facade also binds that seam's redactor), and the native-dialog spawns in `desktop.py`; `test/test_dashboard_files_composition_contract.py` pins the split and those homes. +**Dashboard file API owners**: `dashboard/handlers/files.py` is the facade every file route, the handlers package and tests import and patch; `_facade_compose.compose` runs its owners on the facade's globals, so a patch of `files.` reaches them. New work goes to the owner of its family: `uploads.py` (composer uploads and the Files panel directory upload), `workspaces.py`, `pinned_io.py` (descriptor-checked open, read and pinned write, and the batched kind probe `POST /api/file-kinds`), `transfer.py` (file watch, download, stream), `office_preview.py`, `sheet.py`, `search.py`, `path_complete.py`, `grep.py` (the grep route and its content-search engines), `browse.py` (the directory-picker listings), `project_dirs.py` (known project dirs, branch label), `git_panel.py` (bounded git, status, log), `project_tree.py`, `dashboard_config.py`, `path_probe.py` (the `_run_path_probe` chokepoint), `outbox.py` (the outbox routes), `channel_upload.py` (the Slack and channel uploads and their admission gate), `desktop.py` (reveal, the native picker and screenshot) and `editor.py` (file read, raw, write and diff). The facade keeps the module state, the imports its owners read, and the seams they share (`_sel`, `_audit_file_send`, `_subagent_parent_session_key`, `_body_err_code`, `_walk_monotonic` and the viewer's `_owner_view_bypasses_credential_pass`). The two directory-picker routes (`api_browse_dirs`, `api_browse_files`) stay in the facade too: their skip sets hold a literal the internal content scan refuses on an added line. `_PathProbeBusy`, defined in `path_probe.py`, reports `kiro_crew.dashboard.handlers.files` as its module, the path callers catch it by, so a traceback names that path. Each construct a repository guard reads by path stays in its named homes, and in no other file of the family: the SVG policy literal, the diff route's git argv and the owner-view seam in `editor.py` (the facade also binds that seam's redactor), and the native-dialog spawns in `desktop.py`; `test/test_dashboard_files_composition_contract.py` pins the split and those homes. **Terminal handler owners**: `dashboard/handlers/terminal.py` is a mirror facade (`kiro_crew._facade_mirror`): routes, the handlers package and tests import and patch every terminal name through it, and a patch reaches every owner module that holds the name. It keeps the WebSocket route `api_terminal_ws` with the bash readiness hook it installs, the completion route `api_terminal_complete` with the listing it ranks, the redaction route, the title poller, the shell cwd and title probes (the trusted-path `lsof` fallback) and the code-fence shell list. New work goes to the owner of its responsibility: `terminal_session.py` (`_TerminalSession`, the session registry, the terminal config gate), `terminal_shell.py` (the shell, cwd and child environment a session starts with), `terminal_transport.py` (which socket owns a session, and the PTY's bytes to and from it, forwarded unchanged while the readiness marker is matched), `terminal_lifecycle.py` (the shell child's liveness, exit, failure notice and teardown, and the orphan reaper), `terminal_complete.py` (the vetting steps of path completion) and `terminal_routes.py` (the create, list and delete routes). `test/test_terminal_facade.py` pins the owner set and its layer order. **Usage handler owners**: `dashboard/handlers/usage.py` is a mirror facade (`kiro_crew._facade_mirror`): routes, the gateway's turn writers and tests import and patch every usage name through it, and a patch reaches every owner module that holds the name. It keeps the usage routes, the model a turn is recorded under (`read_turn_model` and the wrapper walk behind it) and the token-record writer (`persist_token_record`, the family's one `kiro_crew.acp` importer). New work goes to the owner of its responsibility: `usage_store.py` (the shard store and its data-home overrides, the reporting window, slot categories, the row readers), `usage_spend.py` (a slot's spend, the per-day credits, one slot's per-turn usage), `usage_context.py` (context-window occupancy and a session's context trace), `usage_cost.py` (the Telemetry spend breakdown), `usage_history.py` (the Usage page's token history) and `usage_sessions.py` (the Usage page's kiro-cli session activity and its cached parse). `test/test_usage_facade.py` pins the owner set and its layer order. **Dashboard prompt and skill route owners**: `dashboard/handlers/prompts.py` is the facade every prompt and skill route, the handlers package, `handlers/discover.py` and tests import and patch. It is a `_facade_mirror` facade over sibling `prompts_.py` owners and keeps no code of its own: a patch of `prompts.` reaches every owner that binds the name, and no owner imports the facade. New work goes to the owner of its family: `prompts_caller_gates.py` (the owner and app-slot checks, the project a requesting chat names, the prompt-write refusals and their audit), `prompts_paths.py` (the prompt roots, the linked-root refusal, the pinned directory and the platform capabilities the pinned create relies on), `prompts_reading.py` (`GET /api/prompts` and the descriptions it shows), `prompts_authoring.py` (`GET`/`PUT`/`DELETE /api/prompts/{name}`, `POST /api/prompts` and the name limits), `prompts_skill_catalog.py` (`GET /api/skills`, project trust, a skill's file tree), `prompts_skill_edits.py` (a skill's detail and edits, pin, inject-on-trigger, `POST /api/skills`) and `prompts_skill_pending.py` (staged candidates). `test/test_prompts_handler_facade.py` pins the family. diff --git a/docs/system-specs/modules/memory-skills-hooks.md b/docs/system-specs/modules/memory-skills-hooks.md index 52563f6a0cc..f6f84d8c6d1 100644 --- a/docs/system-specs/modules/memory-skills-hooks.md +++ b/docs/system-specs/modules/memory-skills-hooks.md @@ -5366,6 +5366,13 @@ alias was deleted or a symlink retargeted. Repeat calls avoid a re-walk only thr to `atomic_write`, so it must land for all three surfaces at once rather than by reverting one of them to an in-place write that a mid-write failure or a full disk would turn into data loss. + Directory upload (`/api/directory-upload`) is not one of these surfaces: it is + create-only, so it never replaces a file. It creates the file through + `pinned_fs.write_new_file_pinned` (an `O_EXCL` create under the pinned + directory, with an identity-checked rollback), and an existing name answers + 409 `name_collision`. The bytes are written under the final name, so a reader + can see a partially written file while the upload runs, and a failed write + removes it (see [learn-cron-dashboard.md](learn-cron-dashboard.md)). - `delete_skill(name)` — removes entire skill directory - All three address the leaf relative to a descriptor pinning the parent chain (`pinned_fs`) where the platform has the descriptor-relative syscalls, so an diff --git a/src/kiro_crew/dashboard/file_api/uploads.py b/src/kiro_crew/dashboard/file_api/uploads.py index e474d3d8848..34565bfc317 100644 --- a/src/kiro_crew/dashboard/file_api/uploads.py +++ b/src/kiro_crew/dashboard/file_api/uploads.py @@ -1,11 +1,14 @@ -"""Composer upload intake: ``POST /api/upload/file``, its per-type content checks and the media streaming path.""" +"""Upload intake: ``POST /api/upload/file`` (composer attachments) and ``POST /api/directory-upload`` (Files panel), their per-type content checks and the media streaming path.""" from __future__ import annotations import asyncio +import errno import hashlib +import ntpath import os import re +import stat as _stat_mod import uuid import zipfile from pathlib import Path @@ -21,6 +24,7 @@ _ALLOWED_IMAGE_EXT, _ALLOWED_TEXT_EXT, _ALLOWED_VIDEO_EXT, + _CONTROL_CHARS_RE, _HEIF_BRANDS, _MAGIC_PREFIXES, _MAX_UPLOAD_FILES, @@ -32,12 +36,21 @@ _UPLOAD_DIR, _VIDEO_HINT_EXT, _ZIP_CONTAINER_EXTS, + OWNER_ONLY_FILE_MODE, SNIFF_BYTES, + WINDOWS_DEVICE_STEMS, + _PathProbeBusy, + _probe_busy_response, + _run_path_probe, _sel, + _validate_dashboard_path, data_home, ensure_directory, + is_owner_only_target, logger, part_stream, + pinned_fs, + platform_compat, sniff_raster_mime, upload_max_bytes, ) @@ -511,3 +524,572 @@ def _sniff_media_type(header: bytes) -> str | None: if header[:4] == b"RIFF" and header[8:12] == b"WAVE": return "audio/wav" return None + + +async def api_directory_upload(request: web.Request) -> web.Response: + """POST /api/directory-upload?dir= — write an uploaded file into a + directory already visible in the Files panel. + + Sibling of :func:`api_upload_file`, for a different destination: that + endpoint always writes into the fixed chat-attachment scratch directory + (``_upload_dir()``); this one writes into whatever directory the caller + named -- a drag-and-drop target row, the full-pane drop overlay's root, or + the "Upload files..." row-menu action -- which may be anywhere the + dashboard's own path gate (``_validate_dashboard_path`` / + ``is_sensitive_path``) permits, the same path gate ``/api/file-write`` + trusts for writing INTO the workspace. Like ``/api/file-write``, the + handler opens with the dashboard owner gate + (``require_owner_dashboard_request``) before any query parse, path probe or + multipart read, so a non-owner holding a channel-created dashboard session + cannot use it to plant files into the workspace. + + Reuses the existing admission gates rather than inventing new ones: the + SAME extension allowlist and the SAME magic-byte content-signature check + (``_content_matches_ext``) + ``api_upload_file`` applies to a chat attachment. The per-file size cap + here is ``dashboard.upload_max_mb`` (``upload_max_bytes``) for EVERY admitted file, video + containers included: unlike ``api_upload_file``, which streams a video + against the larger ``_MAX_VIDEO_UPLOAD_BYTES`` (512 MB), this endpoint + applies no media-specific cap, so a video or audio file the shared + allowlist admits is still refused past that limit (``file_too_large``). There is no + credential/secret content scan here, deliberately matching + ``api_upload_file``'s own boundary rather than the OUTBOUND ``file_send`` + gate's (``_gate_upload_file``'s credential scan): that scan protects + content leaving the workspace toward a third party, which is the opposite + trust direction from a user dropping their OWN file into their OWN + workspace, so porting it here would be inventing a new gate rather than + reusing an existing one. The ``file_delivery_consent`` module the handlers + module already imports is not the missing piece either: its one class + (``CLASS_OWNER_DASHBOARD``) gates handing workspace content OUT to the + dashboard, so consulting it here would be asking permission for the + opposite direction of travel. + + Unlike ``api_upload_file``'s aggressive ``[^\\w.\\-]`` character + replacement (harmless there -- every scratch upload is UUID-prefixed and + never user-facing by name), the filename here is the file's permanent, + user-visible identity in their own workspace, so only the traversal- + relevant part is stripped (``Path.name``) and the rest is kept as typed. + An empty name, ``.``, ``..`` or a name with a control character is + refused with ``invalid_filename`` on every platform. On a Windows host a + name Windows cannot store (``<>:"\\|?*``, a trailing dot or space, or a + device name such as ``CON``) is refused with the same 400 before any + write; a Linux or macOS host stores such a name as typed, like every + other tool that writes into the workspace. + + One file per request, by design: the frontend sends one multipart POST + per dropped/picked file (mirroring the Knowledge Base upload), which is + what makes per-file name-collision handling possible -- a batch endpoint + would have to decide the collision policy for the whole batch at once. + + Uploads are create-only: an existing file at the destination name is + never replaced. A name collision is always refused with + ``code: "name_collision"`` and a 409, so the frontend can tell the user + the file already exists. + + **Destination gating, not just directory gating.** ``dir`` alone passing + ``_validate_dashboard_path`` is not enough: the crew-home ROOT is + deliberately unfenced (only its leaves -- ``security_policy.json``, + ``profiles/``, ``admission_policy.json``, ``computer_use.json`` -- are, per + ``security/paths.py``), so ``dir=`` with a ``file`` part named + ``security_policy.json`` would otherwise create the keystone where it is + absent -- exactly the ``api_file_write`` sibling's own gate, which runs + the FULL path (filename included). ``dest`` is therefore re-validated here + the identical way, after the filename is known and before either the + extension check or the body is read, so a sensitive destination is refused + before any byte of the upload is even accepted off the wire. + + **Ancestor-swap and partial-write safety.** ``pinned_fs.pin_parent`` + walks ``target_dir`` -- already realpath-canonicalized by + ``_validate_dashboard_path`` above, so this walks that RECORDED chain + component-by-component with ``O_NOFOLLOW`` rather than re-resolving it (a + fresh resolution here would just as happily follow a swap that landed after + validation as the original lookup would). ``pin_parent`` on the parent, not + ``open_dir_pinned`` on the target -- ``open_dir_pinned`` resolves its own + parent chain internally, which is exactly the re-resolution this avoids. A + single ``os.open(dest, ..., O_NOFOLLOW)`` only guards the FINAL component; + the parents on the way to it are not covered by any flag on that one call. + The file is then created by ``pinned_fs.write_new_file_pinned``: an + ``O_EXCL`` create under the pinned descriptor, so an existing name is + never replaced, and an identity-checked rollback when any write, flush or + close fails. The bytes are written under the final name, so a reader can + see a partially written file while the upload runs, and a failed write + removes it. The existing-target lstat that decides + collision/symlink/directory happens through the SAME pinned descriptor, + so it is not a second by-name resolution the ancestor pin does not cover. + + Windows cannot pin a directory descriptor at all (no ``O_NOFOLLOW`` + + ``dir_fd`` support -- see ``pinned_fs.supports_pinned_walk``), so the + ancestor-swap protection above is NOT available in that FORM there -- but + the property it buys IS still available, through a different primitive: + ``platform_compat.pin_directory(target_dir)`` opens a handle without + ``FILE_SHARE_DELETE``, and Windows refuses to rename or delete that + directory OR ANY DIRECTORY ABOVE IT for as long as the handle stays open + (see ``TestPinDirectory::test_a_pinned_directory_cannot_be_renamed_or_removed`` + in ``test_platform_compat_filesystem.py``). The open refuses a link or + junction at ``target_dir``'s own name and pins a reparse directory that + holds its own contents, such as a OneDrive cloud-files folder + (``links_only=True``). The handle's kernel-reported final path + must first match the already-validated target, so an ancestor swap that + redirects the open itself is refused rather than trusted as a lock on the + wrong directory. Held across the whole classify-then-publish below, the + verified handle prevents a later ancestor swap, so the SAME by-name + classification and publish the POSIX floor already uses become safe on + Windows too -- ``platform_compat.is_link_or_junction`` still catches a + planted symlink OR junction at the final name (which + ``path.is_symlink()`` alone would miss), and the same ``O_EXCL`` create, + write and close run by name, with the failed-write rollback going through + ``pinned_fs.unlink_verified_by_name``. Where NEITHER + ancestor-pinning primitive exists on a platform (unreachable in the + current CI matrix: every one of Linux/macOS/Windows takes one branch or + the other), the upload refuses outright with ``ancestor_pin_unsupported`` + rather than publish with no ancestor guard at all. This mirrors the + documented residual in ``pinned_fs.write_file_pinned``: the + everywhere-floor is "a planted NAME is never followed," not "no + same-process race exists." + """ + from kiro_crew.dashboard.handlers._shared import require_owner_dashboard_request + + # Ahead of the query parse, the path probe and the multipart read. This route + # creates files anywhere off the sensitive floor -- which includes + # the steering documents, the skills and the MCP config whose own write routes are + # owner-gated -- so leaving it open would hand a non-owner (a channel-allowlisted + # user's dashboard session, created by send_dashboard_link) every file those gates + # protect. It is the same gate, and the same ordering, api_file_write uses. Ahead + # of the probe too, so whether a path exists is not a non-owner's to learn. + owner_denied = await require_owner_dashboard_request(request, "upload.directory") + if owner_denied is not None: + return owner_denied + + caller = request.get("user", "dashboard") + # NOT stripped: a POSIX directory name may legitimately end in a space, and + # trimming one would resolve the upload to the space-less sibling instead + # -- silently writing the file into a different directory than the client + # named. An untrimmed value that is blank or bad is rejected below. + raw_dir = request.query.get("dir", "") + + def _denied(code: str, message: str, status: int, *, resources: str = "") -> web.Response: + _sel().log_api_access( + caller=caller, + operation="upload.directory", + outcome="rejected", + source="dashboard", + resources=resources or f"reason:{code}", + ) + # Dispatched through literal-status branches, EACH with its own + # dict-literal body, rather than one `web.json_response(body, + # status=status)` call: the error-code contract gate + # (test/test_error_code_contract.py) statically reads a call's + # `status=` keyword and its body argument, and either one arriving as + # a variable is indistinguishable from a status/body that is + # genuinely runtime-decided -- which the gate correctly cannot verify + # and buckets as a violation-shaped finding on every call, new or + # not. Every status this handler ever passes is one of these five + # literals (400/403/409/413/501), so branching on the value keeps `_denied` as the one + # place that assembles `code`/``message`` while still giving the gate + # a literal status AND a literal, transparent, ``code``-bearing dict + # it can verify at each site. + if status == 400: + return web.json_response({"error": message, "code": code}, status=400) + if status == 403: + return web.json_response({"error": message, "code": code}, status=403) + if status == 409: + return web.json_response({"error": message, "code": code}, status=409) + if status == 413: + return web.json_response({"error": message, "code": code}, status=413) + if status == 501: + return web.json_response({"error": message, "code": code}, status=501) + raise ValueError(f"api_directory_upload._denied: unhandled status {status!r}") + + if not raw_dir: + return _denied("missing_required_fields", "dir is required", 400) + if not (request.content_type or "").startswith("multipart/"): + return _denied("not_multipart", "expected multipart/form-data", 400) + + def _validate_directory(raw: str) -> str: + """``_validate_dashboard_path`` plus the existence check, off the loop. + + Returns the validated directory, or ``""`` when it is refused or absent. + + Both are blocking filesystem calls -- ``validate_file_path`` resolves + ``realpath`` and walks ``is_sensitive_path``'s candidate forms, + ``os.path.isdir`` is a ``stat`` -- so they go through + :func:`_run_path_probe`, this module's only sanctioned route for + filesystem work on a caller-supplied path. Not ``asyncio.to_thread``: + that is the loop's shared default executor, and a thread wedged in an + uninterruptible ``stat`` on a dead mount never returns, so a caller + naming one repeatedly would retire a shared worker per request until + every unrelated ``to_thread`` user queued behind them. On the probe + pool the same caller exhausts that pool and nothing else, and the + refusal surfaces as a 503 rather than a silent stall. + """ + validated = _validate_dashboard_path(raw) + if not validated or not os.path.isdir(validated): + return "" + return validated + + try: + target_dir = await _run_path_probe(_validate_directory, raw_dir) + except _PathProbeBusy: + return _probe_busy_response( + resource=raw_dir, operation="upload.directory", caller=caller, source="dashboard" + ) + if not target_dir: + return _denied( + "invalid_directory", "invalid or forbidden directory", 400, resources=raw_dir + ) + + reader = await request.multipart() + part = await reader.next() + if part is None or not isinstance(part, BodyPartReader) or part.name != "file": + return _denied("missing_required_fields", "file is required", 400) + + filename = part.filename or "" + # Strip any directory components the client sent (Path.name) -- the only + # traversal-relevant sanitization a filename needs. It also means `dest` + # below (`target_dir / safe_name`) is LEXICALLY always a direct child of + # `target_dir`: safe_name never carries a `/` or a `..` segment, so there + # is nothing left for a resolve()-based containment check to catch that + # candidate_dest's own re-validation just below does not already cover. + safe_name = Path(filename).name + if ( + not safe_name + or safe_name in (".", "..") + or _CONTROL_CHARS_RE.search(safe_name) + or ( + platform_compat.IS_WINDOWS + and ( + any(ch in '<>:"\\|?*' for ch in safe_name) + or safe_name.endswith((".", " ")) + or safe_name.split(".", 1)[0].lower() in WINDOWS_DEVICE_STEMS + ) + ) + ): + return _denied("invalid_filename", "invalid filename", 400, resources=filename) + + # Re-gate the FULL destination -- directory validation above is not + # enough on its own: the crew-home ROOT is deliberately not itself + # sensitive (only its leaves are -- security_policy.json, profiles/, + # admission_policy.json, computer_use.json), so dir= passes + # target_dir's own check and a filename naming one of those leaves would + # otherwise reach the keystone unfenced. This mirrors api_file_write, + # which runs its caller-supplied path (filename included) through this + # SAME gate -- the destination gets no lighter a check here just because + # part of it was already validated. Checked before the extension check + # and before any byte of the body is read, so a sensitive destination is + # refused as cheaply as every other early rejection in this handler. + # Through the probe pool for the same reason target_dir's own validation is + # above -- never the shared default executor. + candidate_dest = os.path.join(target_dir, safe_name) + try: + dest_ok = await _run_path_probe(_validate_dashboard_path, candidate_dest) + except _PathProbeBusy: + return _probe_busy_response( + resource=candidate_dest, + operation="upload.directory", + caller=caller, + source="dashboard", + ) + if not dest_ok: + return _denied( + "sensitive_destination", + "destination path is not allowed", + 403, + resources=candidate_dest, + ) + + ext = Path(safe_name).suffix.lower() + allowed = ( + _ALLOWED_IMAGE_EXT + | _ALLOWED_TEXT_EXT + | _ALLOWED_DOC_EXT + | _ALLOWED_VIDEO_EXT + | _ALLOWED_AUDIO_EXT + ) + if ext not in allowed: + detail = f"Unsupported file type: {ext}" + if ext in _VIDEO_HINT_EXT: + accepted = ", ".join(sorted(_ALLOWED_VIDEO_EXT)) + detail = f"{detail} — accepted video containers: {accepted}" + return _denied("unsupported_file_type", detail, 400, resources=filename) + + # ``dashboard.upload_max_mb``, read off the loop like api_upload_file. + max_upload_bytes = await asyncio.to_thread(upload_max_bytes) + data = bytearray() + while True: + chunk = await part.read_chunk(8192) + if not chunk: + break + data.extend(chunk) + if len(data) > max_upload_bytes: + return _denied( + "file_too_large", + f"File too large (max {max_upload_bytes // 1024 // 1024}MB)", + 413, + resources=filename, + ) + raw = bytes(data) + if not raw: + return _denied("empty_upload", "no file content received", 400, resources=filename) + if not _content_matches_ext(ext, raw): + return _denied( + "content_signature_mismatch", + f"File content does not match its type: {ext}", + 400, + resources=filename, + ) + + # No dest.resolve().is_relative_to(target_dir) check here: safe_name is + # already traversal-free (see its own comment above), so dest is + # LEXICALLY always a direct child of target_dir, and candidate_dest -- + # the identical path -- was already re-validated through + # _validate_dashboard_path above. A resolve()-based containment check + # would add nothing that check does not already cover, while adding two + # more blocking realpath walks directly on the event loop. + dest = Path(target_dir) / safe_name + + def _write() -> str | None: + """Create ``dest`` holding ``raw``, ancestor-pinned, or leave nothing. + + See the handler's own docstring for why ``pin_parent`` walks the + already-resolved ``target_dir`` rather than re-resolving it. The + pinned branch needs what ``pinned_fs.write_new_file_pinned`` needs: + a descriptor-relative open, plus the descriptor-relative stat and + unlink its identity-checked rollback uses. + + The existing-target CLASSIFICATION below (missing/symlink/dir/file) + happens entirely through stat-shaped checks (``pinned_fs.stat_at``, + ``platform_compat.is_link_or_junction``, ``Path.is_dir``/``is_file``) + BEFORE any open is attempted, never by opening the destination and + branching on which exception comes back: opening an existing DIRECTORY + with write flags is ``IsADirectoryError`` on POSIX but a different type + on Windows, so classifying first keeps the 409 the same on both. + + The classification only describes the destination at the moment it + ran. The create itself is ``O_EXCL``, so of two concurrent same-name + uploads that both classified `missing`, exactly one creates the file + and the other gets ``FileExistsError``, reported as `name_collision`. + An existing `file` is a `name_collision` too. + """ + pinned = pinned_fs.supports_pinned_walk() and {os.unlink, os.stat}.issubset( + os.supports_dir_fd + ) + dir_fd: int | None = None + ancestor_guard_fd: int | None = None + if pinned: + try: + dir_fd = pinned_fs.pin_parent(target_dir, what="upload target directory") + except pinned_fs.PinnedPathRefusal: + return "symlink_refused" + except OSError: + return "invalid_directory" + elif platform_compat.IS_WINDOWS: + # Windows has no O_NOFOLLOW + dir_fd combination to pin a PARENT + # CHAIN with (pinned_fs.supports_pinned_walk() is False there), + # so the by-name classification and publish below are, on their + # own, exactly the by-name write an ancestor swapped for a + # junction after _validate_dashboard_path could still redirect -- + # reaching a keystone leaf would be a governance-ceiling bypass, + # which AGENTS.md treats as absolute. platform_compat.pin_directory + # closes that window a different way: the handle it returns is + # opened WITHOUT FILE_SHARE_DELETE, and for as long as it stays + # open, Windows refuses to rename OR delete the directory it + # names -- NOR ANY DIRECTORY ABOVE IT (its own docstring; pinned + # by TestPinDirectory:: + # test_a_pinned_directory_cannot_be_renamed_or_removed in + # test_platform_compat_filesystem.py). The handle's final path is compared + # with the already-validated target before trusting that lock: an + # ancestor swapped for a junction before the open can otherwise + # redirect the open itself and make it pin the attacker's target. + # Held across the whole classify-then-publish below, a verified + # handle prevents any later ancestor swap. The open refuses a link + # or junction already sitting at target_dir's own name and pins a + # reparse directory that holds its own contents, such as a OneDrive + # cloud-files folder. Any other refusal of the open (a sharing + # violation, EACCES) propagates and answers upload_failed. + try: + ancestor_guard_fd = platform_compat.pin_directory(target_dir, links_only=True) + except OSError as exc: + if isinstance(exc, NotADirectoryError) or exc.errno == errno.ELOOP: + return "symlink_refused" + raise + pinned_target = pinned_fs.fd_real_path(ancestor_guard_fd) + if pinned_target is None or ntpath.normcase( + ntpath.normpath(pinned_target) + ) != ntpath.normcase(ntpath.normpath(target_dir)): + os.close(ancestor_guard_fd) + ancestor_guard_fd = None + return "symlink_refused" + else: + # Neither ancestor-pinning primitive exists on this platform: no + # descriptor-relative walk (POSIX without O_NOFOLLOW/dir_fd + # support) and no Windows handle-based anti-rename lock either. + # Publishing by name here would be an unguarded ancestor-swap + # window with no mitigation at all, so this refuses the upload + # outright rather than risk a write landing wherever an ancestor + # was swapped to -- including a keystone leaf. In the current CI + # matrix (Linux, macOS, Windows) this branch is unreachable: all + # three either satisfy `pinned` above or are Windows, which takes + # the branch above instead. + return "ancestor_pin_unsupported" + try: + # 'missing' | 'symlink' | 'dir' | 'file' for whatever currently + # sits at the destination name. On POSIX this stats through the + # pinned dir_fd, so no ancestor is resolved by name. On Windows it + # checks by name, under the pin_directory handle that stays held + # across this check and the create below, so no ancestor can be + # renamed in between. platform_compat.is_link_or_junction catches + # a planted symlink OR junction, where ``Path.is_symlink()`` alone + # would miss a junction. + if dir_fd is not None: + st = pinned_fs.stat_at(dir_fd, safe_name) + if st is None: + kind = "missing" + elif _stat_mod.S_ISLNK(st.st_mode): + kind = "symlink" + elif _stat_mod.S_ISDIR(st.st_mode): + kind = "dir" + elif _stat_mod.S_ISREG(st.st_mode): + kind = "file" + else: + # FIFO / socket / device planted at the name: refused the + # same way a symlink is, never opened as a plain file. + kind = "symlink" + elif platform_compat.is_link_or_junction(dest): + kind = "symlink" + elif dest.is_dir(): + kind = "dir" + elif dest.is_file(): + kind = "file" + else: + kind = "missing" + + if kind == "symlink": + return "symlink_refused" + if kind == "dir": + return "is_a_directory" + if kind == "file": + return "name_collision" + + # Owner-only inside the data home, 0o666 under the umask elsewhere: + # the default mode atomic_write gives a new file. + mode = OWNER_ONLY_FILE_MODE if is_owner_only_target(dest) else 0o666 + try: + if dir_fd is not None: + pinned_fs.write_new_file_pinned(dir_fd, safe_name, raw, mode=mode, fsync=True) + return None + # Windows: no dir_fd to create relative to. The directory and + # its ancestors are held by ancestor_guard_fd, so the by-name + # O_EXCL create lands in the validated directory, and the + # rollback removes the name only while it is still this file. + fd = os.open( + dest, + os.O_WRONLY + | os.O_CREAT + | os.O_EXCL + | getattr(os, "O_NOFOLLOW", 0) + | getattr(os, "O_BINARY", 0), + mode, + ) + except FileExistsError: + return "name_collision" + created: os.stat_result | None = None + try: + created = os.fstat(fd) + written = 0 + while written < len(raw): + written += os.write(fd, raw[written:]) + os.fsync(fd) + fd, open_fd = -1, fd + os.close(open_fd) + except BaseException: + if fd >= 0: + if created is None: + # Re-ask the identity through the descriptor (a + # transient EIO usually answers the second time); with + # no identity the name stays, as in write_new_file_pinned. + try: + created = os.fstat(fd) + except OSError: + pass + try: + os.close(fd) + except OSError: + pass + if created is not None: + pinned_fs.unlink_verified_by_name( + Path(target_dir), + safe_name, + (created.st_dev, created.st_ino), + links_only=True, + ) + raise + return None + finally: + if dir_fd is not None: + os.close(dir_fd) + if ancestor_guard_fd is not None: + os.close(ancestor_guard_fd) + + # transfer=True: this holds a worker for the length of the write, so it + # belongs on the transfer pool rather than the probe pool a burst of these + # would otherwise starve validation behind. + try: + outcome = await _run_path_probe(_write, transfer=True) + except _PathProbeBusy: + return _probe_busy_response( + resource=str(dest), operation="upload.directory", caller=caller, source="dashboard" + ) + except OSError as exc: + # An unwritable destination, a full disk, or a Windows pin refused for a + # reason other than a link; _write maps every other OSError to an outcome. + logger.exception("upload.directory write failed for %s", dest) + _sel().log_api_access( + caller=caller, + operation="upload.directory", + outcome="failure", + source="dashboard", + resources=str(dest), + error=exc.strerror or type(exc).__name__, + ) + return web.json_response( + {"error": "failed to write file", "code": "upload_failed"}, status=500 + ) + if outcome == "name_collision": + return _denied( + "name_collision", + f"A file named {safe_name!r} already exists", + 409, + resources=str(dest), + ) + if outcome == "is_a_directory": + return _denied( + "is_a_directory", + "a directory with that name already exists", + 409, + resources=str(dest), + ) + if outcome == "symlink_refused": + return _denied( + "symlink_refused", "refusing to write through a symlink", 403, resources=str(dest) + ) + if outcome == "invalid_directory": + return _denied( + "invalid_directory", "invalid or forbidden directory", 400, resources=str(dest) + ) + if outcome == "ancestor_pin_unsupported": + return _denied( + "ancestor_pin_unsupported", + "this platform cannot guard the upload directory's ancestors " + "against being swapped mid-request", + 501, + resources=str(dest), + ) + + _sel().log_api_access( + caller=caller, + operation="upload.directory", + outcome="success", + source="dashboard", + resources=str(dest), + ) + return web.json_response({"ok": True, "path": str(dest), "name": safe_name}) diff --git a/src/kiro_crew/dashboard/handlers/__init__.py b/src/kiro_crew/dashboard/handlers/__init__.py index a30eb833d67..57be4e6e3d7 100644 --- a/src/kiro_crew/dashboard/handlers/__init__.py +++ b/src/kiro_crew/dashboard/handlers/__init__.py @@ -177,6 +177,7 @@ def sel(): api_browse_files, api_channel_upload_file, api_dashboard_config, + api_directory_upload, api_file_diff, api_file_download, api_file_grep, diff --git a/src/kiro_crew/dashboard/handlers/files.py b/src/kiro_crew/dashboard/handlers/files.py index 7f222f431e1..54eaf9d02c6 100644 --- a/src/kiro_crew/dashboard/handlers/files.py +++ b/src/kiro_crew/dashboard/handlers/files.py @@ -79,6 +79,7 @@ LINK_PATTERNS_MAX, link_pattern_url_ok, ) +from kiro_crew.constants import WINDOWS_DEVICE_STEMS # noqa: F401 from kiro_crew.dashboard import git_repo_touches as _git_repo_touches # noqa: F401 from kiro_crew.dashboard import part_stream, upload_destination # noqa: F401 from kiro_crew.dashboard.body_numbers import body_int # noqa: F401 @@ -290,6 +291,7 @@ _stream_media_part, _upload_dir, _write_file_restricted, + api_directory_upload, api_upload_file, ) from kiro_crew.dashboard.file_api.workspaces import ( # noqa: F401 @@ -347,7 +349,11 @@ from kiro_crew.messaging.display_safety import redact_for_display # noqa: F401 from kiro_crew.messaging.outbound_files import OutboundFile # noqa: F401 from kiro_crew.messaging.raster import SNIFF_BYTES, sniff_raster_mime # noqa: F401 -from kiro_crew.owner_only_files import ensure_directory # noqa: F401 - owner: uploads +from kiro_crew.owner_only_files import ( # noqa: F401 - owner: uploads + OWNER_ONLY_FILE_MODE, + ensure_directory, + is_owner_only_target, +) from kiro_crew.pdf_extract import PdfExtraction, extract_pdf_segments # noqa: F401 from kiro_crew.platform import binary_content_is_flagged # noqa: F401 from kiro_crew.platform import wide_content_is_flagged # noqa: F401 diff --git a/src/kiro_crew/dashboard/routes/taskrunner.py b/src/kiro_crew/dashboard/routes/taskrunner.py index 927aeb11429..24b84d77802 100644 --- a/src/kiro_crew/dashboard/routes/taskrunner.py +++ b/src/kiro_crew/dashboard/routes/taskrunner.py @@ -59,6 +59,7 @@ def register(app: web.Application) -> None: app.router.add_get("/api/project/tree", handlers.api_project_tree) app.router.add_post("/api/upload", handlers.api_upload) app.router.add_post("/api/upload/file", handlers.api_upload_file) + app.router.add_post("/api/directory-upload", handlers.api_directory_upload) app.router.add_post("/api/slack/upload-file", handlers.api_slack_upload_file) app.router.add_post("/api/channel/upload-file", handlers.api_channel_upload_file) app.router.add_post("/api/slack/pins", handlers.api_slack_pins) diff --git a/src/kiro_crew/pinned_fs_files.py b/src/kiro_crew/pinned_fs_files.py index 4a769f5f628..0cd365d6e64 100644 --- a/src/kiro_crew/pinned_fs_files.py +++ b/src/kiro_crew/pinned_fs_files.py @@ -740,6 +740,7 @@ def unlink_verified_by_name( expect: tuple[int, int], *, on_error: Callable[[OSError], None] | None = None, + links_only: bool = False, ) -> bool: """Unlink *parent/name* only while it holds ``(st_dev, st_ino)`` *expect*. @@ -748,9 +749,10 @@ def unlink_verified_by_name( the parent, delegates to :func:`unlink_verified` on POSIX, and checks the regular-file identity under the pin before unlinking on Windows. An absent or mismatched name is a deliberate refusal and returns ``False`` without - deleting anything. + deleting anything. ``links_only`` is passed to the parent's + :func:`pin_directory`, so a cloud-files parent is pinned rather than refused. """ - pin = pin_directory(parent) + pin = pin_directory(parent, links_only=links_only) try: if os.name != "nt": return unlink_verified(pin, name, expect, on_error=on_error) diff --git a/src/kiro_crew/platform_compat_links.py b/src/kiro_crew/platform_compat_links.py index 2736b4ac4b4..4808648c5c3 100644 --- a/src/kiro_crew/platform_compat_links.py +++ b/src/kiro_crew/platform_compat_links.py @@ -225,7 +225,7 @@ def unlink_link_or_junction(path: str | os.PathLike) -> None: _WIN_FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 -def pin_directory(path: str | os.PathLike) -> int: +def pin_directory(path: str | os.PathLike, *, links_only: bool = False) -> int: """Open *path* as a directory and return a descriptor that PINS it. For code that must let a child process write to ``/`` by path: @@ -250,6 +250,11 @@ def pin_directory(path: str | os.PathLike) -> int: point raises ``NotADirectoryError``; a POSIX symlink fails with whichever of ``ENOTDIR`` / ``ELOOP`` the kernel reports for ``O_DIRECTORY | O_NOFOLLOW``. Release with ``os.close``. + + ``links_only`` narrows the Windows reparse refusal to a reparse point that + stands for another name (:func:`win_fd_is_link`), so a cloud-files or dedup + directory is pinned as the directory it is. POSIX is unaffected: a link is + the only thing ``O_NOFOLLOW`` refuses there. """ if IS_POSIX: return os.open(os.fspath(path), pinned_dir_flags()) @@ -257,7 +262,9 @@ def pin_directory(path: str | os.PathLike) -> int: fd = _win_open_without_following(path) try: attrs = getattr(os.fstat(fd), "st_file_attributes", 0) - if not attrs & _WIN_FILE_ATTRIBUTE_DIRECTORY or attrs & _WIN_FILE_ATTRIBUTE_REPARSE_POINT: + if not attrs & _WIN_FILE_ATTRIBUTE_DIRECTORY or _win_reparse_refused( + fd, attrs, links_only=links_only + ): raise NotADirectoryError(errno.ENOTDIR, "not a real directory", os.fspath(path)) except BaseException: os.close(fd) diff --git a/temp-screenshots/files-panel-upload/drop-overlay.png b/temp-screenshots/files-panel-upload/drop-overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..df45cd82ab6dfd1837925958e81de5654c3c233d GIT binary patch literal 34218 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 z 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

er2 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%0AwgvOSwyktTfXT2Ow+@ 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 None: if getattr(files, getattr(route.handler, "__name__", ""), None) is not None and route.handler.__module__ == _FACADE ] - assert len({id(h) for h in served}) == 34 + assert len({id(h) for h in served}) == 35 assert [h.__name__ for h in served if getattr(files, h.__name__) is not h] == [] reexports = _package_reexports() - assert len(reexports) == 36 + assert len(reexports) == 37 assert [n for n in reexports if getattr(handlers_pkg, n) is not getattr(files, n)] == [] diff --git a/test/test_handlers_files_directory_upload.py b/test/test_handlers_files_directory_upload.py new file mode 100644 index 00000000000..8f8cae75177 --- /dev/null +++ b/test/test_handlers_files_directory_upload.py @@ -0,0 +1,1177 @@ +"""Tests for ``api_directory_upload`` (POST /api/directory-upload). + +Backs the Files panel's drag-and-drop and "Upload files..." row-menu actions: +unlike ``api_upload_file`` (which always writes into the fixed chat-attachment +scratch directory), this endpoint writes into a caller-supplied directory +anywhere the dashboard's own path gate permits. + +Covers: the happy path, the create-only name-collision contract (an existing +name is always a 409, even for a request that still sends ``overwrite=1``), +the reused extension allowlist and size cap, the magic-byte content-signature +gate, invalid/sensitive/missing directories, that a path-traversal attempt via the uploaded filename lands +inside the target directory rather than escaping it, that a sensitive +DESTINATION is refused even when the directory alone would pass (the crew-home +keystone leaves), and that a symlink/junction planted at the destination name +is refused rather than followed on every platform. +""" + +from __future__ import annotations + +import errno +import os +import shutil +import stat +import threading +from pathlib import Path +from unittest.mock import MagicMock, patch +from urllib.parse import quote + +import aiohttp +import pytest +from aiohttp import web +from aiohttp.test_utils import TestClient, TestServer + +import kiro_crew.dashboard.handlers.files as files_mod +from kiro_crew import platform_compat +from kiro_crew.atomic_write import atomic_write +from kiro_crew.dashboard.handlers.files import api_directory_upload + + +def _make_app() -> web.Application: + """The directory-upload route as it is wired live: behind the dashboard owner gate. + + ``as_owner`` installs the standalone-local owner identity plus a stand-in for + the token middleware, so a request with no ``X-Test-User`` reads as the owner + (the default these tests want) and one carrying ``X-Test-User`` reads as that + non-owner subject -- the shape ``slack/allowlist.py::send_dashboard_link`` + mints for a channel-allowlisted user. + """ + from dashboard_owner_helpers import as_owner + + app = web.Application() + app.router.add_post("/api/directory-upload", api_directory_upload) + return as_owner(app) + + +# Kept as a named alias for the owner-gate tests that read as the non-owner. +_make_owner_app = _make_app + + +@pytest.mark.asyncio +async def test_directory_upload_refuses_a_non_owner(tmp_path: Path, mock_sel) -> None: + """A non-owner dashboard session cannot plant a file into the workspace. + + ``/api/directory-upload`` creates files anywhere off the + sensitive floor -- steering docs, ``SKILL.md`` and the MCP config included -- + so it holds the same owner boundary its sibling ``/api/file-write`` holds. + The refusal must land before the path probe, so a denied caller cannot even + learn whether the directory exists. + """ + steering = tmp_path / ".kiro" / "steering" + steering.mkdir(parents=True) + + with patch.object(files_mod, "_run_path_probe", wraps=files_mod._run_path_probe) as probe: + async with TestClient(TestServer(_make_owner_app())) as client: + form = aiohttp.FormData() + form.add_field("file", b"planted", filename="rules.md", content_type="text/plain") + resp = await client.post( + f"/api/directory-upload?dir={steering}", + data=form, + headers={"X-Test-User": "U0NONOWNER"}, + ) + body = await resp.json() + + assert resp.status == 403 + assert body.get("code") == "owner_only" + # Refused ahead of the path probe, so a non-owner learns nothing about the tree. + probe.assert_not_called() + assert not (steering / "rules.md").exists() + + +@pytest.mark.asyncio +async def test_directory_upload_still_writes_for_the_owner(tmp_path: Path, mock_sel) -> None: + """Positive control: the owner passes the gate and the upload lands.""" + target = tmp_path / "docs" + target.mkdir() + + async with TestClient(TestServer(_make_owner_app())) as client: + resp = await _upload( + client, + directory=str(target), + content=b"hello", + filename="note.md", + ) + + assert resp.status == 200 + assert (target / "note.md").read_bytes() == b"hello" + + +@pytest.fixture() +def mock_sel(): + """Stub the SEL audit sink so upload audit calls don't blow up.""" + with patch("kiro_crew.dashboard.handlers.files._sel") as m: + instance = MagicMock() + m.return_value = instance + yield instance + + +async def _upload( + client: TestClient, + *, + directory: str, + content: bytes, + filename: str, +) -> aiohttp.ClientResponse: + form = aiohttp.FormData() + form.add_field("file", content, filename=filename, content_type="text/plain") + return await client.post(f"/api/directory-upload?dir={directory}", data=form) + + +async def _upload_raw_filename( + client: TestClient, + *, + directory: str, + content: bytes, + filename: str, +) -> aiohttp.ClientResponse: + """Send the filename verbatim instead of applying FormData's quoting.""" + boundary = "kirocrew-directory-upload-test" + body = ( + f"--{boundary}\r\n".encode() + + ( + 'Content-Disposition: form-data; name="file"; ' + f"filename*=UTF-8''{quote(filename, safe='')}\r\n" + ).encode() + + b"Content-Type: text/plain\r\n\r\n" + + content + + f"\r\n--{boundary}--\r\n".encode() + ) + return await client.post( + f"/api/directory-upload?dir={directory}", + data=body, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, + ) + + +@pytest.mark.asyncio +@pytest.mark.skipif( + platform_compat.IS_WINDOWS, + reason="Win32 strips trailing spaces from a path component, so the two " + "directories this test needs to tell apart cannot both exist there", +) +async def test_trailing_space_in_dir_is_not_trimmed_to_a_sibling(tmp_path: Path, mock_sel) -> None: + """``dir`` is used verbatim: a directory whose name ends in a space is a + different directory from its space-less sibling, and trimming the query + value would land the upload in the sibling while the client is told the + name it asked for succeeded.""" + spaced = tmp_path / "reports " + sibling = tmp_path / "reports" + spaced.mkdir() + sibling.mkdir() + async with TestClient(TestServer(_make_app())) as client: + form = aiohttp.FormData() + form.add_field("file", b"payload", filename="a.txt", content_type="text/plain") + resp = await client.post(f"/api/directory-upload?dir={quote(str(spaced))}", data=form) + assert resp.status == 200, await resp.text() + body = await resp.json() + assert body["path"] == str(spaced / "a.txt") + assert (spaced / "a.txt").read_bytes() == b"payload" + assert not (sibling / "a.txt").exists() + + +@pytest.mark.asyncio +async def test_uploads_a_file_into_the_target_directory(tmp_path: Path, mock_sel) -> None: + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=b"hello world", filename="notes.txt" + ) + assert resp.status == 200, await resp.text() + body = await resp.json() + assert body["ok"] is True + assert body["name"] == "notes.txt" + written = tmp_path / "notes.txt" + assert written.read_bytes() == b"hello world" + assert body["path"] == str(written) + assert mock_sel.log_api_access.call_args.kwargs["outcome"] == "success" + # The mode is the one atomic_write's default gives a new file in the same place. + sibling = tmp_path / "sibling.txt" + atomic_write(sibling, b"x") + assert stat.S_IMODE(written.stat().st_mode) == stat.S_IMODE(sibling.stat().st_mode) + + +@pytest.mark.asyncio +async def test_windows_pin_final_path_must_match_validated_directory( + tmp_path: Path, mock_sel, monkeypatch +) -> None: + """A Windows pin that resolves elsewhere is refused before publication.""" + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + opened: list[int] = [] + closed: list[int] = [] + guard_fd = 1_000_000_000 + + def _pin_redirected_directory(path, **_kwargs): + opened.append(guard_fd) + return guard_fd + + real_close = os.close + + def _track_guard_close(fd, *args, **kwargs): + if fd in opened: + closed.append(fd) + return None + return real_close(fd, *args, **kwargs) + + monkeypatch.setattr(files_mod.platform_compat, "pin_directory", _pin_redirected_directory) + monkeypatch.setattr( + files_mod.pinned_fs, + "fd_real_path", + lambda fd: str(tmp_path / "redirected"), + ) + monkeypatch.setattr(os, "close", _track_guard_close) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(tmp_path), + content=b"must not be published", + filename="redirected.txt", + ) + assert resp.status == 403, await resp.text() + body = await resp.json() + + assert body["code"] == "symlink_refused" + assert opened and opened == closed + assert not (tmp_path / "redirected.txt").exists() + + +@pytest.mark.asyncio +async def test_directory_validation_runs_off_the_event_loop_thread( + tmp_path: Path, mock_sel, monkeypatch +) -> None: + """``_validate_dashboard_path`` (``realpath`` plus ``is_sensitive_path``'s + candidate-form walk) and the directory-existence check are blocking + filesystem calls. AUTOSDE's ``no-blocking-call-on-event-loop`` rule + (``blocking: true`` -- see ``AUTOSDE.yaml``) is authoritative here: a + stalled network-mounted ``dir`` resolving or stat-ing directly on the + gateway's event loop would freeze every other request and the heartbeat + for as long as that mount takes to time out. Both validation calls + (``target_dir``'s own, and the destination re-check) must run on a + worker thread, never on the thread driving this test's own event loop. + """ + main_thread_ident = threading.get_ident() + seen_idents: list[int] = [] + + real_validate = files_mod._validate_dashboard_path + + def _recording_validate(raw: str) -> str | None: + seen_idents.append(threading.get_ident()) + return real_validate(raw) + + monkeypatch.setattr(files_mod, "_validate_dashboard_path", _recording_validate) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=b"hello", filename="c.txt") + assert resp.status == 200, await resp.text() + + # target_dir's own validation, plus the destination re-check -- both + # calls this handler makes to _validate_dashboard_path. + assert len(seen_idents) == 2 + assert all(ident != main_thread_ident for ident in seen_idents) + + +@pytest.mark.asyncio +async def test_name_collision_is_refused(tmp_path: Path, mock_sel) -> None: + existing = tmp_path / "report.txt" + existing.write_bytes(b"original") + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=b"new content", filename="report.txt" + ) + assert resp.status == 409, await resp.text() + body = await resp.json() + assert body["code"] == "name_collision" + assert existing.read_bytes() == b"original" + + +@pytest.mark.asyncio +async def test_name_collision_is_refused_even_when_overwrite_is_requested( + tmp_path: Path, mock_sel +) -> None: + """The endpoint never replaces a file: a request that sends the old + ``overwrite=1`` query parameter still gets ``409 name_collision`` and the + original bytes stay as they were.""" + existing = tmp_path / "report.txt" + existing.write_bytes(b"original") + form = aiohttp.FormData() + form.add_field("file", b"replacement", filename="report.txt", content_type="text/plain") + async with TestClient(TestServer(_make_app())) as client: + resp = await client.post(f"/api/directory-upload?dir={tmp_path}&overwrite=1", data=form) + assert resp.status == 409, await resp.text() + body = await resp.json() + assert body["code"] == "name_collision" + assert existing.read_bytes() == b"original" + + +@pytest.mark.asyncio +async def test_concurrent_create_lands_only_the_winner( + tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch +) -> None: + """A same-name race between two concurrent CREATE uploads: both classify + the destination as missing, but a rival claims the name before THIS + request's create runs. The create is ``O_EXCL``, so it fails with + ``FileExistsError`` (reported as ``name_collision``) and the rival's bytes + stay as they were. + + The rival's file is planted from ``is_owner_only_target``, the last call + before the create on both the pinned and the by-name branch, so the REAL + ``O_EXCL`` open meets a genuinely present file. + """ + real_is_owner_only_target = files_mod.is_owner_only_target + + def _rival_wins_then_create(path): + Path(path).write_bytes(b"rival's bytes") + return real_is_owner_only_target(path) + + monkeypatch.setattr(files_mod, "is_owner_only_target", _rival_wins_then_create) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=b"mine", filename="race.txt") + assert resp.status == 409, await resp.text() + body = await resp.json() + assert body["code"] == "name_collision" + assert (tmp_path / "race.txt").read_bytes() == b"rival's bytes" + + +@pytest.mark.asyncio +async def test_existing_directory_name_is_refused(tmp_path: Path, mock_sel) -> None: + (tmp_path / "report.txt").mkdir() + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(tmp_path), + content=b"x", + filename="report.txt", + ) + assert resp.status == 409, await resp.text() + body = await resp.json() + assert body["code"] == "is_a_directory" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "filename", + [ + "report?.txt", + "a None: + """On a Windows host a name Windows cannot store answers a clean 400 + instead of the create's OSError.""" + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload_raw_filename( + client, + directory=str(tmp_path), + content=b"must not be published", + filename=filename, + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "invalid_filename" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("windows", [False, True], ids=["posix", "windows"]) +@pytest.mark.parametrize("control", ["\x01", "\x7f", "\x85"], ids=["c0", "del", "c1"]) +async def test_control_character_filename_is_rejected_on_every_platform( + tmp_path: Path, mock_sel, monkeypatch, windows: bool, control: str +) -> None: + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", windows) + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload_raw_filename( + client, + directory=str(tmp_path), + content=b"must not be published", + filename=f"x{control}y.txt", + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "invalid_filename" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "disposition", + ['form-data; name="file"; filename=""', 'form-data; name="file"'], + ids=["empty-filename", "no-filename"], +) +async def test_empty_filename_is_rejected_as_invalid_filename( + tmp_path: Path, mock_sel, disposition: str +) -> None: + boundary = "kirocrew-directory-upload-test" + body = ( + f"--{boundary}\r\nContent-Disposition: {disposition}\r\n" + "Content-Type: text/plain\r\n\r\nmust not be published\r\n" + f"--{boundary}--\r\n" + ).encode() + async with TestClient(TestServer(_make_app())) as client: + resp = await client.post( + f"/api/directory-upload?dir={tmp_path}", + data=body, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, + ) + assert resp.status == 400, await resp.text() + payload = await resp.json() + assert payload["code"] == "invalid_filename" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +@pytest.mark.skipif( + platform_compat.IS_WINDOWS, reason="the host filesystem cannot store these names" +) +@pytest.mark.parametrize("filename", ["a:b.txt", "a?b.txt", "con.md"]) +async def test_windows_only_names_upload_on_posix(tmp_path: Path, mock_sel, filename: str) -> None: + """A Linux or macOS host stores a name only Windows refuses, as typed.""" + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload_raw_filename( + client, + directory=str(tmp_path), + content=b"payload", + filename=filename, + ) + assert resp.status == 200, await resp.text() + body = await resp.json() + assert body["path"] == str(tmp_path / filename) + assert (tmp_path / filename).read_bytes() == b"payload" + + +@pytest.mark.asyncio +@pytest.mark.skipif( + platform_compat.IS_WINDOWS, reason="the trailing-dot name rule applies on Windows" +) +async def test_trailing_dot_is_not_a_name_refusal_on_posix(tmp_path: Path, mock_sel) -> None: + """On POSIX ``notes.`` passes the name rule; its empty suffix is then + refused by the extension allowlist, like any extension-less name.""" + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload_raw_filename( + client, + directory=str(tmp_path), + content=b"payload", + filename="notes.", + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "unsupported_file_type" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +async def test_unsupported_extension_is_rejected_before_any_write(tmp_path: Path, mock_sel) -> None: + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=b"MZ\x90\x00", filename="payload.exe" + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "unsupported_file_type" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +async def test_size_limit_is_enforced(tmp_path: Path, mock_sel, monkeypatch) -> None: + monkeypatch.setattr("kiro_crew.dashboard.handlers.files.upload_max_bytes", lambda: 8) + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=b"way too many bytes", filename="big.txt" + ) + assert resp.status == 413, await resp.text() + body = await resp.json() + assert body["code"] == "file_too_large" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +async def test_content_signature_mismatch_is_rejected(tmp_path: Path, mock_sel) -> None: + """A ``.png`` extension whose bytes are not really PNG is refused + (CWE-434) — the same magic-byte gate ``api_upload_file`` applies.""" + async with TestClient(TestServer(_make_app())) as client: + form = aiohttp.FormData() + form.add_field( + "file", + b"not actually a png", + filename="fake.png", + content_type="image/png", + ) + resp = await client.post(f"/api/directory-upload?dir={tmp_path}", data=form) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "content_signature_mismatch" + assert not any(tmp_path.iterdir()) + + +@pytest.mark.asyncio +async def test_non_multipart_request_is_rejected_with_coded_400(tmp_path: Path, mock_sel) -> None: + """Malformed upload requests are rejected before aiohttp parses multipart.""" + async with TestClient(TestServer(_make_app())) as client: + resp = await client.post( + f"/api/directory-upload?dir={tmp_path}", + json={"file": "not multipart"}, + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body == {"error": "expected multipart/form-data", "code": "not_multipart"} + + +@pytest.mark.asyncio +async def test_missing_directory_is_rejected(tmp_path: Path, mock_sel) -> None: + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(tmp_path / "does-not-exist"), + content=b"x", + filename="a.txt", + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "invalid_directory" + + +@pytest.mark.asyncio +async def test_directory_that_is_actually_a_file_is_rejected(tmp_path: Path, mock_sel) -> None: + a_file = tmp_path / "not-a-dir.txt" + a_file.write_text("x") + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(a_file), content=b"x", filename="a.txt") + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "invalid_directory" + + +@pytest.mark.asyncio +async def test_sensitive_directory_is_rejected(tmp_path: Path, mock_sel, monkeypatch) -> None: + """The same ``is_sensitive_path`` gate ``/api/file-write`` trusts must + also cover the new write direction: a directory ``hooks.validate_file_path`` + flags as sensitive is refused before any multipart body is even read. + Faked via the shared gate (rather than a real ``~/.ssh``) so the test does + not depend on this host's actual home-directory layout.""" + sensitive_dir = tmp_path / "sensitive" + sensitive_dir.mkdir() + monkeypatch.setattr( + "kiro_crew.hooks.is_sensitive_path", + lambda p: os.path.realpath(p) == os.path.realpath(str(sensitive_dir)), + ) + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(sensitive_dir), content=b"x", filename="id_rsa.pub" + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "invalid_directory" + + +@pytest.mark.asyncio +async def test_missing_dir_query_param_is_rejected(tmp_path: Path, mock_sel) -> None: + async with TestClient(TestServer(_make_app())) as client: + form = aiohttp.FormData() + form.add_field("file", b"x", filename="a.txt", content_type="text/plain") + resp = await client.post("/api/directory-upload", data=form) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "missing_required_fields" + + +@pytest.mark.asyncio +async def test_path_traversal_in_filename_lands_inside_target_dir(tmp_path: Path, mock_sel) -> None: + """A filename carrying ``../`` components must never escape the + validated target directory -- the upload lands, but as a plain file + named after its OWN basename, inside ``tmp_path`` only. + + Built as a raw multipart body (rather than ``aiohttp.FormData``) because + the high-level FormData writer percent-encodes a ``/`` inside a + ``filename=`` parameter before it ever reaches the wire, which would + neutralize the traversal attempt in the TEST CLIENT rather than in the + handler under test — this sends the literal, unencoded ``../`` an + attacker-controlled client can send. + + The upload target is a CHILD of ``tmp_path`` (not ``tmp_path`` itself), so + a single ``../`` resolves to ``tmp_path`` -- still inside pytest's + auto-pruned basetemp -- rather than to ``tmp_path.parent``, which is + outside this test's own temp tree and not pytest's to clean up. The + assertion stays exactly as meaningful: it still fails loudly if the + traversal guards regress, since a real escape would land the file at + ``target_dir``'s PARENT (``tmp_path``), which is exactly what is checked + below. + """ + target_dir = tmp_path / "target" + target_dir.mkdir() + outside_marker = tmp_path / "escaped.txt" + outside_marker_existed = outside_marker.exists() + boundary = "kctestboundary" + body = ( + f"--{boundary}\r\n" + 'Content-Disposition: form-data; name="file"; filename="../escaped.txt"\r\n' + "Content-Type: text/plain\r\n\r\n" + "pwned?\r\n" + f"--{boundary}--\r\n" + ).encode("utf-8") + async with TestClient(TestServer(_make_app())) as client: + resp = await client.post( + f"/api/directory-upload?dir={target_dir}", + data=body, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, + ) + assert resp.status == 200, await resp.text() + response_body = await resp.json() + assert response_body["name"] == "escaped.txt" + assert (target_dir / "escaped.txt").read_bytes() == b"pwned?" + # Nothing was ever written outside target_dir (i.e. one level up, at + # tmp_path.parent, which this test never touches). + assert outside_marker.exists() == outside_marker_existed + + +@pytest.fixture() +def crew_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: + """A ``KIROCREW_HOME`` override, exercising the REAL ``is_sensitive_path`` + gate rather than a monkeypatched stand-in: the keystone leaves + (``security_policy.json``, ``profiles``, ``admission_policy.json``, + ``computer_use.json``) live directly under this directory once the + env var is set, the same way they would under a real crew data home.""" + home = tmp_path / "crew_home" + home.mkdir() + monkeypatch.setenv("KIROCREW_HOME", str(home)) + return home + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "leaf_name", + ["security_policy.json", "computer_use.json", "admission_policy.json", "profiles"], +) +async def test_keystone_destination_is_refused(crew_home: Path, mock_sel, leaf_name: str) -> None: + """The crew-home ROOT passes ``_validate_dashboard_path`` on its own (it + is deliberately not itself sensitive -- only its leaves are), so naming a + keystone leaf as the UPLOADED FILENAME while ``dir`` is the crew-home root + must still be refused by the re-gated destination check. This is the + exact escape the destination re-validation in ``api_directory_upload`` + closes: ``dir=`` with a ``file`` part named after an absent + keystone leaf must never create it.""" + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(crew_home), + content=b"forged content", + filename=leaf_name, + ) + assert resp.status == 403, await resp.text() + body = await resp.json() + assert body["code"] == "sensitive_destination" + # Nothing was written: the keystone leaf must not even come to exist. + assert not (crew_home / leaf_name).exists() + + +@pytest.mark.asyncio +async def test_keystone_destination_is_refused_when_leaf_already_exists( + crew_home: Path, mock_sel +) -> None: + """Same refusal, but against an EXISTING keystone file with real content -- + the destination re-check runs before any collision logic, so the + original ceiling is provably untouched, not merely "not yet created".""" + policy = crew_home / "security_policy.json" + policy.write_text('{"real": "ceiling"}') + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(crew_home), + content=b'{"forged": "ceiling"}', + filename="security_policy.json", + ) + assert resp.status == 403, await resp.text() + body = await resp.json() + assert body["code"] == "sensitive_destination" + assert policy.read_text() == '{"real": "ceiling"}' + + +@pytest.mark.asyncio +async def test_crew_home_root_as_dir_cannot_reach_a_nested_keystone_leaf( + crew_home: Path, mock_sel +) -> None: + """``profiles`` is gated as a whole DIRECTORY, so a file inside it is just + as much a keystone write as the bare leaf name above -- prove the same + re-gate closes that path too, by asking for the upload directly on the + (pre-existing) ``profiles`` directory rather than the crew-home root.""" + profiles_dir = crew_home / "profiles" + profiles_dir.mkdir() + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(profiles_dir), + content=b"rogue profile", + filename="rogue.json", + ) + assert resp.status == 400, await resp.text() + body = await resp.json() + # Caught by the DIRECTORY gate this time (dir itself is sensitive), not the + # destination re-check -- either gate refusing is correct; this pins that + # at least one of them does, so nothing can reach a leaf through the + # crew-home root, whether by naming the leaf directly or by walking into + # its directory. + assert body["code"] == "invalid_directory" + assert not any(profiles_dir.iterdir()) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("existing_kind", "expected_status", "expected_code"), + [ + ("dir", 409, "is_a_directory"), + ("file", 409, "name_collision"), + ("missing", 200, None), + ], +) +async def test_by_name_floor_still_classifies_the_destination_correctly( + tmp_path: Path, + mock_sel, + monkeypatch, + existing_kind: str, + expected_status: int, + expected_code: str | None, +) -> None: + """Forces the platform to the "cannot pin a directory descriptor" floor + (``pinned_fs.supports_pinned_walk`` returning False) -- the branch Windows + takes for real, since it has no ``O_NOFOLLOW`` + ``dir_fd`` support to pin + with -- and pins that the by-name existing-target classification + (``platform_compat.is_link_or_junction`` / ``Path.is_dir()`` / + ``Path.is_file()``) still gets missing/dir/file right, matching the + dir-fd-pinned classification's outcomes exactly. + + This is a real regression: the PRE-REWRITE ``_write()`` used a single + ``os.open(dest, O_EXCL/O_TRUNC | O_NOFOLLOW, ...)`` and branched on the + POSIX-specific exceptions it raised (``IsADirectoryError`` etc.) -- exactly + the design CI's Windows shard caught opening a directory with write flags, + where the OS does not raise that same exception type, so the branch never + matched and the exception surfaced as an unhandled 500 instead of the + intended 409. Detecting the existing target's kind BEFORE ever attempting + to open it (as this rewrite does) does not depend on which exception type + a given OS raises for which failure, so it does not reproduce that gap -- + which is what this test, run for real on every platform CI exercises, + pins. + """ + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + # Also forces IS_WINDOWS so the handler takes its Windows ancestor-guard + # branch (platform_compat.pin_directory) rather than failing closed with + # ancestor_pin_unsupported -- that branch's own real POSIX pin_directory + # implementation still works fine here (IS_POSIX is untouched), so this + # reaches the same by-name classify/publish floor the test's docstring + # describes, guarded exactly as Windows would guard it for real. + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + monkeypatch.setattr( + files_mod.pinned_fs, + "fd_real_path", + lambda fd: str(tmp_path), + ) + target = tmp_path / "report.txt" + if existing_kind == "dir": + target.mkdir() + elif existing_kind == "file": + target.write_bytes(b"original") + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, + directory=str(tmp_path), + content=b"new content", + filename="report.txt", + ) + assert resp.status == expected_status, await resp.text() + if expected_code is not None: + body = await resp.json() + assert body["code"] == expected_code + + if expected_status == 200: + assert target.read_bytes() == b"new content" + elif existing_kind == "file": + assert target.read_bytes() == b"original" + + +@pytest.mark.asyncio +async def test_ancestor_swapped_after_directory_validation_is_refused( + tmp_path: Path, mock_sel, monkeypatch +) -> None: + """Mirrors ``test_a_grandparent_swapped_after_canonicalization_is_refused`` + in ``test_dashboard_pinned_write_migration.py`` (the ``api_file_write`` + sibling's own coverage of the identical race): ``pinned_fs.pin_parent`` + walks ``target_dir``'s OWN already-resolved component chain with + ``O_NOFOLLOW`` rather than re-resolving it, so an ancestor swapped for a + symlink AFTER validation is refused rather than followed. + + ``_validate_dashboard_path`` is frozen to the pre-swap canonical directory + here (bypassing a fresh, live re-resolution) to model a request that is + already past validation at the moment the swap lands -- a live + re-resolution at request time would simply resolve through the new + symlink and validate a different, equally legitimate directory, which is + a different scenario than the one this protects against. + """ + if not files_mod.pinned_fs.supports_pinned_walk(): + pytest.skip("platform without pinned directory descriptors") + + named = tmp_path / "named" + (named / "mid" / "leaf").mkdir(parents=True) + canonical_target = str(Path(os.path.realpath(named / "mid" / "leaf"))) + + # The tree an attacker wants the write redirected into, holding a file + # that must survive with its own bytes. + victim = tmp_path / "victim" + (victim / "leaf").mkdir(parents=True) + (victim / "leaf" / "sentinel.txt").write_text("PROTECTED") + + shutil.rmtree(named / "mid") + (named / "mid").symlink_to(victim, target_is_directory=True) + + monkeypatch.setattr(files_mod, "_validate_dashboard_path", lambda raw: canonical_target) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=canonical_target, content=b"attacker body", filename="a.txt" + ) + assert resp.status == 403, await resp.text() + body = await resp.json() + assert body["code"] == "symlink_refused" + assert not (victim / "leaf" / "a.txt").exists() + assert (victim / "leaf" / "sentinel.txt").read_text() == "PROTECTED" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("branch", ["pinned", "by-name"]) +async def test_a_write_that_fails_mid_body_leaves_nothing_behind( + tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch, branch: str +) -> None: + """The file is created, the first ``os.write`` lands half the body and the + second fails with ``ENOSPC``: the rollback removes the partly written + file, so the destination is empty and a retry is not refused 409. Run on + the pinned branch and on the by-name branch Windows takes.""" + if branch == "pinned" and not files_mod.pinned_fs.supports_pinned_walk(): + pytest.skip("platform without pinned directory descriptors") + if branch == "by-name": + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + monkeypatch.setattr(files_mod.pinned_fs, "fd_real_path", lambda fd: str(tmp_path)) + payload = b"0123456789" * 8 + half = len(payload) // 2 + real_write = os.write + calls: list[int] = [] + + def _half_then_disk_full(fd, data): + if bytes(data) in (payload, payload[half:]): + calls.append(fd) + if len(calls) == 1: + return real_write(fd, payload[:half]) + raise OSError(errno.ENOSPC, "No space left on device") + return real_write(fd, data) + + monkeypatch.setattr(os, "write", _half_then_disk_full) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=payload, filename="report.txt" + ) + assert resp.status == 500, await resp.text() + body = await resp.json() + assert body["code"] == "upload_failed" + assert len(calls) == 2 + assert not (tmp_path / "report.txt").exists() + + +@pytest.mark.asyncio +async def test_a_failed_first_identity_probe_still_rolls_back_on_the_by_name_branch( + tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch +) -> None: + """On the by-name branch Windows takes, the ``O_EXCL`` create succeeds but + the first ``os.fstat`` fails (a transient EIO). The rollback re-asks the + identity through the descriptor, so the empty file is removed and a retry + of the same name is not refused 409.""" + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + monkeypatch.setattr(files_mod.pinned_fs, "fd_real_path", lambda fd: str(tmp_path)) + real_fstat = os.fstat + target = tmp_path / "report.txt" + failed: list[int] = [] + + def _first_probe_fails(fd, *args, **kwargs): + if not failed and target.exists(): + failed.append(fd) + raise OSError(errno.EIO, "Input/output error") + return real_fstat(fd, *args, **kwargs) + + monkeypatch.setattr(os, "fstat", _first_probe_fails) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=b"body", filename="report.txt" + ) + assert resp.status == 500, await resp.text() + assert (await resp.json())["code"] == "upload_failed" + assert failed, "the first identity probe never ran on the created file" + assert not target.exists() + retry = await _upload( + client, directory=str(tmp_path), content=b"body", filename="report.txt" + ) + assert retry.status == 200, await retry.text() + assert target.read_bytes() == b"body" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "refusal", + [ + NotADirectoryError(errno.ENOTDIR, "not a real directory"), + OSError(errno.ELOOP, "reparse point at the final component"), + ], + ids=["not-a-directory", "eloop"], +) +async def test_windows_without_pinned_walk_guards_ancestors_via_pin_directory( + tmp_path: Path, mock_sel, monkeypatch, refusal: OSError +) -> None: + """Windows has no O_NOFOLLOW + dir_fd combination to pin a parent CHAIN + with (``pinned_fs.supports_pinned_walk()`` is False there), so a bare + by-name write there would leave open the exact ancestor-swap window this + fix closes: ``platform_compat.pin_directory(target_dir)`` is called and + its handle held across the whole classify-then-publish -- which, for + real on Windows, blocks renaming ANY ancestor while the handle lives + (``TestPinDirectory::test_a_pinned_directory_cannot_be_renamed_or_removed`` + in ``test_platform_compat.py``). + + Simulated here (this test runs on POSIX dev machines) by forcing the + "not pinned" branch plus ``IS_WINDOWS``, and replacing ``pin_directory`` + with a fake that raises exactly as the real one does when a link or + junction already sits at the name. Before this fix there was no such call + and no such branch at all: the handler fell straight through to an + unguarded by-name write, so this fake would never even be reached and + the upload would have SUCCEEDED instead of being refused. + """ + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + + calls: list[str] = [] + + def _fake_pin_directory(path, **_kwargs): + calls.append(str(path)) + raise refusal + + monkeypatch.setattr(files_mod.platform_compat, "pin_directory", _fake_pin_directory) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=b"attacker", filename="a.txt") + assert resp.status == 403, await resp.text() + body = await resp.json() + assert body["code"] == "symlink_refused" + assert calls == [str(tmp_path)] + assert not (tmp_path / "a.txt").exists() + + +@pytest.mark.asyncio +async def test_windows_without_pinned_walk_still_uploads_when_pin_succeeds( + tmp_path: Path, mock_sel, monkeypatch +) -> None: + """Companion to the refusal test above: when ``pin_directory`` succeeds, + the upload still proceeds normally (by name, exactly as the by-name + floor already did) with the guard held for the duration and released + afterward -- the fix adds a held guard, not a new failure mode for the + ordinary case.""" + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + monkeypatch.setattr( + files_mod.pinned_fs, + "fd_real_path", + lambda fd: str(tmp_path), + ) + + real_pin_directory = files_mod.platform_compat.pin_directory + opened: list[int] = [] + closed: list[int] = [] + + def _tracking_pin_directory(path, **kwargs): + fd = real_pin_directory(path, **kwargs) + opened.append(fd) + return fd + + real_close = os.close + + def _tracking_close(fd, *a, **k): + if fd in opened: + closed.append(fd) + return real_close(fd, *a, **k) + + monkeypatch.setattr(files_mod.platform_compat, "pin_directory", _tracking_pin_directory) + monkeypatch.setattr(os, "close", _tracking_close) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=b"hello", filename="b.txt") + assert resp.status == 200, await resp.text() + assert (tmp_path / "b.txt").read_bytes() == b"hello" + assert opened and opened == closed + + +def _cloud_files_pin(real_pin): + """A ``pin_directory`` stand-in for a OneDrive / cloud-files folder on Windows. + + Such a folder carries a reparse attribute whose tag is not a name surrogate, + so the default pin refuses it as the real one does and ``links_only=True`` + pins it. + """ + calls: list[bool] = [] + + def _pin(path, *, links_only=False): + calls.append(links_only) + if not links_only: + raise NotADirectoryError(errno.ENOTDIR, "not a real directory", str(path)) + return real_pin(path, links_only=True) + + return _pin, calls + + +def _force_windows_branch(monkeypatch: pytest.MonkeyPatch, target: Path) -> None: + monkeypatch.setattr(files_mod.pinned_fs, "supports_pinned_walk", lambda: False) + monkeypatch.setattr(files_mod.platform_compat, "IS_WINDOWS", True) + monkeypatch.setattr(files_mod.pinned_fs, "fd_real_path", lambda fd: str(target)) + + +@pytest.mark.asyncio +async def test_windows_upload_into_a_cloud_files_folder_lands_the_file( + tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch +) -> None: + """A folder under a OneDrive sync root is pinned with ``links_only`` and the + upload lands, rather than being refused as a symlink.""" + _force_windows_branch(monkeypatch, tmp_path) + pin, calls = _cloud_files_pin(files_mod.platform_compat.pin_directory) + monkeypatch.setattr(files_mod.platform_compat, "pin_directory", pin) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=b"hello", filename="c.txt") + assert resp.status == 200, await resp.text() + assert (tmp_path / "c.txt").read_bytes() == b"hello" + assert calls == [True] + + +@pytest.mark.asyncio +async def test_windows_pin_refused_for_another_reason_answers_upload_failed( + tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch +) -> None: + """A pin the filesystem refuses for a reason that is not a link (a sharing + violation, ``EACCES``) is a failed write, 500 ``upload_failed``, never a + 403 ``symlink_refused``.""" + _force_windows_branch(monkeypatch, tmp_path) + + def _denied_pin(path, **_kwargs): + raise PermissionError(errno.EACCES, "Permission denied", str(path)) + + monkeypatch.setattr(files_mod.platform_compat, "pin_directory", _denied_pin) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=b"hello", filename="d.txt") + assert resp.status == 500, await resp.text() + assert (await resp.json())["code"] == "upload_failed" + assert not (tmp_path / "d.txt").exists() + assert mock_sel.log_api_access.call_args.kwargs["outcome"] == "failure" + + +@pytest.mark.asyncio +async def test_windows_failed_write_into_a_cloud_files_folder_still_rolls_back( + tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch +) -> None: + """The rollback re-pins the folder through ``unlink_verified_by_name``; in a + cloud-files folder that pin uses ``links_only`` too, so the partly written + file is removed.""" + _force_windows_branch(monkeypatch, tmp_path) + pin, _calls = _cloud_files_pin(files_mod.platform_compat.pin_directory) + rollback_pin, rollback_calls = _cloud_files_pin(files_mod.pinned_fs.pin_directory) + monkeypatch.setattr(files_mod.platform_compat, "pin_directory", pin) + monkeypatch.setattr(files_mod.pinned_fs, "pin_directory", rollback_pin) + payload = b"0123456789" * 8 + real_write = os.write + + def _disk_full_after_half(fd, data): + if bytes(data) == payload: + real_write(fd, payload[: len(payload) // 2]) + raise OSError(errno.ENOSPC, "No space left on device") + return real_write(fd, data) + + monkeypatch.setattr(os, "write", _disk_full_after_half) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload(client, directory=str(tmp_path), content=payload, filename="e.txt") + assert resp.status == 500, await resp.text() + assert (await resp.json())["code"] == "upload_failed" + assert rollback_calls == [True] + assert not (tmp_path / "e.txt").exists() + + +@pytest.mark.asyncio +async def test_no_file_part_is_rejected(tmp_path: Path, mock_sel) -> None: + async with TestClient(TestServer(_make_app())) as client: + form = aiohttp.FormData() + form.add_field("not_file", b"x", filename="not_file.txt", content_type="text/plain") + resp = await client.post(f"/api/directory-upload?dir={tmp_path}", data=form) + assert resp.status == 400, await resp.text() + body = await resp.json() + assert body["code"] == "missing_required_fields" + + +class TestWriteFailureIsACodedError: + """A write the filesystem refuses answers a coded JSON 500, never aiohttp's bare one. + + The create raises ``PermissionError`` for a destination the gateway cannot + write (read-only or root-owned) and ``OSError`` for ``ENOSPC``; the rail + renders ``body.error`` verbatim, so an unhandled raise shows "Internal + Server Error" with no ``code`` to switch on. The refusal is injected at the + file flush, which both the pinned and the by-name branch run. + """ + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "exc", + [ + PermissionError(errno.EACCES, "Permission denied"), + OSError(errno.ENOSPC, "No space left on device"), + ], + ids=["permission-denied", "disk-full"], + ) + async def test_write_oserror_answers_json_500_with_code( + self, tmp_path: Path, mock_sel, monkeypatch: pytest.MonkeyPatch, exc: OSError + ) -> None: + def _refuse_flush(_fd: int) -> None: + raise exc + + monkeypatch.setattr(os, "fsync", _refuse_flush) + + async with TestClient(TestServer(_make_app())) as client: + resp = await _upload( + client, directory=str(tmp_path), content=b"payload", filename="notes.txt" + ) + assert resp.status == 500, await resp.text() + assert resp.content_type == "application/json" + body = await resp.json() + + assert body["code"] == "upload_failed" + assert body["error"] + assert not (tmp_path / "notes.txt").exists() + assert mock_sel.log_api_access.call_args.kwargs["outcome"] == "failure" + assert mock_sel.log_api_access.call_args.kwargs["resources"] == str(tmp_path / "notes.txt") diff --git a/test/test_platform_compat_filesystem.py b/test/test_platform_compat_filesystem.py index 6e1c91775dd..d8aaca5cbf2 100644 --- a/test/test_platform_compat_filesystem.py +++ b/test/test_platform_compat_filesystem.py @@ -259,6 +259,74 @@ def test_a_pinned_directory_cannot_be_renamed_or_removed(self, tmp_path): assert (tmp_path / "swapped").is_dir() +class TestPinDirectoryLinksOnly: + """``pin_directory(links_only=...)`` on the Windows branch, with its two reads stubbed. + + A OneDrive / cloud-files folder is a directory carrying + ``FILE_ATTRIBUTE_REPARSE_POINT`` with a tag that is NOT a name surrogate, and + no CI host can make one, so the attribute read (``os.fstat``) and the tag read + (``win_fd_is_link``) are replaced and the open hands back a plain descriptor. + """ + + @pytest.fixture + def windows_pin(self, tmp_path, monkeypatch): + import kiro_crew.platform_compat_links as links + + stand_in = tmp_path / "stand-in" + stand_in.write_text("x") + opened: list[int] = [] + state = { + "attrs": links._WIN_FILE_ATTRIBUTE_DIRECTORY | links._WIN_FILE_ATTRIBUTE_REPARSE_POINT, + "is_link": False, + } + real_fstat = os.fstat + + def _open(path, **_kwargs): + opened.append(os.open(stand_in, os.O_RDONLY)) + return opened[-1] + + def _fstat(fd, *args, **kwargs): + info = real_fstat(fd, *args, **kwargs) + if fd not in opened: + return info + return types.SimpleNamespace( + **{ + name: getattr(info, name) + for name in dir(info) + if name.startswith("st_") and name != "st_file_attributes" + }, + st_file_attributes=state["attrs"], + ) + + monkeypatch.setattr(links, "IS_POSIX", False) + monkeypatch.setattr(links, "_win_open_without_following", _open) + monkeypatch.setattr(links, "win_fd_is_link", lambda _fd: state["is_link"]) + monkeypatch.setattr(os, "fstat", _fstat) + return links, state + + def test_default_refuses_a_cloud_files_directory(self, windows_pin, tmp_path): + links, _state = windows_pin + with pytest.raises(NotADirectoryError): + links.pin_directory(tmp_path / "OneDrive") + + def test_links_only_pins_a_cloud_files_directory(self, windows_pin, tmp_path): + links, _state = windows_pin + fd = links.pin_directory(tmp_path / "OneDrive", links_only=True) + os.close(fd) + + def test_links_only_still_refuses_a_junction(self, windows_pin, tmp_path): + links, state = windows_pin + state["is_link"] = True + with pytest.raises(NotADirectoryError): + links.pin_directory(tmp_path / "junction", links_only=True) + + def test_links_only_still_refuses_a_file(self, windows_pin, tmp_path): + links, state = windows_pin + state["attrs"] = links._WIN_FILE_ATTRIBUTE_REPARSE_POINT + with pytest.raises(NotADirectoryError): + links.pin_directory(tmp_path / "placeholder.txt", links_only=True) + + # --------------------------------------------------------------------------- # POSIX-branch coverage for the new platform_compat helpers. The # tests below deliberately exercise the ``if IS_POSIX:`` / Linux ``/proc`` paths diff --git a/website/src/api/client/files.ts b/website/src/api/client/files.ts index 7edcf833422..505d4c119dc 100644 --- a/website/src/api/client/files.ts +++ b/website/src/api/client/files.ts @@ -2,7 +2,7 @@ * Files and projects on the gateway host: worktree creation, recent * projects, directory, drive and file browsing, project git status/log/tree, * named workspaces, reveal/open, the file picker, diff, fuzzy search, path - * completion, composer uploads and screenshots. + * completion, composer and Files-panel uploads and screenshots. */ import { COMPOSER_UPLOAD_PATH, uploadComposerFiles } from '../../utils/composerUpload' @@ -193,6 +193,35 @@ export function createFilesEndpoints({ post, put, del, j, jfetch: fetch, checkSe checkSessionExpired(res) return res }), + /** + * Upload one OS file into an existing workspace directory (Files panel + * drag-and-drop / "Upload files…" row menu). + * + * Sibling of `uploadFiles` above: that one always lands in the fixed + * chat-attachment scratch directory; this one targets whatever directory + * the caller resolved (a drop target row, or the tree root). One request + * per file — the caller loops for multiple files — so a name collision is + * reported per file rather than for a whole batch at once. Returns a + * discriminated result rather than throwing, so a `name_collision` (409) + * reads as data the caller can report instead of an exception to catch. + */ + uploadToDirectory: async ( + dir: string, file: File, + ): Promise< + { ok: true; path: string; name: string } | { ok: false; status: number; code?: string; error: string } + > => { + const fd = new FormData() + fd.append('file', file) + const q = new URLSearchParams({ dir }) + const res = await fetch(`/api/directory-upload?${q}`, { method: 'POST', body: fd }) + checkSessionExpired(res) + let body: { path?: string; name?: string; error?: string; code?: string } + try { body = await res.json() } catch { body = {} } + if (!res.ok) { + return { ok: false, status: res.status, code: body.code, error: body.error || res.statusText } + } + return { ok: true, path: body.path ?? '', name: body.name ?? file.name } + }, screenshot: () => post('/api/screenshot').then(j) as Promise<{ path: string }>, } diff --git a/website/src/components/ChatDropOverlay.tsx b/website/src/components/ChatDropOverlay.tsx index 5c05c2feb75..c8c335dff4f 100644 --- a/website/src/components/ChatDropOverlay.tsx +++ b/website/src/components/ChatDropOverlay.tsx @@ -104,8 +104,12 @@ export function useChatFileDrop( export default function ChatDropOverlay({ active, + label, }: { active: boolean + /** Overrides the default "Drop to attach" copy — the Files rail reuses + * this same overlay for its own drop target with its own wording. */ + label?: string }) { const reduceMotion = useReducedMotion() @@ -147,7 +151,7 @@ export default function ChatDropOverlay({ }} /> - {i18nT('components.chatInput.drop_to_attach')} + {label ?? i18nT('components.chatInput.drop_to_attach')} diff --git a/website/src/i18n/locales/bn.json b/website/src/i18n/locales/bn.json index b37dc4a1e2a..6db9f0f1bf8 100644 --- a/website/src/i18n/locales/bn.json +++ b/website/src/i18n/locales/bn.json @@ -11961,6 +11961,8 @@ "content_searching": "খোঁজা হচ্ছে…", "ctx_add_to_chat": "চ্যাটে যোগ করুন", "ctx_download": "ডাউনলোড", + "ctx_upload_files": "ফাইল আপলোড করো…", + "drop_to_upload": "{{folder}}-এ আপলোড করতে এখানে ছাড়ো", "filter_placeholder": "ফাইল ফিল্টার করুন…", "git_status_failed": "Git স্ট্যাটাস পড়া যায়নি", "mode_content": "বিষয়বস্তু", @@ -11972,7 +11974,15 @@ "search_names": "ফাইলের নাম খুঁজুন", "tree_mode": "ফাইল তালিকা মোড", "content_engine_slow": "এই হোস্টে ripgrep নেই, তাই অনুসন্ধানটি ধীরগতির অন্তর্নির্মিত স্ক্যানে চালানো হয়েছে।", - "content_one_row_per_file": "প্রতি ফাইলে একটি সারি" + "content_one_row_per_file": "প্রতি ফাইলে একটি সারি", + "upload_exists": "“{{name}}” এই ফোল্ডারে ইতিমধ্যে আছে। তোমার ফাইলের নাম বদলাও বা আগে থেকে থাকা ফাইলটি সরিয়ে দাও, তারপর আবার আপলোড করো।", + "upload_failed": "{{name}} আপলোড করা যায়নি", + "uploading_name": "{{name}} আপলোড হচ্ছে…", + "uploading_count_one": "{{count}}টি ফাইল আপলোড হচ্ছে…", + "uploading_count_other": "{{count}}টি ফাইল আপলোড হচ্ছে…", + "uploaded_name": "{{name}} {{folder}}-এ আপলোড হয়েছে।", + "uploaded_count_one": "{{count}}টি ফাইল {{folder}}-এ আপলোড হয়েছে।", + "uploaded_count_other": "{{count}}টি ফাইল {{folder}}-এ আপলোড হয়েছে।" }, "filesHome": { "no_project_dir": "এই চ্যাটের জন্য কোনো প্রজেক্ট ডিরেক্টরি সেট করা নেই", diff --git a/website/src/i18n/locales/de.json b/website/src/i18n/locales/de.json index 313ebebd055..7e96e408c91 100644 --- a/website/src/i18n/locales/de.json +++ b/website/src/i18n/locales/de.json @@ -11961,6 +11961,8 @@ "content_searching": "Suche läuft…", "ctx_add_to_chat": "Zum Chat hinzufügen", "ctx_download": "Herunterladen", + "ctx_upload_files": "Dateien hochladen…", + "drop_to_upload": "Ablegen, um in {{folder}} hochzuladen", "filter_placeholder": "Dateien filtern…", "git_status_failed": "Git-Status konnte nicht gelesen werden", "mode_content": "Inhalt", @@ -11972,7 +11974,15 @@ "search_names": "Dateinamen durchsuchen", "tree_mode": "Dateilisten-Modus", "content_engine_slow": "Auf diesem Host ist ripgrep nicht vorhanden, daher wurde die langsamere integrierte Suche verwendet.", - "content_one_row_per_file": "eine Zeile pro Datei" + "content_one_row_per_file": "eine Zeile pro Datei", + "upload_exists": "„{{name}}“ existiert bereits in diesem Ordner. Benenne deine Datei um oder entferne die vorhandene und lade dann erneut hoch.", + "upload_failed": "Hochladen von {{name}} fehlgeschlagen", + "uploading_name": "{{name}} wird hochgeladen…", + "uploading_count_one": "{{count}} Datei wird hochgeladen…", + "uploading_count_other": "{{count}} Dateien werden hochgeladen…", + "uploaded_name": "{{name}} wurde in {{folder}} hochgeladen.", + "uploaded_count_one": "{{count}} Datei wurde in {{folder}} hochgeladen.", + "uploaded_count_other": "{{count}} Dateien wurden in {{folder}} hochgeladen." }, "filesHome": { "no_project_dir": "Für diesen Chat ist kein Projektverzeichnis festgelegt", diff --git a/website/src/i18n/locales/en-XA.json b/website/src/i18n/locales/en-XA.json index 45f9b9bbedc..bd7279de28f 100644 --- a/website/src/i18n/locales/en-XA.json +++ b/website/src/i18n/locales/en-XA.json @@ -11702,6 +11702,8 @@ "content_searching": "[Şèàŕçĥìñğ… ···············]", "ctx_add_to_chat": "[Àðð ţø çĥàţ ··········]", "ctx_download": "[Ðøẁñĺøàð ············]", + "ctx_upload_files": "[Ùþĺøàð ƒìĺèş… ············]", + "drop_to_upload": "[Ðŕøþ ţø ùþĺøàð ìñţø {{folder}} ··················]", "filter_placeholder": "[Ƒìĺţèŕ ƒìĺèş… ············]", "git_status_failed": "[Çøùĺðñ'ţ ŕèàð ţĥè ğìţ şţàţùş ····················]", "mode_content": "[Çøñţèñţ ···········]", @@ -11713,7 +11715,15 @@ "search_names": "[Şèàŕçĥ ƒìĺè ñàɱèş ···············]", "tree_mode": "[Ƒìĺè ĺìşţ ɱøðè ·············]", "content_engine_slow": "[Ţĥìş ĥøşţ ĥàş ñø ŕìþğŕèþ, şø ţĥè şèàŕçĥ ţøøķ ţĥè şĺøẁèŕ ƀùìĺţ-ìñ şçàñ. ·························]", - "content_one_row_per_file": "[øñè ŕøẁ þèŕ ƒìĺè ··············]" + "content_one_row_per_file": "[øñè ŕøẁ þèŕ ƒìĺè ··············]", + "upload_exists": "[“{{name}}” àĺŕèàðý èẋìşţş ìñ ţĥìş ƒøĺðèŕ. Ŕèñàɱè ýøùŕ ƒìĺè, øŕ ŕèɱøṽè ţĥè èẋìşţìñğ øñè, ţĥèñ ùþĺøàð àğàìñ. ·····························]", + "upload_failed": "[Çøùĺðñ'ţ ùþĺøàð {{name}} ··············]", + "uploading_name": "[Ùþĺøàðìñğ {{name}}… ··········]", + "uploading_count_one": "[Ùþĺøàðìñğ {{count}} ƒìĺè… ··············]", + "uploading_count_other": "[Ùþĺøàðìñğ {{count}} ƒìĺèş… ···············]", + "uploaded_name": "[Ùþĺøàðèð {{name}} ţø {{folder}}. ·············]", + "uploaded_count_one": "[Ùþĺøàðèð {{count}} ƒìĺè ţø {{folder}}. ·················]", + "uploaded_count_other": "[Ùþĺøàðèð {{count}} ƒìĺèş ţø {{folder}}. ··················]" }, "filesHome": { "no_project_dir": "[Ñø þŕøĵèçţ ðìŕèçţøŕý ìş şèţ ƒøŕ ţĥìş çĥàţ ·····················]", diff --git a/website/src/i18n/locales/en.manual.json b/website/src/i18n/locales/en.manual.json index e0fc4415dea..64a8b546866 100644 --- a/website/src/i18n/locales/en.manual.json +++ b/website/src/i18n/locales/en.manual.json @@ -5304,6 +5304,8 @@ "content_searching": "Searching…", "ctx_add_to_chat": "Add to chat", "ctx_download": "Download", + "ctx_upload_files": "Upload files…", + "drop_to_upload": "Drop to upload into {{folder}}", "filter_placeholder": "Filter files…", "git_status_failed": "Couldn't read the git status", "mode_content": "Content", @@ -5315,7 +5317,15 @@ "search_names": "Search file names", "tree_mode": "File list mode", "content_engine_slow": "This host has no ripgrep, so the search took the slower built-in scan.", - "content_one_row_per_file": "one row per file" + "content_one_row_per_file": "one row per file", + "upload_exists": "“{{name}}” already exists in this folder. Rename your file, or remove the existing one, then upload again.", + "upload_failed": "Couldn't upload {{name}}", + "uploading_name": "Uploading {{name}}…", + "uploading_count_one": "Uploading {{count}} file…", + "uploading_count_other": "Uploading {{count}} files…", + "uploaded_name": "Uploaded {{name}} to {{folder}}.", + "uploaded_count_one": "Uploaded {{count}} file to {{folder}}.", + "uploaded_count_other": "Uploaded {{count}} files to {{folder}}." }, "filesHome": { "no_project_dir": "No project directory is set for this chat", diff --git a/website/src/i18n/locales/es.json b/website/src/i18n/locales/es.json index ae22e2af441..35b63f1d2b2 100644 --- a/website/src/i18n/locales/es.json +++ b/website/src/i18n/locales/es.json @@ -12144,6 +12144,8 @@ "content_searching": "Buscando…", "ctx_add_to_chat": "Añadir al chat", "ctx_download": "Descargar", + "ctx_upload_files": "Subir archivos…", + "drop_to_upload": "Suelta para subir a {{folder}}", "filter_placeholder": "Filtrar archivos…", "git_status_failed": "No se pudo leer el estado de git", "mode_content": "Contenido", @@ -12155,7 +12157,17 @@ "search_names": "Buscar nombres de archivo", "tree_mode": "Modo de lista de archivos", "content_engine_slow": "Este host no tiene ripgrep, por lo que la búsqueda usó el escaneo interno más lento.", - "content_one_row_per_file": "una fila por archivo" + "content_one_row_per_file": "una fila por archivo", + "upload_exists": "“{{name}}” ya existe en esta carpeta. Cambia el nombre de tu archivo o elimina el existente y vuelve a subirlo.", + "upload_failed": "No se pudo subir {{name}}", + "uploading_name": "Subiendo {{name}}…", + "uploading_count_one": "Subiendo {{count}} archivo…", + "uploading_count_many": "Subiendo {{count}} archivos…", + "uploading_count_other": "Subiendo {{count}} archivos…", + "uploaded_name": "Se subió {{name}} a {{folder}}.", + "uploaded_count_one": "Se subió {{count}} archivo a {{folder}}.", + "uploaded_count_many": "Se subieron {{count}} archivos a {{folder}}.", + "uploaded_count_other": "Se subieron {{count}} archivos a {{folder}}." }, "filesHome": { "no_project_dir": "Este chat no tiene un directorio de proyecto", diff --git a/website/src/i18n/locales/fr.json b/website/src/i18n/locales/fr.json index ee3500f75c4..2be09826805 100644 --- a/website/src/i18n/locales/fr.json +++ b/website/src/i18n/locales/fr.json @@ -12144,6 +12144,8 @@ "content_searching": "Recherche…", "ctx_add_to_chat": "Ajouter à la conversation", "ctx_download": "Télécharger", + "ctx_upload_files": "Importer des fichiers…", + "drop_to_upload": "Dépose pour importer dans {{folder}}", "filter_placeholder": "Filtrer les fichiers…", "git_status_failed": "Impossible de lire l’état git", "mode_content": "Contenu", @@ -12155,7 +12157,17 @@ "search_names": "Rechercher dans les noms de fichiers", "tree_mode": "Mode liste de fichiers", "content_engine_slow": "Cet hôte n'a pas ripgrep, la recherche a donc utilisé le balayage interne plus lent.", - "content_one_row_per_file": "une ligne par fichier" + "content_one_row_per_file": "une ligne par fichier", + "upload_exists": "« {{name}} » existe déjà dans ce dossier. Renomme ton fichier ou supprime celui qui existe, puis importe-le à nouveau.", + "upload_failed": "Impossible d'importer {{name}}", + "uploading_name": "Importation de {{name}}…", + "uploading_count_one": "Importation de {{count}} fichier…", + "uploading_count_many": "Importation de {{count}} fichiers…", + "uploading_count_other": "Importation de {{count}} fichiers…", + "uploaded_name": "{{name}} importé dans {{folder}}.", + "uploaded_count_one": "{{count}} fichier importé dans {{folder}}.", + "uploaded_count_many": "{{count}} fichiers importés dans {{folder}}.", + "uploaded_count_other": "{{count}} fichiers importés dans {{folder}}." }, "filesHome": { "no_project_dir": "Aucun répertoire de projet n'est défini pour cette discussion", diff --git a/website/src/i18n/locales/hi.json b/website/src/i18n/locales/hi.json index 9d00885e72e..78b4e907040 100644 --- a/website/src/i18n/locales/hi.json +++ b/website/src/i18n/locales/hi.json @@ -11961,6 +11961,8 @@ "content_searching": "खोज रहे हैं…", "ctx_add_to_chat": "चैट में जोड़ें", "ctx_download": "डाउनलोड करें", + "ctx_upload_files": "फ़ाइलें अपलोड करो…", + "drop_to_upload": "{{folder}} में अपलोड करने के लिए यहाँ छोड़ो", "filter_placeholder": "फ़ाइलें फ़िल्टर करें…", "git_status_failed": "Git स्थिति पढ़ी नहीं जा सकी", "mode_content": "सामग्री", @@ -11972,7 +11974,15 @@ "search_names": "फ़ाइल नाम खोजें", "tree_mode": "फ़ाइल सूची मोड", "content_engine_slow": "इस होस्ट पर ripgrep नहीं है, इसलिए खोज धीमे अंतर्निहित स्कैन से की गई।", - "content_one_row_per_file": "प्रति फ़ाइल एक पंक्ति" + "content_one_row_per_file": "प्रति फ़ाइल एक पंक्ति", + "upload_exists": "“{{name}}” इस फ़ोल्डर में पहले से मौजूद है। अपनी फ़ाइल का नाम बदलो या मौजूदा फ़ाइल हटाओ, फिर दोबारा अपलोड करो।", + "upload_failed": "{{name}} अपलोड नहीं हो सकी", + "uploading_name": "{{name}} अपलोड हो रही है…", + "uploading_count_one": "{{count}} फ़ाइल अपलोड हो रही है…", + "uploading_count_other": "{{count}} फ़ाइलें अपलोड हो रही हैं…", + "uploaded_name": "{{name}} {{folder}} में अपलोड हो गई।", + "uploaded_count_one": "{{count}} फ़ाइल {{folder}} में अपलोड हो गई।", + "uploaded_count_other": "{{count}} फ़ाइलें {{folder}} में अपलोड हो गईं।" }, "filesHome": { "no_project_dir": "इस चैट के लिए कोई प्रोजेक्ट डायरेक्टरी सेट नहीं है", diff --git a/website/src/i18n/locales/it.json b/website/src/i18n/locales/it.json index 779a6cec5bf..0f1e6581adf 100644 --- a/website/src/i18n/locales/it.json +++ b/website/src/i18n/locales/it.json @@ -12144,6 +12144,8 @@ "content_searching": "Ricerca in corso…", "ctx_add_to_chat": "Aggiungi alla chat", "ctx_download": "Scarica", + "ctx_upload_files": "Carica file…", + "drop_to_upload": "Rilascia per caricare in {{folder}}", "filter_placeholder": "Filtra file…", "git_status_failed": "Impossibile leggere lo stato di git", "mode_content": "Contenuto", @@ -12155,7 +12157,17 @@ "search_names": "Cerca nei nomi dei file", "tree_mode": "Modalità elenco file", "content_engine_slow": "Questo host non ha ripgrep, quindi la ricerca ha usato la scansione interna più lenta.", - "content_one_row_per_file": "una riga per file" + "content_one_row_per_file": "una riga per file", + "upload_exists": "«{{name}}» esiste già in questa cartella. Rinomina il tuo file o rimuovi quello esistente, quindi caricalo di nuovo.", + "upload_failed": "Impossibile caricare {{name}}", + "uploading_name": "Caricamento di {{name}}…", + "uploading_count_one": "Caricamento di {{count}} file…", + "uploading_count_many": "Caricamento di {{count}} file…", + "uploading_count_other": "Caricamento di {{count}} file…", + "uploaded_name": "{{name}} caricato in {{folder}}.", + "uploaded_count_one": "{{count}} file caricato in {{folder}}.", + "uploaded_count_many": "{{count}} file caricati in {{folder}}.", + "uploaded_count_other": "{{count}} file caricati in {{folder}}." }, "filesHome": { "no_project_dir": "Nessuna directory di progetto impostata per questa chat", diff --git a/website/src/i18n/locales/ja.json b/website/src/i18n/locales/ja.json index 4bf64c70399..e8710c66ff1 100644 --- a/website/src/i18n/locales/ja.json +++ b/website/src/i18n/locales/ja.json @@ -11778,6 +11778,8 @@ "content_searching": "検索中…", "ctx_add_to_chat": "チャットに追加", "ctx_download": "ダウンロード", + "ctx_upload_files": "ファイルをアップロード…", + "drop_to_upload": "ここにドロップすると「{{folder}}」にアップロードします", "filter_placeholder": "ファイルを絞り込む…", "git_status_failed": "Git の状態を読み取れませんでした", "mode_content": "内容", @@ -11789,7 +11791,13 @@ "search_names": "ファイル名を検索", "tree_mode": "ファイル一覧モード", "content_engine_slow": "このホストには ripgrep がないため、検索は低速な内蔵スキャンで実行されました。", - "content_one_row_per_file": "1 ファイルにつき 1 行" + "content_one_row_per_file": "1 ファイルにつき 1 行", + "upload_exists": "「{{name}}」はこのフォルダーに既に存在します。アップロードするファイルの名前を変更するか、既存のファイルを削除してから、もう一度アップロードしてください。", + "upload_failed": "{{name}} をアップロードできませんでした", + "uploading_name": "{{name}} をアップロードしています…", + "uploading_count_other": "{{count}} 個のファイルをアップロードしています…", + "uploaded_name": "{{name}} を「{{folder}}」にアップロードしました。", + "uploaded_count_other": "{{count}} 個のファイルを「{{folder}}」にアップロードしました。" }, "filesHome": { "no_project_dir": "このチャットにはプロジェクトディレクトリが設定されていません", diff --git a/website/src/i18n/locales/ko.json b/website/src/i18n/locales/ko.json index 83028e5b6a3..f71d7bd6f9d 100644 --- a/website/src/i18n/locales/ko.json +++ b/website/src/i18n/locales/ko.json @@ -11778,6 +11778,8 @@ "content_searching": "검색 중…", "ctx_add_to_chat": "채팅에 추가", "ctx_download": "다운로드", + "ctx_upload_files": "파일 업로드…", + "drop_to_upload": "여기에 놓으면 ‘{{folder}}’에 업로드합니다", "filter_placeholder": "파일 필터링…", "git_status_failed": "Git 상태를 읽을 수 없습니다", "mode_content": "내용", @@ -11789,7 +11791,13 @@ "search_names": "파일 이름 검색", "tree_mode": "파일 목록 모드", "content_engine_slow": "이 호스트에는 ripgrep이 없으므로 검색이 더 느린 내장 스캔으로 실행되었습니다.", - "content_one_row_per_file": "파일당 한 행" + "content_one_row_per_file": "파일당 한 행", + "upload_exists": "‘{{name}}’이(가) 이 폴더에 이미 있습니다. 업로드할 파일의 이름을 바꾸거나 기존 파일을 삭제한 다음 다시 업로드하세요.", + "upload_failed": "{{name}} 업로드에 실패했습니다", + "uploading_name": "{{name}} 업로드 중…", + "uploading_count_other": "파일 {{count}}개 업로드 중…", + "uploaded_name": "{{name}}을(를) ‘{{folder}}’에 업로드했습니다.", + "uploaded_count_other": "파일 {{count}}개를 ‘{{folder}}’에 업로드했습니다." }, "filesHome": { "no_project_dir": "이 채팅에는 프로젝트 디렉터리가 설정되어 있지 않습니다", diff --git a/website/src/i18n/locales/pt.json b/website/src/i18n/locales/pt.json index 12bbc9ea116..e04ec6c8186 100644 --- a/website/src/i18n/locales/pt.json +++ b/website/src/i18n/locales/pt.json @@ -12144,6 +12144,8 @@ "content_searching": "Buscando…", "ctx_add_to_chat": "Adicionar à conversa", "ctx_download": "Baixar", + "ctx_upload_files": "Enviar arquivos…", + "drop_to_upload": "Solte para enviar para {{folder}}", "filter_placeholder": "Filtrar arquivos…", "git_status_failed": "Não foi possível ler o status do git", "mode_content": "Conteúdo", @@ -12155,7 +12157,17 @@ "search_names": "Buscar nomes de arquivo", "tree_mode": "Modo de lista de arquivos", "content_engine_slow": "Este host não tem ripgrep, portanto a busca usou a varredura interna mais lenta.", - "content_one_row_per_file": "uma linha por arquivo" + "content_one_row_per_file": "uma linha por arquivo", + "upload_exists": "“{{name}}” já existe nesta pasta. Renomeie o seu arquivo ou remova o existente e envie novamente.", + "upload_failed": "Não foi possível enviar {{name}}", + "uploading_name": "Enviando {{name}}…", + "uploading_count_one": "Enviando {{count}} arquivo…", + "uploading_count_many": "Enviando {{count}} arquivos…", + "uploading_count_other": "Enviando {{count}} arquivos…", + "uploaded_name": "{{name}} enviado para {{folder}}.", + "uploaded_count_one": "{{count}} arquivo enviado para {{folder}}.", + "uploaded_count_many": "{{count}} arquivos enviados para {{folder}}.", + "uploaded_count_other": "{{count}} arquivos enviados para {{folder}}." }, "filesHome": { "no_project_dir": "Nenhum diretório de projeto definido para este chat", diff --git a/website/src/i18n/locales/ru.json b/website/src/i18n/locales/ru.json index b50bc8e21ac..777c95aa784 100644 --- a/website/src/i18n/locales/ru.json +++ b/website/src/i18n/locales/ru.json @@ -12327,6 +12327,8 @@ "content_searching": "Поиск…", "ctx_add_to_chat": "Добавить в чат", "ctx_download": "Скачать", + "ctx_upload_files": "Загрузить файлы…", + "drop_to_upload": "Отпусти, чтобы загрузить в {{folder}}", "filter_placeholder": "Фильтр файлов…", "git_status_failed": "Не удалось прочитать статус git", "mode_content": "Содержимое", @@ -12338,7 +12340,19 @@ "search_names": "Искать в именах файлов", "tree_mode": "Режим списка файлов", "content_engine_slow": "На этом хосте нет ripgrep, поэтому поиск выполнен более медленным встроенным способом.", - "content_one_row_per_file": "по одной строке на файл" + "content_one_row_per_file": "по одной строке на файл", + "upload_exists": "«{{name}}» уже есть в этой папке. Переименуй свой файл или удали существующий, затем загрузи снова.", + "upload_failed": "Не удалось загрузить {{name}}", + "uploading_name": "Загрузка {{name}}…", + "uploading_count_one": "Загрузка {{count}} файла…", + "uploading_count_few": "Загрузка {{count}} файлов…", + "uploading_count_many": "Загрузка {{count}} файлов…", + "uploading_count_other": "Загрузка {{count}} файла…", + "uploaded_name": "Файл {{name}} загружен в {{folder}}.", + "uploaded_count_one": "{{count}} файл загружен в {{folder}}.", + "uploaded_count_few": "{{count}} файла загружены в {{folder}}.", + "uploaded_count_many": "{{count}} файлов загружено в {{folder}}.", + "uploaded_count_other": "{{count}} файла загружено в {{folder}}." }, "filesHome": { "no_project_dir": "Для этого чата не задан каталог проекта", diff --git a/website/src/i18n/locales/zh-CN.json b/website/src/i18n/locales/zh-CN.json index d86947962a1..f9dd9338da0 100644 --- a/website/src/i18n/locales/zh-CN.json +++ b/website/src/i18n/locales/zh-CN.json @@ -11778,6 +11778,8 @@ "content_searching": "正在搜索…", "ctx_add_to_chat": "添加到聊天", "ctx_download": "下载", + "ctx_upload_files": "上传文件…", + "drop_to_upload": "拖放到此处,上传到“{{folder}}”", "filter_placeholder": "筛选文件…", "git_status_failed": "无法读取 Git 状态", "mode_content": "内容", @@ -11789,7 +11791,13 @@ "search_names": "搜索文件名", "tree_mode": "文件列表模式", "content_engine_slow": "此主机没有 ripgrep,因此搜索使用了较慢的内置扫描。", - "content_one_row_per_file": "每个文件一行" + "content_one_row_per_file": "每个文件一行", + "upload_exists": "此文件夹中已存在“{{name}}”。重命名你的文件,或删除已有的同名文件,然后重新上传。", + "upload_failed": "无法上传 {{name}}", + "uploading_name": "正在上传 {{name}}…", + "uploading_count_other": "正在上传 {{count}} 个文件…", + "uploaded_name": "已将 {{name}} 上传到“{{folder}}”。", + "uploaded_count_other": "已将 {{count}} 个文件上传到“{{folder}}”。" }, "filesHome": { "no_project_dir": "此聊天未设置项目目录", diff --git a/website/src/i18n/pluralKeys.json b/website/src/i18n/pluralKeys.json index 42da3368ef0..bf36866a7c3 100644 --- a/website/src/i18n/pluralKeys.json +++ b/website/src/i18n/pluralKeys.json @@ -293,5 +293,7 @@ "pages.settings.chatPanel.follow_default_more", "pages.membersPage.goals_heartbeat_per_day", "appSdk.newMessagesDivider.new_messages", - "pages.chatPage.jump_to_new" + "pages.chatPage.jump_to_new", + "pages.chat.fileBrowserRail.uploading_count", + "pages.chat.fileBrowserRail.uploaded_count" ] diff --git a/website/src/pages/chat/FileBrowserRail.tsx b/website/src/pages/chat/FileBrowserRail.tsx index e5a72fe59d9..30a8f7a73fe 100644 --- a/website/src/pages/chat/FileBrowserRail.tsx +++ b/website/src/pages/chat/FileBrowserRail.tsx @@ -5,7 +5,9 @@ import { useTranslation } from 'react-i18next' import { Files, Diff, Search, X, RefreshCw, FileText, Folder } from 'lucide-react' import { api } from '../../api/client' import { fileGrep, type FileGrepHit } from '../../api/fileGrep' +import ChatDropOverlay, { useChatFileDrop } from '../../components/ChatDropOverlay' import ErrorNotice from '../../components/ErrorNotice' +import StatusNotice from '../../components/StatusNotice' import { gitFilterRefusalCause, gitFilterRefusalCopyKey, @@ -26,6 +28,8 @@ import { PierreWorkspaceTree, type TreeReveal } from '../../pierre/tree' import { mergeTreeMatches } from '../../lib/treeSearchMatches' import { findReport, reportForError } from '../../utils/errorReport' import { errMessage } from '../../utils/thunkError' +import { pathBasename, stripTrailingSeparators } from '../../utils/pathBasename' +import { useDirectoryUpload } from './useDirectoryUpload' /** Rail width bounds; the grip clamps between them. */ const RAIL_MIN_W = 300 @@ -541,6 +545,26 @@ export default function FileBrowserRail({ projectDir, onFileOpen, onAddToContext )}` : t('pages.chat.fileBrowserRail.changed') + // A drop anywhere on the rail uploads into the project root: the overlay + // covers the tree, so no row under the pointer is visible to choose. A + // specific folder is reached through its row menu's "Upload files…". The + // tree computes the same fallback (`tree?.root ?? projectDir`) because the + // backend answers with the realpath, which can differ from a symlinked + // projectDir. + const root = treeData?.root ?? projectDir + // The name the Files tab header shows for the project (FilesHomePanel). + const rootName = pathBasename(stripTrailingSeparators(projectDir)) || projectDir + + const { + uploadFiles, pickAndUpload, progress: uploadProgress, error: uploadError, dismissError, + success: uploadSuccess, dismissSuccess, fileInput, + } = useDirectoryUpload(projectDir, root) + const { active: dragOver, dropTargetProps } = useChatFileDrop((dataTransfer) => { + const files = Array.from(dataTransfer.files) + if (!files.length) return + void uploadFiles(root, files) + }) + // Both queries poll (10s tree / 5s status); this is the "I changed something // outside the app, show me now" escape hatch. `refetchQueries` (not // `invalidateQueries`) so `refreshing` tracks the actual network round trip @@ -618,9 +642,11 @@ export default function FileBrowserRail({ projectDir, onFileOpen, onAddToContext /> )}

+
{/* All/Changed scopes the TREE, and Content mode has no tree. Left rendered it kept its "Changed" highlight while the content results @@ -799,6 +825,30 @@ export default function FileBrowserRail({ projectDir, onFileOpen, onAddToContext />
)} + {(uploadProgress || uploadSuccess) && ( +
+ + +
+ )} + {/* Files rail, no draft to lose on hand-off: the tree itself holds no + unsaved input, so askAgent is safe to leave on. */} + {uploadError && ( +
+ +
+ )}
{contentMode ? ( onFileOpen(abs, changedMode)} onAddToContext={onAddToContext} + onUploadRequest={pickAndUpload} searchQuery={nameSearch ? null : query || null} selectedPath={selectedPath ?? null} onOpenFolder={onOpenFolder ? folder => onOpenFolder(`${projectRoot}/${folder}`) : undefined} @@ -858,6 +909,7 @@ export default function FileBrowserRail({ projectDir, onFileOpen, onAddToContext )}
+ {fileInput} ) } diff --git a/website/src/pages/chat/useDirectoryUpload.tsx b/website/src/pages/chat/useDirectoryUpload.tsx new file mode 100644 index 00000000000..22cb48825b4 --- /dev/null +++ b/website/src/pages/chat/useDirectoryUpload.tsx @@ -0,0 +1,221 @@ +import { useCallback, useEffect, useRef, useState, type ChangeEvent, type ReactNode } from 'react' +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { api } from '../../api/client' +import { i18nT } from '../../i18n/t' +import { backslashIsSeparator, pathBasename, stripTrailingSeparators } from '../../utils/pathBasename' + +export interface DirectoryUploadHandle { + /** Upload one or more OS files into `absDir` (the tree root for a drop on + * the rail). A name that already exists in + * `absDir` is reported in `error`; the existing file is never replaced. + * A name already uploading into `absDir` is skipped, not sent again. */ + uploadFiles: (absDir: string, files: File[]) => Promise + /** Opens the OS file picker and uploads the selection into `absDir` — the + * "Upload files…" row-menu action's click path. */ + pickAndUpload: (absDir: string) => void + /** "Uploading report.md…" / "Uploading 3 files…" while a batch runs, or null. */ + progress: string | null + /** The most recent upload failure, or null. */ + error: string | null + dismissError: () => void + /** "Uploaded report.md to docs." after a batch with no failures, or null. */ + success: string | null + dismissSuccess: () => void + /** Render once in the host's JSX: the hidden file-picker input. */ + fileInput: ReactNode +} + +/** `p` with `/` separators and no trailing separator, for comparing paths. */ +function comparablePath(p: string): string { + return stripTrailingSeparators(backslashIsSeparator(p) ? p.replace(/\\/g, '/') : p) +} + +interface RunningBatch { + id: number + names: string[] +} + +/** + * Upload orchestration shared by the Files rail's drop overlay and its + * "Upload files…" row-menu action — one place for the collision message, the + * query invalidation that makes a new file appear in every file listing and + * search, and the progress, success and error surface, so the two entry points + * cannot drift apart. + * + * `rootDir` is the directory the tree lists (the backend's realpath of + * `projectDir`, which can differ for a symlinked project); the success line + * names a target folder relative to it, and the root itself by the project's + * folder name. + */ +export function useDirectoryUpload(projectDir: string, rootDir: string = projectDir): DirectoryUploadHandle { + const qc = useQueryClient() + const [error, setError] = useState(null) + const [success, setSuccess] = useState(null) + const [running, setRunning] = useState([]) + // A chat-slot switch changes projectDir in place without remounting the + // rail (React's adjust-state-on-prop pattern, synchronous before paint): + // one project's notices must not show in another project's panel. + const [errorDir, setErrorDir] = useState(projectDir) + if (errorDir !== projectDir) { + setErrorDir(projectDir) + setError(null) + setSuccess(null) + setRunning([]) + } + // The project the rail shows now; an upload batch that finishes after a + // switch reads it to keep its failures out of the new project's panel. + const currentDirRef = useRef(projectDir) + useEffect(() => { + currentDirRef.current = projectDir + }, [projectDir]) + const inputRef = useRef(null) + const pendingDirRef = useRef(null) + // `${dir}/${name}` of every file being sent now, across batches. + const inFlightRef = useRef(new Set()) + const batchIdRef = useRef(0) + + // Every cached listing or search of workspace files an upload can add a row + // to. The first three are keyed by `projectDir`. The rest are keyed by a + // root this hook cannot know, so they are invalidated by prefix: the rail's + // filename search uses the backend's realpath of the project, and the folder + // tab's listing and search use whatever folder that tab is showing. + const invalidate = useCallback(() => { + qc.invalidateQueries({ queryKey: ['project-tree', projectDir] }) + qc.invalidateQueries({ queryKey: ['git-status', projectDir] }) + qc.invalidateQueries({ queryKey: ['file-grep', projectDir] }) + qc.invalidateQueries({ queryKey: ['rail-name-search'] }) + qc.invalidateQueries({ queryKey: ['browse-files'] }) + qc.invalidateQueries({ queryKey: ['folder-file-search'] }) + }, [qc, projectDir]) + + // The actual write is a server-state mutation, so it goes through + // useMutation/mutateAsync rather than a bare fetch call (website/AGENTS.md: + // "Data fetching is React Query"). + const uploadMutation = useMutation({ + mutationFn: ({ absDir, file }: { absDir: string; file: File }) => + api.uploadToDirectory(absDir, file), + }) + + // api.uploadToDirectory wraps fetch(); a transport failure (offline, DNS, + // an aborted request) REJECTS that promise instead of resolving an + // {ok:false} result -- and mutateAsync propagates that same rejection. + // Every caller here fires uploadFiles without awaiting it + // (`void uploadFiles(...)`), so an unwrapped rejection becomes an unhandled + // promise rejection: no error state, no invalidate, and the drop or picker + // selection appears to do nothing. Catching here folds a thrown error into + // the SAME {ok:false} shape the API already returns for a non-2xx + // response, so uploadOne's own handling covers both without a second + // error path. + const attemptUpload = useCallback(async ( + absDir: string, file: File, + ): Promise>> => { + try { + return await uploadMutation.mutateAsync({ absDir, file }) + } catch (err) { + return { ok: false, status: 0, error: err instanceof Error ? err.message : String(err) } + } + }, [uploadMutation]) + + const uploadOne = useCallback(async (absDir: string, file: File): Promise => { + const result = await attemptUpload(absDir, file) + if (result.ok) return null + if (result.code === 'name_collision') { + return i18nT('pages.chat.fileBrowserRail.upload_exists', { name: file.name }) + } + return `${i18nT('pages.chat.fileBrowserRail.upload_failed', { name: file.name })}: ${result.error}` + }, [attemptUpload]) + + // The folder a success line names: its path under the tree root, or the + // project's folder name (what the Files tab header shows) for the root. + const folderLabel = useCallback((absDir: string): string => { + const dir = comparablePath(absDir) + const root = comparablePath(rootDir) + if (dir === root || dir === comparablePath(projectDir)) { + return pathBasename(comparablePath(projectDir)) || projectDir + } + if (dir.startsWith(`${root}/`)) return dir.slice(root.length + 1) + return pathBasename(dir) || dir + }, [rootDir, projectDir]) + + const uploadFiles = useCallback(async (absDir: string, files: File[]) => { + // A name still uploading into this folder (typically dropped again because + // the first drop seemed to do nothing) is not sent a second time: the server + // would refuse it as a collision with the copy it is still writing. The + // running batch's progress line already covers it. + const dirKey = comparablePath(absDir) + const batch: File[] = [] + const keys: string[] = [] + for (const file of files) { + const key = `${dirKey}/${file.name}` + if (inFlightRef.current.has(key)) continue + inFlightRef.current.add(key) + keys.push(key) + batch.push(file) + } + if (batch.length === 0) return + const id = ++batchIdRef.current + setError(null) + setSuccess(null) + setRunning(prev => [...prev, { id, names: batch.map(f => f.name) }]) + const failures: string[] = [] + for (const file of batch) { + const failure = await uploadOne(absDir, file) + if (failure) failures.push(failure) + } + for (const key of keys) inFlightRef.current.delete(key) + setRunning(prev => prev.filter(b => b.id !== id)) + if (currentDirRef.current === projectDir) { + if (failures.length > 0) { + setError(failures.join('; ')) + } else { + const folder = folderLabel(absDir) + setSuccess(batch.length === 1 + ? i18nT('pages.chat.fileBrowserRail.uploaded_name', { name: batch[0].name, folder }) + : i18nT('pages.chat.fileBrowserRail.uploaded_count', { count: batch.length, folder })) + } + } + invalidate() + }, [uploadOne, invalidate, projectDir, folderLabel]) + + const runningNames = running.flatMap(b => b.names) + const progress = runningNames.length === 0 + ? null + : runningNames.length === 1 + ? i18nT('pages.chat.fileBrowserRail.uploading_name', { name: runningNames[0] }) + : i18nT('pages.chat.fileBrowserRail.uploading_count', { count: runningNames.length }) + + const pickAndUpload = useCallback((absDir: string) => { + pendingDirRef.current = absDir + inputRef.current?.click() + }, []) + + const onInputChange = useCallback((e: ChangeEvent) => { + const dir = pendingDirRef.current + const files = e.target.files ? Array.from(e.target.files) : [] + // Reset so picking the SAME file again still fires a change event. + e.target.value = '' + if (dir && files.length) void uploadFiles(dir, files) + }, [uploadFiles]) + + const fileInput = ( + + ) + + return { + uploadFiles, + pickAndUpload, + progress, + error, + dismissError: () => setError(null), + success, + dismissSuccess: () => setSuccess(null), + fileInput, + } +} diff --git a/website/src/pierre/PierreWorkspaceTreeImpl.tsx b/website/src/pierre/PierreWorkspaceTreeImpl.tsx index 787891d4e55..c3da2d6809e 100644 --- a/website/src/pierre/PierreWorkspaceTreeImpl.tsx +++ b/website/src/pierre/PierreWorkspaceTreeImpl.tsx @@ -20,7 +20,7 @@ import type { ContextMenuOpenContext as FileTreeContextMenuOpenContext, } from '@pierre/trees' import { FileTree, useFileTree } from '@pierre/trees/react' -import { AtSign, Download, FileDiff, FolderDot, FolderLock, FolderOpen, Undo2 } from 'lucide-react' +import { AtSign, Download, FileDiff, FolderDot, FolderLock, FolderOpen, Undo2, Upload } from 'lucide-react' import { api } from '../api/client' import { ApiError } from '../api/apiError' import ErrorNotice from '../components/ErrorNotice' @@ -120,15 +120,20 @@ function expandedDirectoriesOf( * anchor, the outside-click wash, and open/close (the portal root's * `data-file-tree-context-menu-root` marker is the library's documented way to * keep a portaled surface counting as "inside"); this renders only the item - * list: the built-in "Add to chat" action, plus any row an installed app - * contributes for the `tree-context` surface (row click already opens a file, - * so the menu deliberately carries no Open duplicate). Every action closes the - * menu itself so focus returns to the row. */ -function TreeContextMenu({ item, context, root, onAddToContext, contribItems, onError }: { + * list: the built-in "Add to chat" action, the built-in "Upload + * files…" action (DIRECTORY rows only), plus any + * row an installed app contributes for the `tree-context` surface (row click + * already opens a file, so the menu deliberately carries no Open duplicate). + * Every action closes the menu itself so focus returns to the row. */ +function TreeContextMenu({ item, context, root, onAddToContext, onUploadRequest, contribItems, onError }: { item: FileTreeContextMenuItem context: FileTreeContextMenuOpenContext root: string onAddToContext?: (absPath: string, kind: TreeEntryKind) => void + /** Right-click "Upload files…" on a directory row: hands the host the + * ABSOLUTE directory path so it can open the OS file picker and upload + * the selection into it. Absent → the row is not rendered. */ + onUploadRequest?: (absDirPath: string) => void /** Contributed `tree-context` rows, resolved by the PARENT (which already holds * the `['apps']` query) and passed down. Deliberately a prop rather than a hook * call here: this component mounts inside Pierre's context-menu slot, and a @@ -175,6 +180,11 @@ function TreeContextMenu({ item, context, root, onAddToContext, contribItems, on context.close() run() } + // "Upload files…" renders only on a directory row, and only when the host + // supplied the handler: this same component instance answers every row the + // tree opens a menu for, and a FILE row must never show the directory-only + // upload action even when the host wired it for other rows. + const showUpload = isDir && !!onUploadRequest // Focus the first item on open. Pierre's own open path calls `item.focus()` // on the tree ROW, never on this slotted content, so a keyboard user who // opens the menu (Shift+F10 / the ContextMenu key) would otherwise be left @@ -193,13 +203,15 @@ function TreeContextMenu({ item, context, root, onAddToContext, contribItems, on ?? menuRef.current?.querySelector('[role="menuitem"]'))?.focus() }, []) // The shared role="menu" keyboard contract (#6231). The item count is no longer - // fixed: the built-in row is gated on `onAddToContext` and an installed app may - // contribute any number of rows its `when` admits, so this menu can hold one row - // or several and the arrows do real navigation whenever it holds more than one. - // Honouring the contract per-surface-by-item-count is how surfaces drift anyway — - // an arrow inside an open menu must be consumed rather than scrolling the tree - // behind it, Tab must stay contained (#2533), and IME composition keys must not - // reach the menu at all, all true whatever the count. `enabled: true` + // fixed: the built-in "Add to chat" row is gated on `onAddToContext`, the + // built-in "Upload files…" row is gated on `onUploadRequest` AND a directory + // row, and an installed app may contribute any number of rows its `when` + // admits — so this menu can hold one row or several and the arrows do real + // navigation whenever it holds more than one. Honouring the contract + // per-surface-by-item-count is how surfaces drift anyway — an arrow inside + // an open menu must be consumed rather than scrolling the tree behind it, + // Tab must stay contained (#2533), and IME composition keys must not reach + // the menu at all, all true whatever the count. `enabled: true` // unconditionally because Pierre only mounts this component while the menu is open. // focusFirstOnOpen: false — the firstItemRef effect above already owns focus // entry (it must, because Pierre focuses the tree ROW, not this slotted @@ -280,14 +292,15 @@ function TreeContextMenu({ item, context, root, onAddToContext, contribItems, on } }, [context]) // Render nothing rather than an empty bordered popup: with no host row, no - // Download (a directory), AND no app row that its `when` admits for this - // node, there is nothing to show and no menuitem for the focus effect to land - // on. + // Download (a directory with no upload handler), no upload row (absent + // handler, or a file row), AND no app row that its `when` admits for this + // node, there is nothing to show and no menuitem for the focus effect to + // land on. // A file row also carries a built-in Download (retrieve the bytes onto the // machine running the browser). Directories do not: the ask is file rows // only, and /api/file-download serves a single file, not a folder. const canDownload = !isDir - if (!hasBuiltinChatRow && !canDownload && rows.length === 0) return null + if (!hasBuiltinChatRow && !canDownload && !showUpload && rows.length === 0) return null return
)} + {/* Built-in Upload for a directory row: hands the absolute directory + path to the host, which opens the OS file picker and POSTs to + /api/directory-upload. Directory rows only (mutually exclusive with + Download above, which is file-only), so the firstItemRef handoff + cannot collide. */} + {showUpload && ( +
onUploadRequest?.(abs))} + onKeyDown={activate(() => onUploadRequest?.(abs))} + > + + {i18nT('pages.chat.fileBrowserRail.ctx_upload_files')} +
+ )} {/* App-contributed rows (contributes.fileMenuItems, surface 'tree-context'). An installed app declares these in its manifest; core POSTs the node context to the app's endpoint on activation and never imports app code. @@ -364,7 +395,7 @@ function TreeContextMenu({ item, context, root, onAddToContext, contribItems, on return (
void /** Right-click "Add to context" on a row: hands the host the ABSOLUTE path @@ -431,6 +462,10 @@ export function PierreWorkspaceTreeImpl({ projectDir, onFileOpen, onAddToContext * rendered, and the context menu opens only if an app contributes a * 'tree-context' row this node matches. */ onAddToContext?: (absPath: string, kind: TreeEntryKind) => void + /** Right-click "Upload files…" on a DIRECTORY row: hands the host the + * ABSOLUTE directory path so it can open the OS file picker and upload the + * selection into it. Absent → no upload row on any menu. */ + onUploadRequest?: (absDirPath: string) => void /** Forwarded into the tree's search session (null clears it). */ searchQuery?: string | null /** 'all' renders the full workspace; 'changed' renders only the files with @@ -1346,6 +1381,8 @@ export function PierreWorkspaceTreeImpl({ projectDir, onFileOpen, onAddToContext // query, so it must come through the ref too. const onAddToContextRef = useRef(onAddToContext) onAddToContextRef.current = onAddToContext + const onUploadRequestRef = useRef(onUploadRequest) + onUploadRequestRef.current = onUploadRequest const rootRef = useRef(root) rootRef.current = root @@ -1430,6 +1467,7 @@ export function PierreWorkspaceTreeImpl({ projectDir, onFileOpen, onAddToContext context={ctx} root={rootRef.current} onAddToContext={onAddToContextRef.current} + onUploadRequest={onUploadRequestRef.current} contribItems={treeItemsRef.current} onError={setActionError} /> @@ -1736,8 +1774,9 @@ export function PierreWorkspaceTreeImpl({ projectDir, onFileOpen, onAddToContext // Always wired: a file row now always carries a built-in Download, so // the menu is useful for every file even with no host and no app rows. // The per-node `TreeContextMenu` still renders NOTHING (returns null) for - // a node with no action — a directory with no host and no app row — so a - // right-click there opens no bordered popup despite the menu being wired. + // a node with no action — a directory with no host, no upload handler and + // no app row — so a right-click there opens no bordered popup despite the + // menu being wired. renderContextMenu={renderContextMenu} />
diff --git a/website/src/pierre/tree.tsx b/website/src/pierre/tree.tsx index 18f36db6753..07c9a21e01e 100644 --- a/website/src/pierre/tree.tsx +++ b/website/src/pierre/tree.tsx @@ -42,12 +42,15 @@ export interface TreeReveal { nonce: number } -export function PierreWorkspaceTree({ projectDir, onFileOpen, onAddToContext, searchQuery, mode, selectedPath, persistExpansion, onOpenFolder, reveal, onRevealed, active }: { +export function PierreWorkspaceTree({ projectDir, onFileOpen, onAddToContext, onUploadRequest, searchQuery, mode, selectedPath, persistExpansion, onOpenFolder, reveal, onRevealed, active }: { projectDir: string onFileOpen?: (absPath: string) => void /** Right-click "Add to context" on a row — forwards the ABSOLUTE path and * whether it is a file or a directory to the host composer. */ onAddToContext?: (absPath: string, kind: 'file' | 'dir') => void + /** Right-click "Upload files…" on a directory row — forwards the ABSOLUTE + * directory path so the host can open the OS file picker for it. */ + onUploadRequest?: (absDirPath: string) => void /** Forwarded into the tree's search session (null clears it). */ searchQuery?: string | null /** 'all' (default) = full workspace; 'changed' = only working-tree changes. */ @@ -77,7 +80,7 @@ export function PierreWorkspaceTree({ projectDir, onFileOpen, onAddToContext, se const key = persistExpansion ? [mode ?? 'all', projectDir].join('\u0000') : (mode ?? 'all') return ( }> - + ) } diff --git a/website/src/test/ApiClient.refactor.surface.test.ts b/website/src/test/ApiClient.refactor.surface.test.ts index 686d71e000a..01106322f11 100644 --- a/website/src/test/ApiClient.refactor.surface.test.ts +++ b/website/src/test/ApiClient.refactor.surface.test.ts @@ -151,7 +151,7 @@ const API_KEY_ORDER = [ 'setUpdateChannel', 'restartGateway', 'armUpdate', 'armStatus', 'dismissUpdateArm', 'cancelUpdate', 'simulateUpdate', 'pickFiles', 'fileDiff', 'fileSearch', 'pathComplete', 'uploadFiles', - 'screenshot', 'themes', 'dashboardConfig', 'featureVideoNext', + 'uploadToDirectory', 'screenshot', 'themes', 'dashboardConfig', 'featureVideoNext', 'featureVideoFeedback', 'featureVideoProbe', 'featureVideoStatus', 'featureVideoFetchAll', 'updateDashboardConfig', 'createTheme', 'installTheme', 'updateTheme', 'deleteTheme', 'themeDetail', 'themeBoot', 'updateThemeConfig', @@ -240,7 +240,7 @@ const NO_SESSION_KEY = [ 'pendingQuestions', 'logLevel', 'taskRunnerStatus', 'renameTaskRun', 'updateTask', 'refineStatus', 'planContext', 'checkUpdate', 'changelog', 'releases', 'armStatus', 'fileDiff', - 'fileSearch', 'pathComplete', 'uploadFiles', 'themes', + 'fileSearch', 'pathComplete', 'uploadFiles', 'uploadToDirectory', 'themes', 'dashboardConfig', 'themeDetail', 'themeBoot', 'voiceConfig', 'voiceVoices', 'voiceSystemVoices', 'awsConsent', 'fileDeliveryConsent', 'fileDeliveryConsentArmStatus', 'credentialRedaction', 'channelsList', 'channelPresets', @@ -254,6 +254,7 @@ const ARGS: Record = { memoryRecords: ['member-reviewer', { q: 'contact', kind: 'fact' }, 0, 50], memoryRecordHistory: ['member-reviewer', { kind: 'fact', id: 'user.contact' }, 25, 0], uploadFiles: [[new File(['abc'], 'a.txt', { type: 'text/plain' })]], + uploadToDirectory: ['/tmp/proj', new File(['abc'], 'a.txt', { type: 'text/plain' })], uploadCrewAvatar: ['crew-a', new File(['png'], 'a.png', { type: 'image/png' })], sttTranscribe: [new Blob(['wav'], { type: 'audio/wav' })], // Takes only an AbortSignal; its deadline helper throws on a string before it fetches. diff --git a/website/src/test/FileBrowserRail.test.tsx b/website/src/test/FileBrowserRail.test.tsx index 97d09a3d8bb..e1f9143ddc9 100644 --- a/website/src/test/FileBrowserRail.test.tsx +++ b/website/src/test/FileBrowserRail.test.tsx @@ -25,6 +25,7 @@ const H = vi.hoisted(() => ({ projectTree: vi.fn(), projectGitStatus: vi.fn(), fileSearch: vi.fn(), + uploadToDirectory: vi.fn(), }, fileGrep: vi.fn(), })) @@ -44,6 +45,7 @@ vi.mock('../pierre/tree', () => ({ onOpenFolder?: (folder: string) => void reveal?: { path: string; nonce: number } | null active?: boolean + onUploadRequest?: (absDir: string) => void }) => ( <> {/* Stands in for the limit row under a cut folder: present only when @@ -65,6 +67,16 @@ vi.mock('../pierre/tree', () => ({ > tree + {/* Stands in for a directory row's "Upload files…" context-menu action: + PierreWorkspaceTreeImpl.test.tsx pins that click wired to this same + callback, so this probe only needs to pin that FileBrowserRail + forwards it and reacts correctly when it fires. */} + ), })) @@ -334,6 +346,121 @@ describe('FileBrowserRail file opens', () => { }) }) +function fileTransfer(name = 'a.txt'): DataTransfer { + const file = new File(['x'], name, { type: 'text/plain' }) + return { + types: ['Files'], + items: [{ kind: 'file', type: file.type, getAsFile: () => file }], + files: [file], + dropEffect: 'none', + } as unknown as DataTransfer +} + +describe('FileBrowserRail upload', () => { + beforeEach(() => { + H.api.uploadToDirectory.mockReset().mockResolvedValue({ ok: true, path: `${DIR}/a.txt`, name: 'a.txt' }) + }) + + const railEl = () => rail() + + it('shows the drop overlay naming the project root for an OS file drag over the rail', () => { + mount() + fireEvent.dragEnter(railEl(), { dataTransfer: fileTransfer() }) + expect(screen.getByText('Drop to upload into repo')).toBeInTheDocument() + }) + + it('uploads a pane-wide drop (no specific row) into the tree root', async () => { + mount() + fireEvent.drop(railEl(), { dataTransfer: fileTransfer() }) + + await waitFor(() => expect(H.api.uploadToDirectory).toHaveBeenCalledWith(DIR, expect.any(File))) + }) + + it('uploads a drop released over a folder row or a file row into the tree root', async () => { + // The overlay hides the tree, so the row under the pointer must not pick + // the destination. These rows carry the attributes @pierre/trees renders. + mount() + const folderRow = document.createElement('div') + folderRow.setAttribute('data-item-path', 'src/') + folderRow.setAttribute('data-item-type', 'folder') + const fileRow = document.createElement('div') + fileRow.setAttribute('data-item-path', 'src/a.ts') + fileRow.setAttribute('data-item-type', 'file') + fileRow.setAttribute('data-item-parent-path', 'src/') + tree().append(folderRow, fileRow) + + fireEvent.drop(folderRow, { dataTransfer: fileTransfer('one.txt') }) + fireEvent.drop(fileRow, { dataTransfer: fileTransfer('two.txt') }) + + await waitFor(() => expect(H.api.uploadToDirectory).toHaveBeenCalledTimes(2)) + expect(H.api.uploadToDirectory.mock.calls.map(([dir, file]) => [dir, (file as File).name])).toEqual([ + [DIR, 'one.txt'], + [DIR, 'two.txt'], + ]) + }) + + it('forwards onUploadRequest to the tree and uploads the picker selection into that directory', async () => { + mount() + fireEvent.click(screen.getByTestId('upload-request')) + const input = screen.getByLabelText('Upload files…') as HTMLInputElement + const file = new File(['y'], 'picked.txt', { type: 'text/plain' }) + + fireEvent.change(input, { target: { files: [file] } }) + + await waitFor(() => expect(H.api.uploadToDirectory).toHaveBeenCalledWith(`${DIR}/target-dir`, file)) + }) + + it('shows an upload failure inline and lets it be dismissed', async () => { + H.api.uploadToDirectory.mockResolvedValue({ ok: false, status: 400, code: 'unsupported_file_type', error: 'Unsupported file type: .exe' }) + mount() + + fireEvent.drop(railEl(), { dataTransfer: fileTransfer('a.exe') }) + + const notice = await screen.findByTestId('file-browser-rail-upload-error') + expect(notice).toHaveTextContent('Unsupported file type: .exe') + + fireEvent.click(within(notice).getByLabelText('Dismiss')) + expect(screen.queryByTestId('file-browser-rail-upload-error')).toBeNull() + }) + + it('shows a polite uploading status while a drop uploads, then a dismissible success line', async () => { + let resolveUpload: (value: unknown) => void = () => {} + H.api.uploadToDirectory.mockReturnValueOnce(new Promise((resolve) => { resolveUpload = resolve })) + mount() + + fireEvent.drop(railEl(), { dataTransfer: fileTransfer('report.md') }) + + const status = await screen.findByTestId('file-browser-rail-upload-progress') + expect(status).toHaveAttribute('role', 'status') + expect(status).toHaveTextContent('Uploading report.md…') + + resolveUpload({ ok: true, path: `${DIR}/report.md`, name: 'report.md' }) + const done = await screen.findByTestId('file-browser-rail-upload-success') + expect(done).toHaveAttribute('role', 'status') + expect(done).toHaveTextContent('Uploaded report.md to repo.') + expect(screen.queryByTestId('file-browser-rail-upload-progress')).toBeNull() + + fireEvent.click(within(done).getByLabelText('Dismiss')) + expect(screen.queryByTestId('file-browser-rail-upload-success')).toBeNull() + }) + + it('does not send a second drop of a file that is still uploading', async () => { + let resolveUpload: (value: unknown) => void = () => {} + H.api.uploadToDirectory.mockReturnValueOnce(new Promise((resolve) => { resolveUpload = resolve })) + mount() + + fireEvent.drop(railEl(), { dataTransfer: fileTransfer('report.md') }) + await screen.findByTestId('file-browser-rail-upload-progress') + fireEvent.drop(railEl(), { dataTransfer: fileTransfer('report.md') }) + + expect(H.api.uploadToDirectory).toHaveBeenCalledTimes(1) + resolveUpload({ ok: true, path: `${DIR}/report.md`, name: 'report.md' }) + await screen.findByTestId('file-browser-rail-upload-success') + expect(H.api.uploadToDirectory).toHaveBeenCalledTimes(1) + expect(screen.queryByTestId('file-browser-rail-upload-error')).toBeNull() + }) +}) + describe('FileBrowserRail refresh', () => { it('hands the agent the journaled server report, not the translated sentence', async () => { // The notice shows a composed line ("Couldn't load the file tree — Refresh to retry"), which is diff --git a/website/src/test/PierreWorkspaceTreeImpl.contextMenu.test.tsx b/website/src/test/PierreWorkspaceTreeImpl.contextMenu.test.tsx index 7480130c031..cbe307255ad 100644 --- a/website/src/test/PierreWorkspaceTreeImpl.contextMenu.test.tsx +++ b/website/src/test/PierreWorkspaceTreeImpl.contextMenu.test.tsx @@ -459,6 +459,103 @@ describe('PierreWorkspaceTreeImpl — row context menu Download', () => { }) }) +describe('PierreWorkspaceTreeImpl — row context menu "Upload files…"', () => { + const openMenu = (item: MenuItem) => { + const close = vi.fn() + const context: MenuContext = { + anchorElement: document.createElement('div'), + anchorRect: document.createElement('div').getBoundingClientRect(), + close, + restoreFocus: vi.fn(), + } + const node = treeMock.fileTreeProps.at(-1)!.renderContextMenu!(item, context) + return { close, ...render(<>{node}) } + } + + it('offers Upload files… on a directory row, wired to the absolute directory path', async () => { + const onUploadRequest = vi.fn() + renderTree({ onUploadRequest }) + await waitForTree() + + const { close } = openMenu({ kind: 'directory', name: 'a', path: 'src/a' }) + + fireEvent.click(screen.getByRole('menuitem', { name: 'Upload files…' })) + expect(onUploadRequest).toHaveBeenCalledWith(`${ROOT}/src/a`) + expect(close).toHaveBeenCalledTimes(1) + }) + + it('never offers Upload files… on a file row, even when the handler is wired', async () => { + const onUploadRequest = vi.fn() + renderTree({ onUploadRequest }) + await waitForTree() + + openMenu({ kind: 'file', name: 'b.ts', path: 'src/a/b.ts' }) + + expect(screen.queryByRole('menuitem', { name: 'Upload files…' })).not.toBeInTheDocument() + }) + + it('offers only the built-in Download on a file row when only onUploadRequest is wired', async () => { + // Upload is directory-only, but a file row still carries its built-in + // Download row, so the menu renders with exactly that one action. + const onUploadRequest = vi.fn() + renderTree({ onUploadRequest }) + await waitForTree() + + openMenu({ kind: 'file', name: 'b.ts', path: 'src/a/b.ts' }) + + const items = screen.getAllByRole('menuitem') + expect(items).toHaveLength(1) + expect(items[0]).toHaveTextContent('Download') + expect(screen.queryByRole('menuitem', { name: 'Upload files…' })).not.toBeInTheDocument() + }) + + it('offers both actions on a directory row, Add to chat first, activating independently', async () => { + const onAddToContext = vi.fn() + const onUploadRequest = vi.fn() + renderTree({ onAddToContext, onUploadRequest }) + await waitForTree() + + openMenu({ kind: 'directory', name: 'a', path: 'src/a' }) + + const items = screen.getAllByRole('menuitem') + expect(items.map(i => i.textContent)).toEqual(['Add to chat', 'Upload files…']) + + fireEvent.click(screen.getByRole('menuitem', { name: 'Upload files…' })) + expect(onUploadRequest).toHaveBeenCalledWith(`${ROOT}/src/a`) + expect(onAddToContext).not.toHaveBeenCalled() + }) + + it('focuses Add to chat first when both actions are present on a directory row', async () => { + renderTree({ onAddToContext: vi.fn(), onUploadRequest: vi.fn() }) + await waitForTree() + + openMenu({ kind: 'directory', name: 'a', path: 'src/a' }) + + expect(screen.getByRole('menuitem', { name: 'Add to chat' })).toHaveFocus() + }) + + it('focuses Upload files… when it is the only action on a directory row', async () => { + renderTree({ onUploadRequest: vi.fn() }) + await waitForTree() + + openMenu({ kind: 'directory', name: 'a', path: 'src/a' }) + + expect(screen.getByRole('menuitem', { name: 'Upload files…' })).toHaveFocus() + }) + + it('activates Upload files… from the keyboard', async () => { + const onUploadRequest = vi.fn() + renderTree({ onUploadRequest }) + await waitForTree() + + openMenu({ kind: 'directory', name: 'a', path: 'src/a' }) + const menuitem = screen.getByRole('menuitem', { name: 'Upload files…' }) + + fireEvent.keyDown(menuitem, { key: 'Enter' }) + expect(onUploadRequest).toHaveBeenCalledWith(`${ROOT}/src/a`) + }) +}) + describe('PierreWorkspaceTreeImpl — row context menu keyboard contract (#6231)', () => { // The DEGENERATE case of the shared `role="menu"` contract: this menu hosts // exactly ONE menuitem, so every focus-move assertion is vacuously true — diff --git a/website/src/test/useDirectoryUpload.test.tsx b/website/src/test/useDirectoryUpload.test.tsx new file mode 100644 index 00000000000..36f5fa7fd6b --- /dev/null +++ b/website/src/test/useDirectoryUpload.test.tsx @@ -0,0 +1,435 @@ +/** + * `useDirectoryUpload` — the upload orchestration shared by the Files rail's + * drop overlay and its "Upload files…" row-menu action. + * + * Rendered through a small harness (not `renderHook`) because the hook hands + * back JSX (`fileInput`) that must actually mount for the hidden picker input + * to be interactive. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +vi.mock('../api/client', () => ({ api: { uploadToDirectory: vi.fn() } })) + +import { api } from '../api/client' +import { useDirectoryUpload } from '../pages/chat/useDirectoryUpload' + +const PROJECT_DIR = '/repo' +const REAL_PROJECT_DIR = '/private/repo' +const TARGET_DIR = '/repo/dir' + +function Harness({ projectDir = PROJECT_DIR, rootDir }: { projectDir?: string; rootDir?: string }) { + const { uploadFiles, pickAndUpload, progress, error, dismissError, success, dismissSuccess, fileInput } = + useDirectoryUpload(projectDir, rootDir) + return ( +
+ + + + + + {progress &&
{progress}
} + {success &&
{success}
} + {success && } + {error &&
{error}
} + {error && } + {fileInput} +
+ ) +} + +function mount(projectDir = PROJECT_DIR, rootDir?: string) { + const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + const invalidateSpy = vi.spyOn(qc, 'invalidateQueries') + const view = render( + + + , + ) + const rerenderWith = (nextDir: string) => view.rerender( + + + , + ) + return { qc, invalidateSpy, rerenderWith } +} + +beforeEach(() => { + vi.mocked(api.uploadToDirectory).mockReset() +}) + +describe('useDirectoryUpload — uploadFiles', () => { + it('invalidates every file listing and search query an upload can change after a successful batch', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue({ ok: true, path: `${TARGET_DIR}/a.txt`, name: 'a.txt' }) + const { qc, invalidateSpy } = mount() + // The keys the Files rail, its tree and the folder tab read, each built + // with the root its reader uses: the rail's filename search with the + // backend's realpath of the project, the folder tab with its own folder. + const listingKeys = [ + ['project-tree', PROJECT_DIR], + ['project-tree', PROJECT_DIR, 'dir', 'dir'], + ['git-status', PROJECT_DIR], + ['file-grep', PROJECT_DIR, 'needle'], + ['rail-name-search', REAL_PROJECT_DIR, 'a.txt'], + ['browse-files', TARGET_DIR], + ['folder-file-search', PROJECT_DIR, 'a.txt', 50], + ] + for (const key of listingKeys) qc.setQueryData(key, { seeded: true }) + + fireEvent.click(screen.getByTestId('upload')) + + await waitFor(() => + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['project-tree', PROJECT_DIR] }), + ) + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['git-status', PROJECT_DIR] }) + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['file-grep', PROJECT_DIR] }) + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['rail-name-search'] }) + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['browse-files'] }) + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['folder-file-search'] }) + for (const key of listingKeys) { + expect(qc.getQueryState(key)?.isInvalidated, JSON.stringify(key)).toBe(true) + } + expect(screen.queryByTestId('error')).toBeNull() + }) + + it('surfaces a non-collision failure', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue({ + ok: false, status: 400, code: 'unsupported_file_type', error: 'Unsupported file type: .txt', + }) + mount() + + fireEvent.click(screen.getByTestId('upload')) + + await waitFor(() => + expect(screen.getByTestId('error')).toHaveTextContent("Couldn't upload a.txt: Unsupported file type: .txt"), + ) + }) + + it('surfaces every refusal from one multi-file upload batch', async () => { + vi.mocked(api.uploadToDirectory) + .mockResolvedValueOnce({ + ok: false, status: 400, code: 'unsupported_file_type', error: 'Unsupported file type: .txt', + }) + .mockResolvedValueOnce({ + ok: false, status: 413, error: 'File is too large', + }) + mount() + + fireEvent.click(screen.getByTestId('upload-two')) + + await waitFor(() => { + const notice = screen.getByTestId('error') + expect(notice).toHaveTextContent("Couldn't upload a.txt: Unsupported file type: .txt") + expect(notice).toHaveTextContent("Couldn't upload b.exe: File is too large") + }) + }) + + it('dismisses a shown error', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue({ ok: false, status: 500, error: 'boom' }) + mount() + fireEvent.click(screen.getByTestId('upload')) + await waitFor(() => expect(screen.getByTestId('error')).toBeInTheDocument()) + + fireEvent.click(screen.getByTestId('dismiss')) + + expect(screen.queryByTestId('error')).toBeNull() + }) + + it('clears a failure notice when projectDir changes in place', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue({ ok: false, status: 500, error: 'boom' }) + const { rerenderWith } = mount() + fireEvent.click(screen.getByTestId('upload')) + await waitFor(() => expect(screen.getByTestId('error')).toBeInTheDocument()) + + rerenderWith('/other-repo') + + expect(screen.queryByTestId('error')).toBeNull() + }) + + it('drops the failures of an upload that finishes after projectDir changes', async () => { + let resolveUpload: (value: Awaited>) => void = () => {} + vi.mocked(api.uploadToDirectory).mockReturnValue( + new Promise((resolve) => { resolveUpload = resolve }), + ) + const { invalidateSpy, rerenderWith } = mount() + fireEvent.click(screen.getByTestId('upload')) + await waitFor(() => expect(api.uploadToDirectory).toHaveBeenCalledTimes(1)) + + rerenderWith('/other-repo') + resolveUpload({ ok: false, status: 500, error: 'boom' }) + + await waitFor(() => + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['project-tree', PROJECT_DIR] }), + ) + expect(screen.queryByTestId('error')).toBeNull() + }) + + it('clears an earlier failure notice when a later upload succeeds', async () => { + vi.mocked(api.uploadToDirectory) + .mockResolvedValueOnce({ ok: false, status: 500, error: 'boom' }) + .mockResolvedValueOnce({ ok: true, path: `${TARGET_DIR}/a.txt`, name: 'a.txt' }) + const { invalidateSpy } = mount() + fireEvent.click(screen.getByTestId('upload')) + await waitFor(() => expect(screen.getByTestId('error')).toBeInTheDocument()) + invalidateSpy.mockClear() + + fireEvent.click(screen.getByTestId('upload')) + + await waitFor(() => + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['project-tree', PROJECT_DIR] }), + ) + expect(api.uploadToDirectory).toHaveBeenCalledTimes(2) + expect(screen.queryByTestId('error')).toBeNull() + }) + + it('reports a name collision with the already-exists message and never retries', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue({ + ok: false, status: 409, code: 'name_collision', error: 'already exists', + }) + const { invalidateSpy } = mount() + + fireEvent.click(screen.getByTestId('upload')) + + await waitFor(() => + expect(screen.getByTestId('error')).toHaveTextContent( + '“a.txt” already exists in this folder. Rename your file, or remove the existing one, then upload again.', + ), + ) + expect(api.uploadToDirectory).toHaveBeenCalledTimes(1) + expect(vi.mocked(api.uploadToDirectory).mock.calls[0]).toEqual([TARGET_DIR, expect.any(File)]) + expect(screen.queryByRole('dialog')).toBeNull() + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['project-tree', PROJECT_DIR] }) + }) + + it('reports a collision alongside another file\'s failure in one batch', async () => { + vi.mocked(api.uploadToDirectory) + .mockResolvedValueOnce({ ok: false, status: 409, code: 'name_collision', error: 'already exists' }) + .mockResolvedValueOnce({ ok: false, status: 400, code: 'unsupported_file_type', error: 'Unsupported file type: .exe' }) + mount() + + fireEvent.click(screen.getByTestId('upload-two')) + + await waitFor(() => { + const notice = screen.getByTestId('error') + expect(notice).toHaveTextContent('“a.txt” already exists in this folder.') + expect(notice).toHaveTextContent("Couldn't upload b.exe: Unsupported file type: .exe") + }) + expect(api.uploadToDirectory).toHaveBeenCalledTimes(2) + }) + + it('surfaces a rejected upload call (e.g. a transport failure) as an error instead of an unhandled rejection', async () => { + vi.mocked(api.uploadToDirectory).mockRejectedValue(new Error('Failed to fetch')) + mount() + + fireEvent.click(screen.getByTestId('upload')) + + await waitFor(() => + expect(screen.getByTestId('error')).toHaveTextContent("Couldn't upload a.txt: Failed to fetch"), + ) + }) +}) + +type UploadResult = Awaited> + +/** Makes the next upload call hang until the returned resolver is called. */ +function deferUpload(): (value: UploadResult) => void { + let resolveUpload: (value: UploadResult) => void = () => {} + vi.mocked(api.uploadToDirectory).mockReturnValueOnce( + new Promise((resolve) => { resolveUpload = resolve }), + ) + return (value) => resolveUpload(value) +} + +const OK: UploadResult = { ok: true, path: `${TARGET_DIR}/a.txt`, name: 'a.txt' } + +describe('useDirectoryUpload — progress and success', () => { + it('shows the uploading file while its upload runs and drops the line when it ends', async () => { + const resolve = deferUpload() + mount() + + fireEvent.click(screen.getByTestId('upload')) + + expect(await screen.findByTestId('progress')).toHaveTextContent('Uploading a.txt…') + resolve(OK) + await waitFor(() => expect(screen.queryByTestId('progress')).toBeNull()) + }) + + it('counts the files of a running multi-file batch', async () => { + const resolve = deferUpload() + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + mount() + + fireEvent.click(screen.getByTestId('upload-two')) + + expect(await screen.findByTestId('progress')).toHaveTextContent('Uploading 2 files…') + resolve(OK) + await waitFor(() => expect(screen.queryByTestId('progress')).toBeNull()) + }) + + it('names the file and its folder, relative to the project, after a successful upload', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + mount() + + fireEvent.click(screen.getByTestId('upload')) + + expect(await screen.findByTestId('success')).toHaveTextContent('Uploaded a.txt to dir.') + expect(screen.queryByTestId('error')).toBeNull() + }) + + it('names the project folder for an upload into the tree root, and a subfolder under a realpath root', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + mount(PROJECT_DIR, REAL_PROJECT_DIR) + + fireEvent.click(screen.getByTestId('upload-root')) + expect(await screen.findByTestId('success')).toHaveTextContent('Uploaded a.txt to repo.') + + fireEvent.click(screen.getByTestId('upload-real-sub')) + await waitFor(() => expect(screen.getByTestId('success')).toHaveTextContent('Uploaded a.txt to sub.')) + }) + + it('counts the files of a successful multi-file batch', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + mount() + + fireEvent.click(screen.getByTestId('upload-two')) + + expect(await screen.findByTestId('success')).toHaveTextContent('Uploaded 2 files to dir.') + }) + + it('shows no success line for a batch with a failure', async () => { + vi.mocked(api.uploadToDirectory) + .mockResolvedValueOnce(OK) + .mockResolvedValueOnce({ ok: false, status: 413, error: 'File is too large' }) + mount() + + fireEvent.click(screen.getByTestId('upload-two')) + + await waitFor(() => expect(screen.getByTestId('error')).toHaveTextContent("Couldn't upload b.exe")) + expect(screen.queryByTestId('success')).toBeNull() + }) + + it('does not send a name that is still uploading into the same folder', async () => { + const resolve = deferUpload() + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + mount() + + fireEvent.click(screen.getByTestId('upload')) + await waitFor(() => expect(api.uploadToDirectory).toHaveBeenCalledTimes(1)) + fireEvent.click(screen.getByTestId('upload')) + + expect(api.uploadToDirectory).toHaveBeenCalledTimes(1) + expect(screen.getByTestId('progress')).toHaveTextContent('Uploading a.txt…') + resolve(OK) + expect(await screen.findByTestId('success')).toHaveTextContent('Uploaded a.txt to dir.') + expect(api.uploadToDirectory).toHaveBeenCalledTimes(1) + expect(screen.queryByTestId('error')).toBeNull() + + // Once the first upload has ended the same name is sent again (and the + // server answers for it). + fireEvent.click(screen.getByTestId('upload')) + await waitFor(() => expect(api.uploadToDirectory).toHaveBeenCalledTimes(2)) + }) + + it('clears the success line on dismiss and when the next batch starts', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + mount() + fireEvent.click(screen.getByTestId('upload')) + await screen.findByTestId('success') + + fireEvent.click(screen.getByTestId('dismiss-success')) + expect(screen.queryByTestId('success')).toBeNull() + + fireEvent.click(screen.getByTestId('upload')) + await screen.findByTestId('success') + const resolve = deferUpload() + fireEvent.click(screen.getByTestId('upload')) + + await screen.findByTestId('progress') + expect(screen.queryByTestId('success')).toBeNull() + resolve(OK) + await screen.findByTestId('success') + }) + + it('clears the success line and the running batch line when projectDir changes in place', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValueOnce(OK) + const { invalidateSpy, rerenderWith } = mount() + fireEvent.click(screen.getByTestId('upload')) + await screen.findByTestId('success') + + rerenderWith('/other-repo') + expect(screen.queryByTestId('success')).toBeNull() + + const resolve = deferUpload() + fireEvent.click(screen.getByTestId('upload-two')) + await screen.findByTestId('progress') + rerenderWith(PROJECT_DIR) + expect(screen.queryByTestId('progress')).toBeNull() + + // A batch that finishes after the switch reports nothing in the new panel. + vi.mocked(api.uploadToDirectory).mockResolvedValue(OK) + resolve(OK) + await waitFor(() => + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['project-tree', '/other-repo'] }), + ) + expect(screen.queryByTestId('success')).toBeNull() + expect(screen.queryByTestId('progress')).toBeNull() + }) +}) + +describe('useDirectoryUpload — pickAndUpload', () => { + it('opens the hidden file picker scoped to the requested directory', () => { + mount() + const input = screen.getByLabelText('Upload files…') as HTMLInputElement + const clickSpy = vi.spyOn(input, 'click') + + fireEvent.click(screen.getByTestId('pick')) + + expect(clickSpy).toHaveBeenCalledTimes(1) + }) + + it('uploads the picker selection into the pending directory', async () => { + vi.mocked(api.uploadToDirectory).mockResolvedValue({ ok: true, path: `${TARGET_DIR}/b.txt`, name: 'b.txt' }) + mount() + fireEvent.click(screen.getByTestId('pick')) + const input = screen.getByLabelText('Upload files…') as HTMLInputElement + const file = new File(['y'], 'b.txt', { type: 'text/plain' }) + + fireEvent.change(input, { target: { files: [file] } }) + + await waitFor(() => expect(api.uploadToDirectory).toHaveBeenCalledWith(TARGET_DIR, file)) + }) + + it('does nothing when the picker is dismissed with no selection', () => { + mount() + fireEvent.click(screen.getByTestId('pick')) + const input = screen.getByLabelText('Upload files…') as HTMLInputElement + + fireEvent.change(input, { target: { files: [] } }) + + expect(api.uploadToDirectory).not.toHaveBeenCalled() + }) +})