Skip to content

feat(transfers): progress and cancel for SSH uploads and all remote downloads - #23849

Open
Chihen-Tai wants to merge 27 commits into
stablyai:mainfrom
Chihen-Tai:feat/remote-transfer-progress
Open

Chihen-Tai wants to merge 27 commits into
stablyai:mainfrom
Chihen-Tai:feat/remote-transfer-progress

Conversation

@Chihen-Tai

@Chihen-Tai Chihen-Tai commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #16107 (runtime upload progress). This branch is current main + a merge of #16107 (ae66b3fbd3, resolved against main's owner-routed target and the shared streamExternalFileSlices streamer) + one commit with this PR's change (43c77436bb). Review git show 43c77436bb; the PR diff shrinks once #16107 lands.

Reworked after the relay removal (#27214, #27215). SSH workspaces now run on the host's managed runtime, so the earlier SSH-specific half (desktop-side SFTP/system-SSH upload progress, the rollback ledger, main-process download progress/cancel IPC) is gone. Everything now rides #16107's runtime transfer path.

ELI5

When you copy files to or from a remote workspace (an SSH host or a remote Orca server), Orca shows one progress panel: a row per item with bytes, a bar and a Cancel button. Before, only files dropped onto a remote terminal got a panel; explorer and editor drops and all downloads showed nothing until they finished.

What Changed

Before (main + #16107):

  • Dropping files onto a remote terminal shows feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107's panel with cancel.
  • Dropping into the file explorer or editor of a remote workspace shows no progress and cannot be cancelled.
  • Downloads (explorer → Download, terminal "download & open") show nothing until the final toast and cannot be cancelled.
  • On an SSH workspace, the explorer Download and terminal "download & open" for a file went to the desktop-side SSH filesystem provider, which nothing registers since the relay was removed.

After:

  • Explorer, editor and terminal drops into a remote workspace all use the same panel, with per-row cancel.
  • File downloads from a remote workspace show the panel (bytes of total, bar, percentage, Cancel). Cancel stops the download, discards the temp file, and never overwrites the chosen destination. A download that finished before the cancel landed still ends with the usual "Downloaded … / Open" toast.
  • SSH workspace files download through the host's runtime, like every other remote workspace.

Mechanism:

  • UI: feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107's terminal panel, its session store, heading and byte formatting move to components/transfer-progress/ and take a direction (upload/download). createUploadProgressPanel wraps feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107's RuntimeImportProgressHandlers; runRemoteDownloadWithProgress wraps a download. Rows end from the transfer's result, not the click: "Cancelling…" until the result arrives, "remote state unconfirmed" after 15 s.
  • Uploads: no new transport code. Explorer and editor drops pass progress to importExternalPathsToRuntime, which already reports bytes over feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107's fs:uploadProgress and cancels through fs:cancelRuntimeUpload. Local workspaces are untouched (their copy has no progress handlers).
  • Downloads: downloadRuntimeFile already pulls the file chunk by chunk in the renderer (files.readChunk). It now takes an optional transfer: it reads the size with files.stat (failure only means "bytes, no percentage"), counts each appended chunk (throttled to one update per 100 ms), and checks an AbortSignal before every chunk and before the final commit. Older servers that answer in one preview reply still show the panel and can be cancelled before the save. No main-process or wire change.
  • SSH download routing: the explorer prefers the runtime context for files (pickRemoteDownloadTarget); terminal "download & open" always calls downloadRuntimeFile, which still falls back to the SSH connection when the context has no runtime owner. Folder downloads keep main's existing SSH-only path.
  • Runtime cancel fixes (touch only feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107's import loop): directories check the cancel before and after each create, so nothing new appears after a cancel and an empty folder cancelled mid-create is cleaned up; a source is marked cancelled only when the cancel is what stopped it, so a failure after the click is still reported.

Why

Linked Issue

Fixes #16077 (together with #16107)

Visual Proof

Captured from the dev app (this branch) against an Ubuntu 24.04 SSH host served by its managed runtime (orcad), driven through CDP. Crops show only the panel.

Upload (explorer drop) Download (explorer → Download)
In progress upload in progress download in progress
Cancel one row cancelled, the other keeps going download cancelled
Finished (panel holds its outcome, then closes) downloaded toast
Two items Terminal drop Terminal after the drop
two items uploading terminal drop progress remote path pasted

Testing

Automated (ran locally on this branch):

  • pnpm tc: pass.
  • check:code-quality:changed, oxlint on changed files, check:max-lines-ratchet, check:dead-classes, check:owner-routing-ratchet, verify:localization-catalogs|extraction|coverage: pass.
  • Changed/related suites (transfer-progress/, runtime/runtime-file-client-*, right-sidebar/FileExplorerRow.actions, terminal-pane/terminal-drop-*, terminal-runtime-drop-upload, terminal-pane-element-file-drop, main/ipc/runtime-upload-*, filesystem-mutations-runtime-upload): pass.
  • New/updated tests: download progress against the remote size, unknown size, cancel between chunks (temp discarded), cancel during the final append (nothing committed), older-server one-reply download, an SSH workspace file downloading through its runtime instead of the SSH connection, explorer download target choice, the download panel (dismissed dialog, success, cancel, failure, finish-before-cancel), the tracker throttle, and the runtime cancel fixes above.
  • A wider run (src/main/ipc, src/renderer/src/{runtime,lib,components/terminal-pane,components/editor,...}) has 29 failures in files this PR does not touch; the same files fail on clean main on my machine (node:sqlite unavailable, a BrowserWindow mock, timing tests).

Manual, macOS → Ubuntu 24.04 SSH host on the managed runtime (dev app, driven through CDP):

Scenario Result
Explorer drop, 286 MB file Bar climbs, panel closes after its outcome; remote sha256 matches
Drop a file + a folder, cancel the file mid-upload File row reads "Cancelled", its .orca-upload-* temp is removed on the host; the folder finishes and both files' sha256 match
Terminal drop Panel, then the remote .orca/drops/ path is pasted; sha256 matches
Explorer → Download, 286 MB Panel, then "Downloaded … / Open"; local sha256 matches
Cancel mid-download "Download cancelled"; the temp file is removed and nothing is written at the destination

Not run by hand: editor drop and terminal "download & open" (same upload/download functions as above, covered by unit tests). The dev app needed out/orcad-template from a release build to connect at all; without it every host reports "isn't supported" (main's dev setup, unrelated to this PR).

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

AI Disclosure

Implemented and tested with Claude Code (Claude Opus 5.5).

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

🤖 Generated with Claude Code

mmarabel and others added 18 commits August 23, 2026 00:32
Runtime-environment imports read the entire file into memory and
base64-encoded it before sending, so peak main-process memory was ~2.3x
the file size. A 25 MB per-file / 100 MB per-drop ceiling existed only to
keep that from exhausting the heap, and it was checked right after lstat
— so dropping a 40 MB recording failed instantly with no bytes moved.

The socket already lives in main, but the chunking loop lived in the
renderer, so the whole base64 payload crossed IPC only to be sliced and
passed back through main.

Move the byte pump into main, next to the file handle and the socket:
one open handle per file, 384 KiB slices (512 KiB on the wire, matching
the previous chunk size), sent through the existing files.writeBase64Chunk
method. Staging now records byteLength instead of file contents. The
renderer keeps deconflict, commit, rollback and cleanup, and never sees
file bytes.

Peak memory per upload drops to one slice. The ceilings become user
safety limits rather than memory guards, raised to 2 GB / 8 GB until the
drop UI can show progress and cancel a running upload.

No protocol change: files.writeBase64Chunk already existed and was
already used by this path for payloads over 512 KB. Small files now use
it with append: false, which is exactly what files.writeBase64 does
internally, so mixed client/host versions are unaffected.

Symlink rejection, O_NOFOLLOW, realpath containment and the stat
identity check all move to upload time and now share one handle for the
whole file, so every slice comes from the same inode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dropping a large file onto a remote terminal gave no feedback until the
transfer finished or failed, so a slow upload looked like a hung one. That
became easy to hit once streaming removed the 25 MB per-file import cap.

The chunk loop now reports bytes acknowledged by the runtime, coalesced to
at most one update per megabyte or 100ms so a multi-gigabyte file does not
flood IPC. The renderer sums staged byte counts up front and tracks
committed files plus the file in flight, so the drop toast shows one figure
for the whole drop rather than per file.

Client-local only: main to renderer, no RPC params or stream frames change,
so there is no wire-compatibility surface.

Cancellation is deliberately excluded; it needs an abort path threaded into
the streamer plus temp-file cleanup, and belongs with UI that can trigger it.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the single-line progress toast with a panel: a header carrying the
aggregate percent and a collapse toggle, then one row per dropped source
showing name, transferred-of-total, a bar, percent, and a cancel control.

Cancel is real rather than decorative. Each dropped source streams under one
id that is both its progress key and its abort handle, so cancelling a row
stops that source and leaves the rest of the drop running. The streamer
checks between slices, which leaves a well-formed partial temp file for the
existing cleanup to remove; nothing lands at the final path. A cancel that
arrives between two files of a source is remembered so the next file aborts
instead of the click doing nothing, and cancelled sources are excluded from
the failure summary because a cancel is not a failure.

Both figures in a row scale to the total's unit: formatting them
independently rendered "900 / 32.5" when a file crossed a unit boundary.

Per STYLEGUIDE, cancel is a back-out rather than a destructive action, so it
stays a quiet ghost control with no colour, and the bar uses the existing
Progress primitive's `primary` fill rather than a new hue.

Splits the drop flow into terminal-runtime-drop-upload and
terminal-drop-paste; the host file had grown past the max-lines limit.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cancelling every file removed the session immediately, so the panel vanished
mid-gesture with its header still reading "Uploading 2 files" — the one moment
the user most needs to see what happened.

Finishing now settles the session instead of deleting it. The header switches
to the outcome (cancelled, failed, uploaded X of N), the percent and collapse
toggle drop away since nothing is moving, and the panel holds for 1.2s before
fading out and dismissing itself. Reduced-motion skips the fade but keeps the
hold, so the outcome is still readable.

Dismissal moves from the drop flow to the panel, which owns the timing; only
the error path still tears it down at once.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collapsing the file list left the header hanging where the expanded panel's
top edge was, with dead space below it, instead of dropping to where a
single-line toast sits.

Sonner caches each toast's height and recomputes it only when the toast's
element identity changes — `toast.jsx` is in that effect's dependency list.
Our panel re-renders internally from its own store, so the cached height
stayed at the expanded size and the shrunken content was top-aligned inside
it.

Collapse state moves into the session store and toggling re-issues
toast.custom under the same id, which changes the element identity and makes
sonner re-measure. Keeping the flag in the store is what makes the remount
safe: local component state would reset on every toggle.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collapsing the file list added a second panel rendering the same session
instead of updating the first, so two identical cards ticked along together.

sonner's toast.custom mints an id, then spreads the caller's options over it:

  const id = data?.id || toastsCounter++
  this.create({ jsx: jsx(id), id, ...data })
  return id

Passing `id: undefined` therefore reaches create() as an explicit undefined,
which mints a second id internally while custom() returns the first. The
stored id never matched the registered toast, so re-issuing under it found no
existing toast and added another.

The id key is now omitted entirely on the first publish and only supplied on
re-issue. A test pins that the first call carries no id key at all, since
passing undefined reads as harmless and is not.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t jump

An unstyled sonner toast is content-sized, so the expanded panel was as wide
as its longest file name and the collapsed header shrank to fit "Uploading 2
files". Collapsing therefore moved the left edge as well as the bottom, which
read as a jitter rather than a fold.

The panel now takes the Toaster's own --width, so both states occupy the same
box and only the height changes. File names are clipped inside that box
instead of widening it.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collapsing swapped ChevronUp for ChevronDown, which replaces the DOM node, so
there was nothing to tween and the arrow flipped in a single frame.

One ChevronDown now rotates 180deg over 200ms. The rotation survives the
toast re-issue because sonner keys the list item by toast id and only swaps
its jsx, so React reconciles the same element rather than remounting it.
motion-reduce drops the transition.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Uploading 2 files" reads as a local copy. This panel only ever appears for a
drop onto a remote workspace, so it now says "to runtime", matching the
wording the old single-line toast used.

The cancelled and failed lines stay short: once nothing landed there, the
destination is no longer the point.

Fixes stablyai#16077

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-ups on the import ceilings.

Staging and upload are separate IPC calls, so a source could be replaced or
grown in between and the streamer would take the new size as authoritative —
transferring a file the ceilings had already rejected, and invalidating the
per-drop aggregate. The staged byteLength now rides the upload request and a
mismatch is refused before any chunk is sent.

formatByteCeiling rounded, so 2 GiB + 1 B rendered "2.0 GB" against a "2 GB"
limit and the error read like a broken check. It now rounds up, so anything
over the ceiling renders strictly greater than it.

Symlink tests are skipped on Windows, where symlink() needs elevation or
Developer Mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ty drop

Review follow-ups on the drop panel.

Electron does not order webContents.send against an invoke reply, so a final
progress event can arrive after completeFile and be counted a second time,
jumping the bar ahead. The tracker now only accepts progress inside an
explicit per-file window that completeFile closes.

onStart still fires when every source was skipped, so a drop that staged
nothing flashed an empty panel; it now returns before creating one.

releaseRuntimeUpload was a bare `void` on a promise, which surfaces as an
unhandled rejection if the IPC fails during cleanup — it now carries the same
`.catch(() => {})` the sibling cleanup calls use.

Comments trimmed to the one-line rule in AGENTS.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-progress

Picks up the review follow-ups on the base branch: staged-size enforcement,
the rounding fix for over-limit errors, and the Windows symlink test guards.
The ceiling lived only in staging, and the upload IPC takes the staged size as
optional — so the limit held only for callers that had actually staged. The
streamer now checks it itself, independent of whether a staged size was
supplied.

Both ceilings and their formatter move to runtime-import-limits so staging and
the streamer share one definition rather than drifting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-progress

Picks up the per-file ceiling check at the streaming boundary and the shared
runtime-import-limits module.
…ings

truncate() sets a logical length but does not guarantee a sparse file, so on
NTFS the over-limit fixtures could allocate two to three gigabytes on Windows
CI to prove a comparison.

The ceiling tests now mock runtime-import-limits down to kilobytes and use
small real files, and the formatter's one-byte-over case moves to a pure unit
test on formatByteCeiling where no file is needed at all. Largest fixture in
these suites drops from 3 GiB to 3 MiB.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rows

Headings now pass count so i18next picks the plural form, with named placeholders.
Drop-handler success mocks settle their row before finishing, and the runtime
file client harness stubs cancel/release so progress-enabled imports resolve.
Copilot AI balanced review requested due to automatic review settings September 29, 2026 09:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Adds progress tracking and cancellation to file transfers.

The recent changes appear safe to merge; the remaining closed-window test gap is non-blocking.

Findings

  1. P2 Cancel hides an upload failure ▶
  2. P2 Closed-window coverage was removed ▶
  3. P2 Unbounded folder size walk ▶

Summary

The PR adds a shared progress panel and cancellation flow for SSH uploads and remote downloads. Since the previous review, it preserves an SSH upload’s original error when Cancel is clicked during failure cleanup and clarifies when a folder download’s total may arrive.

  • The prior upload-error finding is fixed by recording the failure before cleanup.
  • No new actionable issue was established in the changes since the previous review.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Remote transfer] --> B[Progress panel]
  B --> C{Cancel requested?}
  C -- No --> D[Transfer result]
  C -- Yes --> E[Abort and cleanup]
  E --> D
  D --> F[Show result-driven outcome]
Loading

Reviews (19) · Last reviewed commit: "docs(transfers): say a folder total may ..."

Comment thread src/main/ipc/filesystem-import-ssh-progress.ts Outdated
Comment thread src/main/ipc/filesystem/filesystem-download-handlers.ts Outdated
Comment thread src/renderer/src/runtime/runtime-file-read-client.ts
Comment thread src/renderer/src/runtime/runtime-file-read-client.ts
Comment thread src/main/ipc/runtime-upload-cancellation.ts
Comment thread src/main/ipc/filesystem-import-ssh-progress.ts Outdated
Comment thread src/renderer/src/runtime/runtime-upload-progress-tracker.ts
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

The change adds progress reporting and cancellation for remote downloads and uploads. Main-process handlers and SSH/runtime transfer paths report byte counts through IPC. SSH imports track created entries and roll back canceled uploads. Renderer code tracks transfer sessions and displays progress panels in file explorer, terminal, and file-drop workflows. Tests cover progress updates, cancellation, cleanup, rollback, and transfer-session behavior.





Pre-merge checks | Passed 3 | Failed 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check Warning The PR adds SSH upload progress and cancellation, remote file and folder download progress and cancellation, shared transfer UI, and cancellation infrastructure. Issue [#16077] requires runtime droppe… Split SSH upload, remote download, and cancellation work into separate pull requests, or link active issues that require these features. Keep this pull request limited to runtime dropped-file upload progress and supporting code and tests.
Docstring Coverage Warning Docstring coverage is 32.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 120 functions across 74 files. (2 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check Passed The PR implements the coding requirements in [#16077]. Runtime upload streaming reports acknowledged byte progress with throttled IPC updates. Preload and web APIs expose the subscription. Staged file…
Title check Passed The title clearly summarizes the primary changes: transfer progress and cancellation for SSH uploads and remote downloads.
Description check Passed The description is complete and relevant. It includes the required explanation, change summary, rationale, linked issue, visual proof, testing details, AI disclosure, scope notes, and checklist. The s…

Full details: Out of Scope Changes check

Explanation

The PR adds SSH upload progress and cancellation, remote file and folder download progress and cancellation, shared transfer UI, and cancellation infrastructure. Issue [#16077] requires runtime dropped-file upload progress only. It explicitly excludes cancellation and does not require SSH uploads or downloads.


Full details: Docstring Coverage

Explanation

Docstring coverage is 32.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 120 functions across 74 files. (2 skipped: 2 unsupported.)


  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8309eb7b-1277-4fc2-9052-9643195067ec

📥 Commits

Reviewing files that changed from the base of the PR and between 31012ae and 60803e5.

📒 Files selected for processing (70)
  • src/main/ipc/filesystem-download-folder.ts
  • src/main/ipc/filesystem-download-progress.test.ts
  • src/main/ipc/filesystem-download-transfers.test.ts
  • src/main/ipc/filesystem-import-ssh-progress.test.ts
  • src/main/ipc/filesystem-import-ssh-progress.ts
  • src/main/ipc/filesystem-import-ssh.ts
  • src/main/ipc/filesystem-mutations.ts
  • src/main/ipc/filesystem/filesystem-download-handlers.ts
  • src/main/ipc/filesystem/remote-download-sessions.test.ts
  • src/main/ipc/filesystem/remote-download-sessions.ts
  • src/main/ipc/runtime-upload-cancellation.test.ts
  • src/main/ipc/runtime-upload-cancellation.ts
  • src/main/ipc/runtime-upload-file-stream.test.ts
  • src/main/ipc/runtime-upload-file-stream.ts
  • src/main/ipc/runtime-upload-progress.test.ts
  • src/main/ipc/runtime-upload-progress.ts
  • src/main/providers/filesystem-provider-contract.ts
  • src/main/providers/ssh-filesystem-download-progress.test.ts
  • src/main/providers/ssh-filesystem-download.ts
  • src/main/providers/ssh-filesystem-file-upload.ts
  • src/main/providers/ssh-filesystem-provider-download-folder.test.ts
  • src/main/providers/ssh-filesystem-provider-sftp.ts
  • src/main/providers/ssh-filesystem-provider.test.ts
  • src/main/providers/ssh-filesystem-provider.ts
  • src/main/ssh/sftp-upload.ts
  • src/main/ssh/ssh-connection.ts
  • src/main/ssh/ssh-relay-session.ts
  • src/main/ssh/ssh-system-fallback.test.ts
  • src/main/ssh/system-ssh-file-binary-transfer.ts
  • src/preload/api/filesystem-api.ts
  • src/preload/api/fs-bridge.ts
  • src/renderer/src/components/right-sidebar/FileExplorerRow.actions.test.tsx
  • src/renderer/src/components/right-sidebar/file-explorer-row-file-transfer.ts
  • src/renderer/src/components/right-sidebar/useFileExplorerImport.ts
  • src/renderer/src/components/terminal-pane/terminal-drop-handler.test.ts
  • src/renderer/src/components/terminal-pane/terminal-drop-paste.ts
  • src/renderer/src/components/terminal-pane/terminal-native-file-drop.ts
  • src/renderer/src/components/terminal-pane/terminal-remote-file-download-open.ts
  • src/renderer/src/components/terminal-pane/terminal-runtime-drop-upload.ts
  • src/renderer/src/components/transfer-progress/TransferProgressPanel.tsx
  • src/renderer/src/components/transfer-progress/download-progress-panel.test.ts
  • src/renderer/src/components/transfer-progress/download-progress-panel.ts
  • src/renderer/src/components/transfer-progress/open-transfer-progress-panel.ts
  • src/renderer/src/components/transfer-progress/transfer-progress-format.test.ts
  • src/renderer/src/components/transfer-progress/transfer-progress-format.ts
  • src/renderer/src/components/transfer-progress/transfer-progress-heading.test.ts
  • src/renderer/src/components/transfer-progress/transfer-progress-heading.ts
  • src/renderer/src/components/transfer-progress/transfer-session-state.test.ts
  • src/renderer/src/components/transfer-progress/transfer-session-state.ts
  • src/renderer/src/components/transfer-progress/upload-progress-panel.test.ts
  • src/renderer/src/components/transfer-progress/upload-progress-panel.ts
  • src/renderer/src/hooks/useGlobalFileDrop.ts
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/runtime/runtime-file-client-download-progress.test.ts
  • src/renderer/src/runtime/runtime-file-client-external-import.test.ts
  • src/renderer/src/runtime/runtime-file-client-test-harness.ts
  • src/renderer/src/runtime/runtime-file-client-types.ts
  • src/renderer/src/runtime/runtime-file-client.ts
  • src/renderer/src/runtime/runtime-file-import-client.ts
  • src/renderer/src/runtime/runtime-file-read-client.ts
  • src/renderer/src/runtime/runtime-file-upload-client.ts
  • src/renderer/src/runtime/runtime-upload-progress-tracker.test.ts
  • src/renderer/src/runtime/runtime-upload-progress-tracker.ts
  • src/renderer/src/runtime/ssh-upload-progress-client.test.ts
  • src/renderer/src/runtime/ssh-upload-progress-client.ts
  • src/renderer/src/web/preload-api/web-filesystem-api.ts
  • src/shared/remote-download-progress.test.ts
  • src/shared/remote-download-progress.ts
  • src/shared/runtime-upload-staging-contract.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread src/main/ipc/filesystem-import-ssh-progress.ts Outdated
Comment thread src/main/ipc/filesystem-mutations.ts Outdated
Comment thread src/main/ipc/filesystem-import-ssh-progress.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Check cancellation after the final chunk is acknowledged. · runtime-upload-file-stream.ts:114-127

src/main/ipc/runtime-upload-file-stream.ts:114-127
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Check cancellation after the final chunk is acknowledged.

cancelSignal is checked before each slice, but the final sendChunk uses only the lifetime signal and is followed by no cancellation check. A user cancellation during that send can therefore let the upload resolve. The renderer then calls files.commitUpload and reports the file as imported.

Suggested fix
     if (afterReadStat.mtimeMs !== openedStat.mtimeMs || !isSameFile(afterReadStat, openedStat)) {
       throw new Error(`File changed during upload: '${displayPath}'`)
     }
+    throwIfCancelled(args.cancelSignal)
     return { byteLength: totalBytes }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: a26a9a1b-d1af-4283-aa68-02632077ee40

📥 Commits

Reviewing files that changed from the base of the PR and between 60803e5 and a5bac70.

📒 Files selected for processing (15)
  • src/main/ipc/filesystem-download-progress.test.ts
  • src/main/ipc/filesystem-import-ssh-progress.test.ts
  • src/main/ipc/filesystem-import-ssh-progress.ts
  • src/main/ipc/filesystem-mutations.ts
  • src/main/ipc/filesystem/filesystem-download-handlers.ts
  • src/main/ipc/runtime-upload-cancel-ipc.test.ts
  • src/main/ipc/runtime-upload-cancel-ipc.ts
  • src/main/ipc/runtime-upload-cancellation.test.ts
  • src/main/ipc/runtime-upload-cancellation.ts
  • src/main/providers/filesystem-provider-contract.ts
  • src/main/ssh/sftp-upload.ts
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/runtime/runtime-file-client-download-progress.test.ts
  • src/renderer/src/runtime/runtime-file-read-client.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/i18n/locales/en.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one minor suggestion inline.

Reviewed changes — the top commit 60803e5, which extends #16107's runtime upload panel to every remote transfer.

  • SSH upload progress and cancel — fs:importExternalPaths / fs:resolveDroppedPathsForAgent take per-source uploadIds; main measures each source locally, counts bytes off the SFTP/system-SSH upload streams, reports on fs:uploadProgress, and a cancel deletes a cancelled exclusive partial remote file.
  • Remote download progress and cancel — fs:downloadFile / fs:downloadFolder take a downloadId; main tracks bytes from fastGet, the system-SSH cat stream and a folder size pre-walk, reports on fs:downloadProgress, and fs:cancelDownload aborts.
  • One shared panel — the upload panel, session store, heading and byte formatting move to components/transfer-progress/ and take a direction; openTransferProgressPanel, createUploadProgressPanel and runRemoteDownloadWithProgress wrap each entry point.

ℹ️ Visual proof is still a TODO

The PR body's "Visual proof" section still reads "TODO: attach the upload, cancelled-upload, download and cancelled-download captures." The checklist requires before/after captures for UI changes; attaching them (or marking the row N/A) would let a reviewer see the cancel/outcome states without driving the dev app.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/renderer/src/runtime/runtime-upload-progress-tracker.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Follow-up commits reviewed — no new issues found.

Reviewed changes — the two commits pushed since the initial review (60803e5), both closing cancel-edge gaps:

  • Window-scoped upload ids — scopeRuntimeUploadId(senderId, uploadId) keys cancellation, and a new runtime-upload-cancel-ipc.ts validates the request and scopes by event.sender.id, so one window cannot cancel another window's upload. The remembered-cancel set is capped at 1024 with oldest-first eviction.
  • Only delete a partial this upload created — FileUploadSession.uploadFile gains onRemoteCreated, fired from ssh2 WriteStream's open event (verified the pinned v1.17 emits open on a successful exclusive open) and from a set -C; : > … marker command on the system-SSH path. Delete-on-cancel now requires it, so an EEXIST loser no longer removes a file another client just wrote.
  • Cancel after the last byte — an SSH import rolls back an imported result when the source was cancelled (covers empty folders and a late click); fs:downloadFile and the chunked runtime download both re-check the signal before promotion; the runtime download loop now throws on a zero-byte non-EOF chunk instead of spinning.

No new defects found in these changes. The earlier note about runtime-upload-progress-tracker.ts's cross-file progress guard is unchanged and still open.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/ssh/system-ssh-file-binary-transfer.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the two commits pushed since the prior pullfrog review at 84dfdcd8e3 (eb51f43f91, 6bfce94e2b), which bound the identity reads a stalled relay can accumulate during an SSH import.

  • Capped abandoned identity requests per connection — readIdentity now skips a new identity read once 64 requests on the same provider are still awaiting a reply, tracked through a module-level WeakMap keyed by the provider instance. The provider is cached per connectionId and rebuilt on reconnect, so the cap is shared by every import a connection runs and a fresh connection starts clean; a skipped read is unverifiable, so its entry is kept and reported rather than deleted.
  • Raised the rollback check timeout to 30 s — ROLLBACK_LSTAT_TIMEOUT_MS grew from 5 s to 30 s so a congested but healthy relay does not trip the relayStalled breaker; the first check that times out still keeps every remaining entry without another relay request.
  • Added a regression test — filesystem-import-ssh-rollback.test.ts records 64 stalled reads across two ledgers sharing one provider, asserts the relay saw exactly 64 lstat calls, and that a third ledger's identity resolves null without a 65th call. It fails if the limit is per-ledger rather than shared, or if the guard is removed.

Verified locally: pnpm test src/main/ipc/filesystem-import-ssh-rollback.test.ts passes (8/8). The changed files are filesystem-import-ssh-rollback.ts and its test only; requireSshFilesystemProvider confirms the provider instance is stable per connection, so the shared counter does span imports on one connection as intended. All prior pullfrog threads are resolved.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the four commits pushed since the prior pullfrog review at 6bfce94 (fe5852b55b, 05844df5ca, a90c8ae18f, 0932186331) plus the main merge resolution, all pre-merge cleanup.

  • Handled a failed upload cancel request — createUploadProgressPanel now attaches .catch(() => {}) to the fs:cancelRuntimeUpload call, matching the download panel, so a failed cancel no longer surfaces as an unhandled rejection; the row stays "Cancelling…" for the existing 15 s unconfirmed fallback.
  • Fixed three inert tests — a new beforeOpen hook on the fake remote lets the "lost the race" and FIFO cases cancel as the transport opens (they previously cancelled before the import started, so they never reached the race or the FIFO); a new case pins the pre-start cancel; and the zero-total progress case now uses a finished row so only the zero-total guard keeps the percentage null.
  • Made two test names match what they prove — the failed-cancel case now runs the rejection handler and asserts the row is left untouched, and the FIFO case is renamed to what it covers (rollback leaves the existing FIFO in place).
  • Dropped review-iteration leftovers, no behavior change — the shared leftover helper is imported at its use site instead of re-exported from the rollback ledger, the ledger's doc comment moved back onto SshImportCreatedLedger, REMOTE_TREE_MEASURE_BUDGET_MS is no longer exported, and the Windows note in ssh-connection is corrected.

The one behavior change follows the download panel's existing cancel pattern, and each changed test was verified to reach its target rather than pass vacuously. All prior pullfrog threads remain resolved.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

…gets

The upload panel (and the SSH upload spinner) was an app-wide toast, so it
followed the user into every workspace and stacked with uploads bound for
other workspaces. It now shows only while the destination workspace is
active, comes back with current progress on return, and a drop that finishes
while the user is elsewhere ends without replaying its outcome. Failures stay
visible everywhere and name the workspace when it is not the active one.

The drop-handler tests share one SSH repo stub so the file stays under the
max-lines limit after the main merge.

Refs stablyai#24151
- A late progress event from one file no longer counts toward the next file
  of the same source: main echoes a per-file sequence and the tracker drops
  events that do not carry the current one.
- The same path dropped twice gets two upload ids, so each copy has its own
  bar and cancel instead of the second overwriting the first.
- Cancelling a source made only of directories stops it before the next
  directory is created and removes the root it already made.
- A finished empty file or folder reads 100% instead of 0%.
- A drop where nothing landed reads "Upload failed" unless every row was
  cancelled, so a cancel no longer hides a failure.
Cancelled sources were filtered out of the failure notice by path, so when the
same path was dropped twice and one copy was cancelled, the other copy's
failure was hidden too. That notice is the only signal once the panel is
hidden in another workspace. The import now marks the cancelled source's own
result, and the drop flow filters on that instead of the shared path.
@mmarabel

Copy link
Copy Markdown
Contributor

Heads-up: #16107 has three new commits that this PR doesn't include yet (head is now b10aea3964):

  • 7d12794c3b: the drop upload panel and the SSH upload spinner now show only in the workspace the files are going to ([Feature]: Show drop upload progress only in the workspace the files are going to #24151). Switching away hides the panel, and switching back reopens it. Failures still show everywhere and name the workspace. The helper is openWorktreeScopedToast in src/renderer/src/lib/worktree-scoped-toast.ts.
  • 33753f67d2 and b10aea3964: fixes for six gaps review found in the feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107 code:
    • A late progress event could count toward the next file of the same source. I copied your fileSequence design and name so our branches match.
    • The same path dropped twice shared one upload id.
    • Cancelling a source made only of folders didn't stop it.
    • A finished empty row read 0%.
    • A drop with both a failure and a cancel read "Upload cancelled".
    • When the same path was dropped twice and one copy was cancelled, the other copy's failure was hidden.

A trial merge of the new head into this branch conflicts in 11 files. Most come from your moving the panel into transfer-progress/, which git reads as renames. What to watch for:

  • fileSequence (runtime-upload-progress.ts, fs-bridge.ts, filesystem-api.ts): keep your side. The only difference is your extra kind field.
  • terminal-drop-upload-heading.ts: deleted here and changed on feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107. The new rule, "Upload cancelled" only when every row was cancelled, needs to move into transfer-progress-heading.ts.
  • Row percent: toRowPercent in terminal-drop-upload-progress.ts (a done row reads 100% with 0 bytes) needs to move into TransferProgressPanel.
  • terminal-runtime-drop-upload.ts and terminal-native-file-drop.ts: these now open the panel through openWorktreeScopedToast, which takes the destination worktree id. If you route SSH uploads through the shared panel, pass that id so they stay scoped too. Downloads aren't tied to one workspace, so they can stay global.

Happy to help with the merge if that's useful.

Comment thread src/renderer/src/runtime/runtime-file-import-client.ts Outdated
Comment thread src/renderer/src/runtime/runtime-file-import-client.ts Outdated
Comment thread src/renderer/src/runtime/runtime-file-import-client.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the merge of #16107 plus the three follow-up commits pushed since the prior pullfrog review at 0932186 (3d812b9a4e, 9ff1f09eb2, ae043f637f, a9bec91d66), which bring workspace-scoped drop panels and report what a cancelled or failed drop left on the host.

  • Merged #16107 into the branch — conflict resolutions keep this branch's window-scoped upload-cancel registry (scopeRuntimeUploadId, registerRuntimeUploadCancelHandlers) and its extra kind progress field; #16107's "cancelled only when every row was cancelled" heading rule and toRowPercent moved into the shared transfer-progress-heading/transfer-progress-format modules. The dead terminal-drop-upload-heading.ts, runtime-upload-session-state and TerminalDropUploadToast are gone with no dangling references, and the main-retired tests stay deleted.
  • Workspace-scoped drop panels — openTransferProgressPanel takes an optional { worktreeId } scope and drives the new openWorktreeScopedToast (src/renderer/src/lib/worktree-scoped-toast.ts), which shows the panel only while that workspace is active, dismisses it on switch-away, and re-issues it under a fresh id on return. Terminal runtime and SSH drops now pass the destination workspace; downloads stay global as before.
  • Per-row cancels — createUploadProgressPanel tracks cancels by uploadId rather than source path and exposes isCancelled, so a path dropped twice can be cancelled once without hiding the other copy's failure. The runtime import re-checks isCancelled before each directory create and removes the no-clobber root it made, covering a folder-only source.
  • Leftover reporting for a closed panel — failuresToReport keeps a cancelled source only when its reason names files left on the host, and the drop failure toast names the destination workspace and the first leftover path (with (+N more)), the only notice once a scoped panel closes unseen.

Verified locally: the changed and new renderer plus main tests set pass (terminal-drop-handler, terminal-drop-upload-report, terminal-drop-workspace-label, worktree-scoped-toast, terminal-runtime-drop-upload, transfer-progress-format/heading, open/upload/download panel, runtime-file-client-external-import, runtime-upload-progress-tracker, filesystem-mutations-runtime-upload, runtime-upload-cancel-ipc, filesystem-import-ssh-progress, runtime-upload-progress).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +199 to +207
if (signal.aborted) {
// Why: covers a cancel after the last byte and an empty folder, which raise nothing.
await ledger.rollback()
return {
sourcePath,
status: 'failed',
reason: describeCancelledImport(ledger.remaining),
cancelled: true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cancel hides an upload failure

If an SSH upload fails—for example, because the remote disk is full—and the user clicks Cancel while the folder's failure cleanup is still running, this branch replaces the actual error with “Upload cancelled” and marks the result cancelled. The explorer then suppresses its failure toast, so the user does not learn why the upload failed.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the four commits pushed since the prior pullfrog review at a9bec91 (91e12f2e32, 1e50c47787, 03a44db0b7, e197ff1edf), the cancel fixes from review of the #16107 merge.

  • Marked a runtime source cancelled only when the cancel stopped it — wasStoppedByCancel now also requires the error to read as the cancel reason, so a dropped connection or failed mutation that lands after a Cancel click is reported as a failure instead of being hidden as cancelled.
  • Checked a directory cancel before and after each runtime create — the shared stopIfCancelled now runs on both sides of files.createDirNoClobber, so a cancel before the first directory writes nothing, and one landing mid-request stops the source and runs the existing root cleanup.
  • Decided a cancel per result, not per dropped path — ImportItemResult and ResolveDroppedPathsResult carry an optional cancelled; SSH imports set it in main from the source's cancel handle, fs:resolveDroppedPathsForAgent passes it through, and the explorer, editor and terminal drops read it. The upload panel drops its cancelledSourcePaths set, so cancelling one copy of a twice-dropped path no longer hides the other copy's failure.

Verified the surrounding code: the runtime cancel reason survives Electron's IPC wrapper (extractIpcErrorMessage), there are no dangling cancelledSourcePaths references, and each new test reaches its target (the wasStoppedByCancel reason check, the before/after directory checks, and the exact cancelled arrays in main and the dropped-paths handler). All prior pullfrog threads remain resolved.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the two commits pushed since the prior pullfrog review at 03a44db (20b936897c, 7debe2415a), which merge main's SSH file-transfer move and restore the rollback hook the move dropped.

  • Merged main's SSH file-transfer extraction — SshConnection.downloadFile and openFileUploadSession now delegate into ssh-connection-file-transfers.ts through fileTransferHost() and the work ledger; the branch's download progress/cancel observers and the upload cancel signal, onRemoteCreated/onBytesTransferred, and POSIX removeCreatedEntry are carried across. The two SFTP observer listeners moved out of sftp-upload.ts into sftp-upload-observers.ts, unchanged.
  • Restored removeCreatedEntry through the tracked upload session — openTrackedSshUploadSession now forwards the optional method inside the session lifetime, so a cancelled SSH import's rollback uses the remote's guarded non-recursive removal instead of keeping folders and deleting files by path; a Windows host still omits the method, preserving its keep-folders fallback. New tests in ssh-upload-session-lifetime.test.ts pin both halves.

Verified locally: pnpm tc:node is clean, and ssh-upload-session-lifetime, ssh-connection-file-transfer, sftp-upload, filesystem-import-ssh-cancel-invariants, filesystem-import-ssh-progress, and ssh-connection-system-work-drain pass (58 tests). The relay-session upload path reaches this wrapper via ssh-relay-session.ts → rawTransfer.openFileUploadSession, so the restored method is on the live import path.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +79 to +81
invoke('fs:cancelRuntimeUpload', sender, { uploadId: 'b' })
sender.emit('render-process-gone')
expect(isUploadCancelled(scopeRuntimeUploadId(3, 'b'))).toBe(false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Closed-window coverage was removed
The rewritten test covers reloads and renderer crashes, but no longer checks that closing a window clears its remembered upload cancels. If that cleanup path regresses, this IPC suite could pass while stale cancels remain in main. Please retain a closed-window assertion.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the two commits since the prior pullfrog review at 7debe2415a: the merge of #16107's two newest commits (98d6c8efa6, bringing 363dea098b and de80e94010) and the doc commit 5d12792d62 (7 files, +125/-74).

  • Adopted #16107's hardened upload-cancel IPC — the add/add conflict in runtime-upload-cancel-ipc.ts resolves to #16107's version: abortWhenRendererGone wiring, an inline readUploadId check replacing the branch's shared parseTransferId, and no test-only sender.once guard. Behavior is equivalent — same validation, same per-sender scoping via scopeRuntimeUploadId(event.sender.id, uploadId) — and production always passes a WebContents. registerRuntimeUploadCancelHandlers stays imported (filesystem-mutations.ts:11) and called (:284).
  • Cancellation registry and tests aligned with #16107 — the registry, its IPC test and the cancellation test now match #16107 (adding the release-race case); runtime-upload-progress.ts keeps the branch's kind field, only a redundant comment dropped.
  • New cross-renderer cancel test — filesystem-mutations-runtime-upload.test.ts drives the real fs:uploadExternalFileToRuntime and asserts a same-uploadId cancel from another renderer does not abort the owner's upload while the owner's does; it holds only because registration and cancellation share the sender scope.
  • Doc-only — transfer-id.ts scopes parseTransferId to download ids; its sole caller is remote-download-sessions.ts.

The remaining conflicts resolved back to the branch's newer work: ssh-connection.ts keeps its sftp()/sftpUntracked work-ledger split, terminal-native-file-drop.ts keeps createUploadProgressPanel, terminal-drop-upload-report.ts keeps the leftover-note join, and en-runtime-required.json drops #16107's obsolete drop.handler.upload.heading keys (no remaining references) while keeping transferProgress.*.

Verified: pnpm test on the four touched suites (38 tests pass) and pnpm run check:code-quality:changed is clean. No open Pullfrog threads to retire.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

if (onTotalBytes) {
// Why: a timed-out walk left big trees, the ones that need a bar, with none; walking
// alongside the download costs no wait and supplies the total whenever it finishes.
void measureRemoteTree(sftp, sourcePath, options)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unbounded folder size walk

For a large folder, the size walk now has no time limit and runs beside the download on the same SFTP channel. Both operations list the folder tree, so the walk can compete with file reads throughout the transfer. The former two-second budget limited that cost.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one stale contract comment to update.

Reviewed changes — the single commit pushed since the prior pullfrog review at 5d12792d62 (60d329f9cd), which gives a folder download a moving bar.

  • Folder size walk now runs alongside the download — downloadFolderViaSftp starts measureRemoteTree without awaiting it and reports the total when the walk finishes, dropping a total that arrives after the download settled; the 2 s budget and its null return are gone, and a failed walk or a throwing observer is swallowed so it never fails the download.
  • Indeterminate row bar while the total is unknown — TransferProgressPanel renders an animated, aria-valuenow-less role="progressbar" for an active row with no total, keeping the determinate <Progress> once the total is known and for ended rows.
  • Tests — the SFTP progress suite replaces the budget case with four cases (total lands mid-transfer, observer throws, slow walk does not block and its late total is dropped, walk failure still downloads), and a new TransferProgressPanel.test.tsx covers the animated/determinate/ended bar states.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/providers/ssh-filesystem-download.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the two commits pushed since the prior pullfrog review at 60d329f9cd (3689d8d21f, c20a5f85dc), which keep a failed SSH upload's real error and correct a stale contract comment.

  • Kept a failed SSH upload's real error — importOneSourceSsh now reports the failure to importSshSourceWithProgress through an onFailure callback that runs before any cleanup, and the cancel branch only relabels the result as cancelled when the failure did not precede the click; otherwise the real reason is returned (with any rollback leftovers appended).
  • Corrected the onTotalBytes contract — the shared observer JSDoc now says a folder's total may arrive mid-transfer and is never reported after the transfer ends, matching the alongside-the-download walk.

The new tests are non-vacuous: the progress suite cancels from a beforeRemove hook so the click lands inside cleanup, and the rollback suite pins both directions (failure-then-cancel keeps the real reason; cancel-caused failure still reports as cancelled). The prior review's inline nit is addressed by c20a5f85dc, and its thread is resolved.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the single merge pushed since the prior pullfrog review at c20a5f85dc (2595868489), which merges main after #24559 reverted #24401.

  • Restored the branch's transfers inline — main's revert deleted ssh-connection-file-transfers.ts and ssh-upload-session-lifetime.ts, and git auto-merged downloadFile / openFileUploadSession to main's plain bodies, silently dropping this branch's download progress/cancel, upload cancel signal and callbacks, and removeCreatedEntry. The merge restores both methods in ssh-connection.ts to this branch's pre-#24401 form, keeping main's unrelated changes.
  • Verified faithful — the restored methods (uploadDirectory, downloadFile, openFileUploadSession, writeFile, writeBuffer) match the previously approved 03a44db0b7 state; pnpm tc:node is clean, no references to the deleted modules or work ledger remain, and the SSH transfer suites pass (8 tests).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the main merge 88f930fe74 (main at e2da3a1eb1), the only commit since the prior pullfrog review at 2595868489.

  • Merged main's LocalFileAccess file-access feature — main's #24489 replaced filesystem-auth with local-file-access-resolution and added access?: LocalFileAccess to fs:readFile / rename / stat / pathExists / importExternalPaths. All five conflicts (filesystem-mutations.ts args, filesystem-api.ts / fs-bridge.ts field + import, runtime-upload-file-stream.ts's removed authorizeExternalPath, runtime-file-import-client.ts options) were resolved by keeping both sides.
  • Kept the branch's import path intact — runtime-file-import-client retains its progress option and runSshUploadWithProgress, adding main's access and ...localAccess(...) to the local import call only, so remote destinations stay root-relative.
  • Extracted the runtime upload tracking — trackRuntimeUploadForSender moved from the fs:uploadExternalFileToRuntime handler into runtime-upload-cancel-ipc.ts, behavior-identical to the prior inline wiring (null when no uploadId, same throttle/emit, same release).

Verified: pnpm tc:node and pnpm tc:web are clean; filesystem-mutations-runtime-upload, runtime-upload-cancel-ipc, runtime-upload-cancellation, and runtime-file-client-external-import pass (34 tests); pnpm run check:code-quality:changed reports 0 findings. git show --remerge-diff 88f930fe74 confirms every conflict kept both sides and dropped no branch behavior.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@OrcaWin

OrcaWin commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this, @Chihen-Tai. Quick update: the legacy SSH relay has been removed (#27214, #27215), and SSH workspaces now run on Orca's managed server on the host, so their uploads and downloads go through the same remote-runtime transfer path as #16107 rather than the old desktop-side SFTP import. The idea here (one progress panel with cancel for remote transfers) still applies, but the SSH-specific half would need to be reworked onto that runtime path when this is rebased.

Microdaery and others added 2 commits October 11, 2026 16:42
Resolved against main's owner-routed runtime target (settings -> target),
the shared streamExternalFileSlices streamer, and the terminal drop split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d downloads

SSH workspaces now run on the host's managed runtime, so their transfers use
the same runtime path as remote Orca servers. This builds on stablyai#16107's runtime
upload progress instead of the removed desktop-side SFTP import:

- stablyai#16107's terminal-drop panel moves to components/transfer-progress and takes
  a direction; file explorer and editor drops use it too.
- Runtime downloads (explorer Download, terminal "download & open") count the
  chunks the renderer already pulls, show the panel, and cancel between chunks
  through an AbortSignal; the temp file is discarded and nothing is committed.
- SSH workspace files download through the host runtime instead of the SSH
  filesystem provider, which nothing registers anymore. Folder downloads keep
  their existing SSH-only path.
- Runtime import cancel fixes: directories check the cancel before and after
  each create, and a source is marked cancelled only when the cancel is what
  stopped it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Chihen-Tai
Chihen-Tai force-pushed the feat/remote-transfer-progress branch from 88106e8 to 43c7743 Compare October 11, 2026 09:15
Chihen-Tai pushed a commit to Chihen-Tai/orca that referenced this pull request Oct 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Show progress while uploading dropped files to a remote workspace

5 participants