(sessions): list the files a session touched, including outside any repository (#309) - #423
Conversation
…epository Changes answers what differs in a working tree, not what a session touched: a file outside any repository, a directory with no git, a tree shared by several sessions and a change since committed or reverted all escape it. The new Touched tab reads the session's transcript and its subagents' for Edit, Write, MultiEdit and NotebookEdit calls, checks each path against the disk (present, gone, not a file, unreadable, refused) and states in the panel that it is a lower bound: Bash-made changes are not listed. Paths come from transcripts, so they are treated as untrusted: relative paths resolve only against a cwd that verifiedTranscriptCwd accepts, UNC, device and drive-relative forms are not followed, credential locations are listed as refused without a stat, and a click goes through read-file-for-panel and its guards. Closes #309
|
Reviewing |
devsuitup
left a comment
There was a problem hiding this comment.
Adversarial review at 97272d0: no security blocker on IPC input or opening. The renderer sends only a session id and main derives the folder. A click opens only through readFileForPanel and its guards, and nothing persists the probe results. Being fixed before merge: (F1, reproduced) isSensitive runs outside the 3 s timeout, so an offline mapped drive can hang the listing and starve the fs thread pool. (F2, reproduced) bidi, C1 and format characters pass, so a path displays reversed before a click. Also: an uncapped overflow Set, a 32 MiB per-line parse cap, comment length and the eslint block scope.
…rmat characters The sensitivity guard ran outside the stat timeout, so a path on an offline mapped drive could hold the listing forever. The guard and the stat now run under one timeout, a timeout reads as unreadable, and after a few timed-out checks no new call is issued, which bounds the threads planted paths can hold. Paths with bidi overrides or isolates, marks, zero-width, C1 or other format characters are no longer resolved, and an unresolved path shows them as visible escapes inside a bidi isolate. A tool-call line over 4 MiB is skipped and counted, the overflow past the row cap is a counter, and the lint exceptions for the new view are narrowed to its three hooks. Refs #309
|
Reviewing |
devsuitup
left a comment
There was a problem hiding this comment.
Re-review at 06eeda7 (delta from 97272d0): 0 blocking. The guard and the stat run under one timeout, and the timed-out gate is per refresh: mutating it away issues 60 calls instead of about 10. C0, C1, U+2028/2029 and \p{Cf} (bidi, BOM, zero-width, tag characters) are refused. They render escaped, astral code points included, and both row types get unicode-bidi: isolate. The 4 MiB skip is counted in the summary. The eslint exemptions are narrowed to /* exported */. Minor: the gate is order-dependent, since 8 slow paths early in the list mark the rest unreadable (a documented trade-off). A path cut at 300 characters can leave a lone surrogate, which renders as U+FFFD (display only).
The timer that bounds a disk check was unref'd, so a check that never answers left nothing alive: on Node 20 and 22 the test runner saw an empty event loop with a pending promise and cancelled the file. The timer is now referenced, and still cleared as soon as the race settles. Refs #309
|
CI green at |
# Conflicts: # CHANGELOG.md
What
A Touched tab in the file panel (header toggle next to Changes) lists the files a local session's file tools touched, from its own transcript and its subagents'. It works outside any git repository. Closes #309.
tool_useblocks namedEdit,Write,MultiEdit,NotebookEdit(file_path, ornotebook_path). Parent transcript plus the subagents found byenumerateSessionFiles(both layouts).present,gone,not-file,unreadable(also: no answer in 3 s),refused. A gone file says so instead of failing to open.#touched-coverage): it is a lower bound, Bash-made changes are not listed. The text is in the DOM while loading, on error and when the list is empty, and a test pins it. An empty list reads "No files touched by the file tools".Trust
Paths in a transcript are attacker-influenced (a sandboxed session writes its own).
verifiedTranscriptCwd((sessions): trust a transcript cwd only if it encodes back to its folder (#385) #419), per transcript: a subagent in a worktree leaves its relative pathsunresolved, shown with the reason and not openable.\p{Cf}count as control characters: bidi overrides and isolates, zero-width, tag characters; the unresolved text shows them as visible escapes and paths render in a bidi isolate): UNC /\\?\/\\.\forms (astatthere reaches the network), a rooted path with no drive, a drive-relative path,~, control characters, over 4096 characters.isSensitivePathAsyncbefore itsstat, both under one 3 s timeout (a timeout isunreadable); after 8 timed-out checks no new file-system call is issued and the rest areunreadable, so planted paths on an offline drive cannot starve the thread pool; a hit, or a check that throws, lists the row asrefusedand never stats it. Nothing is read by the listing.readFileForPanel(read-file-for-paneland its guards) and the ordinary file tab, only for a row that is bothopenableandpresent. A source check pins that the view calls no otherwindow.apimethod thansessionTouchedFilesandreadFileForPanel.sub:id and a remote folder are refused.omittedis a counter), concurrency 8, 256 MiB of transcript in total (coverage.truncated), a tool-call line over 4 MiB and any line over 32 MiB skipped and counted (coverage.skippedLines).Choices the issue left open
reason: 'remote'and the tab shows it.goneif the file does not exist). Tool results are not read.Tests
test/session-touched-files.test.js(37): extraction (four tools, ignored tools and user turns, malformed lines), path resolution (also withpath.win32), the walk over real temp directories (outside-repo file, subagents in both layouts, other sessions excluded, merge, gone / not-file / unreadable / refused, relative paths with and without a verified cwd, caps, byte budget, stat timeout and concurrency), and target resolution.test/dom-file-panel-touched.test.js(16, jsdom, through the realfile-panel.js): the toggle and rows, the coverage statement in every state, state labels, no click on a non-openable row, unresolved rows, a refused read, notes for omitted / subagents / malformed / truncated, markup shown as text, stale open, hiding the previous container.test/touched-files-wiring.test.js(5): main handler guard wiring, preload channel, script order, the view's API surface.test/header-controls.test.jsandtest/terminal-manager-harness.jsupdated for the new control.~, UNC form, relative without verified cwd, control characters, sensitive refusal, fail-closed sensitivity, folder containment, verified cwd, stat concurrency, stat timeout, byte budget, own-subagents filter, file cap, dedupe, malformed count, gone vs unreadable, not-file, session id validation, remote refusal, openable flag, present state, coverage note, open stale guard, text vs HTML for both path kinds, click guard, refused read, truncated and omitted notes, previous-container hiding, main handler's guard. The one survivor is the first of two length checks on a raw path, which the second check after resolution makes equivalent.\p{Cf}, C1, visible escape, per-line bound, bidi isolate CSS, skipped-lines note.task -d <worktree> check: lint 0 errors (349 warnings, none in the new files), 3101 + 120 tests, 0 failures.Not verified
readFileForPanelon a path reached through a junction or 8.3 name relies on the existing (file-panel): the sensitive-path guard misses 8.3 short names and \?\ paths on Windows #390 / (file-panel): refuse 8.3 short names and \?\ paths into credential directories (#390) #399 guards; the listing adds no test of that.