* feat(terminal): Add configurable terminal bar cursor width
Adds a configurable bar-cursor width for the terminal so thin vertical cursors are easier to find, particularly when vi/vim switches to insert mode.
* Add terminal bar cursor width setting
This adds the terminal bar cursor width control to the settings UI with an aria-label for accessibility and a matching test. It also adds localized labels/descriptions and search catalog entries so the setting is discoverable in both English and Chinese.
* Fix terminal cursor width override bug
Prevent width-only cursor updates from clearing DEC/vi cursor overrides by tracking the last applied shape/blink snapshot. The runtime now resets the snapshot on teardown, skips full cursor reapplication for width-only changes, and preserves remote bar/underline cursor styles. Added regression tests covering boot-time and later width updates.
* Fix terminal cursor preference lifecycle
Preserve DEC/vi cursor overrides during the first width-only terminal update after runtime creation. The runtime now records the user cursor preference snapshot when a term is published and applies width-only updates without clearing active cursor style or blink overrides. This prevents regressions when terminal settings change before the panel has fully booted.
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* terminal: allow Shift+Enter text inside ConPTY Win32 input mode
Win32 input mode (PR #3247) delivers Shift+Enter to ConPTY as a lossless
INPUT_RECORD, which CLIs reading console records natively (Codex, Grok) can
use. Node/Bun CLIs read input through libuv, which drops modifier state when
translating INPUT_RECORD to VT bytes, so Claude Code and CodeBuddy still see a
bare CR and submit instead of inserting a newline (anthropics/claude-code#92771,
reproducing on 2.1.278).
While the mode is active, Netcatty also skips its Shift+Enter text fallback and
Kitty encoding, so shiftEnterNewlineText had no effect on local Windows
sessions at all.
Add shiftEnterForceText (default false): when enabled, Shift+Enter sends the
configured text even inside Win32 input mode, and consumes the keydown so no
native record is emitted as well. Add `\e` (ESC) to the escape decoder so the
text can carry the legacy Alt+Enter encoding, which is the only sequence that
survives the ConPTY input path.
Defaults keep the #2974 behaviour untouched for Codex/Grok.
* docs: record libuv input findings and the Win32 input mode opt-out
* fix(terminal): don't swallow Shift+Enter when there is no text to send
The Shift+Enter send-text interception resolved its text after already
calling preventDefault/stopPropagation, and returned false outside the
`if (shiftEnterText)` guard. When resolveShiftEnterText() yields an empty
string (e.g. a persisted empty setting) the keydown was therefore consumed
with nothing written, and the handler never fell through to the Win32
bookkeeping or Kitty paths below, so no Enter reached the session either.
The result was indistinguishable from a dead feature.
Resolve the text first and only enter the interception block when it is
non-empty; empty text now falls through exactly as it did before the
`shiftEnterForceText` setting existed, so a normal Enter is still produced.
The opt-out semantics for the non-empty case are unchanged.
Also tighten the source-shape test so it verifies the real intent: the
interception (preventDefault through return false) must live inside the
empty-text guard, which fails against the previous code.
* fix(terminal): scope Shift+Enter force-text to Win32 mode, honor it per broadcast target
Address review on #3464:
- The force-text opt-out could override a negotiated Kitty encoding that
preserves Shift+Enter in non-Win32 sessions. The keydown decision now
lives in shouldClaimShiftEnterForText: under Win32 input mode it follows
shiftEnterForceText, otherwise only the existing collapse fallback
applies. Covered by a regression test for a non-Win32 session whose Kitty
encoding already preserves the chord.
- Broadcast Win32 targets returned the native record without consulting
their own shiftEnterForceText. They now send their configured text for
Shift+Enter when opted in, whether the source forwarded a key chord or a
native Win32 record, and drop the paired release so ConPTY never sees a
key-up without its key-down. Targets without the opt-out keep the native
press/release pair. Tests cover source and target settings that differ.
ShiftEnterEvent now accepts KittyKeyboardEvent's optional type/modifier
fields so broadcast events can be checked with the same helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: prevent cloud sync version inflation when idle
Three bugs combine to cause an infinite upload loop where the sync
version increments every ~2.5 minutes even with no user interaction:
1. Smart merge unconditionally uploads (useAutoSync.ts):
`if (mergeResult.payload)` is always true since payload is an object.
Now checks `cloudSyncPayloadsEqual(portableMerge, remotePayload)`
before uploading — if merged result matches remote, skip the upload
and mark data as synced.
2. Download-remote re-uploads to same single provider (useAutoSync.ts):
When only one cloud provider is connected, downloading the remote
payload and immediately re-uploading it to the same provider bumps
the version unnecessarily. Now checks `readyProviders.length > 1`
before round-tripping.
3. applySyncableSettings bumps versions unconditionally (syncPayload.ts):
customAccent, terminalFontSize, and customKeyBindings handlers always
bump their storage version counters even when incoming values are
identical to existing values. This fires LOCAL_STORAGE_ADAPTER_CHANGED_EVENT
and triggers a debounced syncNow(). Now guarded with change detection:
- customAccent: compare parsed color values
- terminalFontSize: compare fontSize numbers
- customKeyBindings: compare JSON-serialized bindings
Observed impact: lastSyncVersion reached 7796+ with LevelDB compaction
every ~155 seconds matching getRuntimeRemoteCheckIntervalMs(5).
* fix(sync): avoid repeated versioned setting writes
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* Add local shell side panel auto-open settings with i18n support
- Add settings to control side panel behavior for local shell sessions
- Support auto-opening side panel when local shell is launched
- Allow users to choose which pane (Files, SFTP, Scripts, etc.) opens by default
- Add complete i18n translations for en, zh-CN, zh-TW, es, ru locales
* fix: wire local shell side panel settings
* fix: restore undefined check for local shell protocol detection
Local shell sessions may have protocol=undefined, not just 'local'.
Restore the original condition to auto-open sidebar for both cases.
* fix: enable SFTP sidebar for local shell sessions
Local shell now supports SFTP (for local file browsing).
Update sftpAvailable to include local shell so the auto-open
sidebar setting works correctly for PowerShell/bash sessions.
* chore: bump version to 1.1.83-fork.1
Distinguish fork from upstream (1.1.83) and previous 0.0.0 dev version.
* feat: show SFTP button in local shell toolbar
Local shell sessions now display the 'Open SFTP' button in the toolbar,
allowing users to browse local files via SFTP sidebar, consistent with
the auto-open behavior added earlier.
* feat: 本地Shell侧边栏支持追随终端目录功能
- 追随终端目录现在支持本地 POSIX shell(bash/zsh/fish)
- 排除 PowerShell 和 cmd(因为它们的 cd 命令和路径格式不同)
- 串口连接仍然不支持(它们根本没有 SFTP)
- 定位路径到终端功能已支持本地 Shell(无需修改)
* feat: support PowerShell and CMD in session restore
- Accept Windows absolute paths (C:\...) in isRestoreCwdPathEligible
- Add shell-specific cd commands:
* PowerShell: Set-Location -LiteralPath with '' quote escaping
* CMD: cd /d with "" quote escaping
* bash/POSIX: cd -- with '\'' quote escaping
- Remove PowerShell/CMD restrictions in cwd injection eligibility
- Fix line terminator in SFTP locate path (use \r\n for Windows PTY)
* fix: bound local shell panel preferences and synchronize settings
---------
Co-authored-by: HHXXYY123 <HHXXYY123@outlook.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Introduce a new TerminalTabDoubleClickBehavior setting (duplicate | copy | disabled) with default 'duplicate'. Add model field, normalization, default, and sync key (tabDoubleClickBehavior). UI: TerminalBehaviorSettings adds a Select; AppView and TopTabs pass the setting through; TopTabItems implements createTopTabSessionDoubleClickHandler to perform duplicate/copy with a safe fallback. Add i18n strings, settings search entry, and update tests to cover behavior and serialization. Improves user control over double-click actions on session tabs.
Select default command-safety rules from the live session shell so legal
PowerShell subexpressions are allowed and PowerShell-native dangerous
commands are blocked. Untouched pre-shell-aware lists gain the new
defaults on read.
Closes#3256
Codex P2: agentThinkingMap was persisted locally but omitted from
collect/apply sync, so backups and other devices restored provider
and model without the chosen reasoning level.
Closes#3019
Codex and other agents emit OSC 9 when a turn completes or approval
is needed, but Netcatty ignored those sequences. Register OSC 9, 777
notify, and kitty OSC 99 handlers, map them to Electron notifications,
and reuse tray focus so a click jumps back to the emitting session.
Ignore ConEmu OSC 9;n progress sequences so they do not become toasts.
* feat(portforward): make main process authoritative for runtime state
Phase 1 of #2288: add snapshot/runtime event protocol with epoch+revision,
stop persisting active/connecting/error into localStorage, migrate legacy
stored phases on read, and project unknown when snapshots are unavailable.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(portforward): access runtime bridge via netcattyBridge
Satisfy the no-restricted-properties lint rule for window.netcatty.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(portforward): honor snapshot authority and prune on resync
Default runtime projection to unknown until a successful snapshot,
reconcile (not sync-only) on epoch/revision gaps, serialize runtime
event handling, clear worker runtime subscribers on window destroy,
and align tray unknown UX with RuleCard.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): instant folder-drop scan UI and drop full-tree pre-scan
Restore interleaved folder transfers as the only non-compressed path, remove
the folder pre-scan setting, and make local folder drops show a scanning task
with live discovered counts via native listLocalTree (with cancel + progress).
UI shows "done · found" instead of a fake grand-total percentage while
discovery is still growing.
* fix(sftp): make folder scan cancellation bridge-safe
* fix(sftp): preserve path-only terminal drops
* fix(terminal): accept native folder drop paths
* fix(sftp): keep scan task cancellable during setup
* fix(sftp): preserve scan failure state during setup errors
* fix(sftp): keep folder scan task across retries
* fix(sftp): cancel scan task immediately
* fix(sftp): keep cancellation active through session setup
* fix(sftp): settle failed drop upload retry
* fix(sftp): preserve cancellation in final scan cleanup
* fix(sftp): harden folder scan cancellation and conflicts
* fix(sftp): finish Codex follow-ups on folder scan cancel UX
- Abort sibling listLocalTree walks when one multi-root scan fails
- Prefer ERR_*_CANCELLED over matching "cancel" in error text
- Clear scanning phase on settle; only label live rows as Scanning
- Omit directory metadata sizes from drop entries used for totals
* fix(sftp): stream folder discovery into concurrent uploads
Local path-backed folder drops now edge-scan and edge-transfer: listLocalTree
streams entry batches while upload workers consume them, so the first files
start before a 100k-file walk finishes. Cancel aborts the walk, clears the
queue, and stops creating child tasks. Compressed uploads still materialize
fully then pack.
* fix(sftp): harden progressive folder UX, retry, and size-based uploads
Align the SFTP panel with transfer-center progress, keep nested tree batches
until tree-end, honor pause during progressive enqueue, add low-cost retry for
external drag-drop files, and drop whole-file upload digests for industry
size-based resume (download live-log prefix checks unchanged).
* fix(sftp): address multi-agent review on progressive upload and retry
Gate progressive drops to path-backed folders only, prefer pool sessions on
drag-drop retry with in-flight and cancel guards, soft-check upload mtime
without fail-open contentVerifiedSeparately, skip multi-root pause HOL,
and fail closed when the local tree stream misses tree-end.
* fix(sftp): finish multi-root pause HOL and parent rollup guards
Race-wait on all latched parents so resuming a non-head folder can proceed,
and only roll progressive parents to completed after terminal failed status.
* fix(sftp): address Codex progressive conflict and hang findings
Add root-level conflict preflight to progressive folder uploads, wake all
backpressure waiters, preserve cancel listeners across uploadEntriesDirect,
and surface empty-directory mkdir failures.
* fix(sftp): parallel folder discovery and unblock transfer stalls
Address #2712 / #2556 leftovers after the pipelined fail-closed work
(#2449 / #2720 / #2761): keep bulk body I/O on concurrent READ/WRITE only,
surface upload digest prep as verifying instead of a silent hang, make the
fastPut path-gate wait cancelable (#2755), parallelize multi-select enqueue,
and bound-parallelize directory listings so folder totals appear faster.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(sftp): folder pre-scan and skip unchanged files
Absorb rsync strengths without an rsync backend: optionally walk the
full tree before transfer for stable file totals, and skip files whose
size and mtime already match the destination.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): drop unused DiscoveredTransferFile import
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: address Codex review on PR #2772
* fix(sftp): address Codex review on path gate, aliases, and worker drain
- Invalidate same-transfer fallback when a force-released path gate gets a late truncating OPEN
- Use per-branch ancestors so sibling symlink aliases copy under pre-scan BFS
- Keep interleaved subdirectory walks sequential for deterministic manifest indices
- Drain transfer/bounded workers before propagating errors; ASCII punctuation
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): canonical pre-scan file order and pre-2001 mtime skip
Sort discovered files with compareDirectoryTraversalPaths before index
assignment, and treat timestamps >= 1e11 as milliseconds so year-2000
local mtimes still match SFTP second precision.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): bound multi-select downloads and preserve dest mtime
Address Codex review on PR #2772: serialize local blob reads, bound
multi-select root downloads before pre-scan, replace remaining Unicode
em dashes, and stamp destination mtimes after successful transfers so
skip-unchanged can match on a later pass.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): keep in-place OPEN poison until callback settles
Address Codex review on e2cc8241: do not force-release path gates for
in-place truncating OPENs, fail closed on pending-OPEN wait timeouts
without clearing poison, and replace remaining Unicode em dashes in
ASCII-only transfer modules.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): fail closed when in-place OPEN poison is held
When an isolated in-place truncating OPEN leaves the path gate poisoned
after channel error/cancel, mark the transfer terminal (noTransferFallback)
so fastPut/concurrent-shared cannot wait forever on the unreleased prior
gate.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): omit cyclic dirs from pre-scan plan and lower mtime cutoff
Only record discovered directories after a visit is accepted so symlink
cycles are not mkdir'd as empty targets, and treat magnitudes >= 1e10 as
milliseconds so early-1970s local Date values still match SFTP seconds.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): count max-depth symlink gaps and skip symlink quick-check
Pre-scan now surfaces max-depth symlink directory omissions as transfer
errors (matching interleaved walk), and discovered file symlinks carry
isSymlink so skip-unchanged does not trust link-node size/mtime.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): unit-aware skip mtimes and interleaved symlink bypass
Declare mtimeUnit on skip identities so early-1970 millisecond values
normalize without magnitude cutoffs, and bypass skip-unchanged for
symlinks on the interleaved (non-prescan) directory walk as well.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): bound local pre-scan and preserve SCP destination mtimes
Apply the same directory/entry traversal budget to local folder pre-scan,
and best-effort touch SCP upload targets after commit so skip-unchanged
can match on a later pass.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): address Codex review on pause, skip restat, mtime bound
- Honor transfer pause during folder pre-scan BFS waves/listings
- Re-stat source immediately before skip-unchanged on pre-scanned files
- Bound best-effort destination setStat/utimes so sendComplete cannot hang
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): harden skip restat and pre-transfer mtime capture
- Require a fresh source stat before skip-unchanged (prescan + interleaved)
- Capture source soft identity before bytes move for non-resumable/SCP
- Drop post-commit mtime re-stat fallback; keep ASCII in touched comments
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): propagate fresh re-stat size into interleaved transfers
Use the re-statted source size/mtime for child tasks on the non-prescan
path so grown files are not truncated to the stale listing size.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): omit stale listing size after re-stat failure
When source re-stat fails, clear child totalBytes/mtime so
startStreamTransfer omits an explicit snapshot and the bridge
re-stats on the dedicated session instead of truncating to the
stale listing size.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): make soft-identity capture cancelable
Race pre-transfer source soft-identity capture against cancel and
pass the transfer abort signal into requireSftpChannel so a dead
SFTP reopen cannot block cancel for the channel timeout.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* fix(credentials): stop double-encrypting undecryptable safeStorage blobs
When local decrypt failed (e.g. OSCrypt key churn after reboot), encrypt
used to wrap the leftover enc:v1 ciphertext again, permanently poisoning
the vault. Startup sync could also push those placeholders to cloud and
make download restore the same poison.
- Keep real enc:v1 blobs unchanged on encrypt (header check, no wrap)
- Strip device-bound placeholders when applying portable sync payloads
- Guard startup local-wins / merge round-trips before upload
- Skip vault init re-encrypt writes when secrets are still undecrypted
Fixes#2702
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): strip enc:v1 secrets from smart-merge uploads
Bugbot found that after local apply sanitized placeholders, legacy v1
smart-merge could still decrypt an unstripped remote, merge it, and
re-upload the poison. Strip device-bound credentials on remote decrypt
and again on the merged payload before upload.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): address Codex P2 review on #2702
- Always re-encrypt vault init batches so plaintext siblings are not
left unprotected when one record is a stale enc:v1 placeholder
- Sanitize device-bound secrets before convergent restore prepare so
CRDT replica commit matches vault import
- Require a complete safeStorage blob (header + min 31 bytes) before
treating enc:v1 as ciphertext; encrypt header-only coincidences
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): address Codex P1 review on #2702
- Decode enc:v1 payloads with atob in the renderer-safe domain helper
- Heal poisoned remote secrets from local/base before smart-merge so
good credentials are not discarded as remote-only deletions
- Keep post-merge strip so leftover enc:v1 never uploads
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): address Codex P1 CBC min and startup heal
- Accept 19-byte v10/v11 CBC OSCrypt blobs (not only 31-byte GCM)
- Heal poisoned remote secrets before startup smart-merge
- Strip unresolved placeholders on merge round-trip upload
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): sanitize payloads at every apply/commit boundary
Codex P1: vault apply stripped enc:v1 while commitRemoteInspection and
convergent materialization could keep the poison. Sanitize merged/remote
payloads before apply+base commit, strip on CRDT materialization, and
sanitize convergent apply inputs.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): heal local and remote secrets before smart-merge
Smart-merge could select a locally-changed entity whose only secret
delta was enc:v1 poison, then strip and upload empty secrets. Heal
both sides from the opposite payload/base before merge on all sync
paths.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): keep enc:v1 intact during convergent materialize
Stripping device-bound secrets inside materializeSyncPayloadFromConvergentState
broke envelope validation for poisoned-but-consistent v2 snapshots, so decrypt
could not hydrate the clouds #2702 needs to recover. Portable stripping stays
at apply/upload boundaries.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): detect Windows DPAPI by decoded header bytes
Real DPAPI blobs start with 01 00 00 00 d0 8c... and base64-encode as
AQAAANCM..., so the previous AQAAAA string prefix rejected them and
allowed double-wrapping after key rotation. Match decoded headers in
both the main-process bridge and renderer predicate.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(credentials): use complete enc:v1 fixtures for stricter detector
Short placeholders like enc:v1:djEwAAAA no longer pass the platform
header + minimum-size checks. Update auth/SFTP/proxy/sync fixtures to
full v10-shaped blobs so npm test matches production validation.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): treat preferred credential deletions as authoritative
When healing enc:v1 before merge, an empty/missing secret on a present
preferred entity is an intentional clear and must not be revived from
base. Only fall back to base when preferred is absent or also poisoned.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): reject impossible v10/v11 ciphertext lengths
Accept only CBC-aligned sizes (19+16n) or GCM-sized blobs (>=31) so
coincidental enc:v1 plaintext of intermediate length is encrypted instead
of treated as an undecryptable placeholder.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(credentials): require full DPAPI provider GUID signature
Accept Windows safeStorage blobs only when they start with version
01 00 00 00 plus provider GUID df9d8cd0-1501-11d1-8c7a-00c04fc297eb,
so coincidental 01 00 00 00 prefixes are not treated as ciphertext.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(#2745): automated Cursor CLI fix
* fix(sftp): reset IME composing guard when filter bar closes
If the filter input unmounts mid-composition (filter bar toggled closed),
compositionend never fires, leaving filterComposingRef stuck at true and
blocking every later onSetFilter commit. Clear the guard when the bar hides.
Also drop the now-unused startTransition prop from the toolbar (its only
use was the removed deferred filter path).
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): resync filter draft to committed filter when bar closes
Resetting only the composing guard left filterDraft diverged from pane.filter
when the bar closed mid-composition, so reopening showed stale uncommitted text.
Resync the draft to pane.filter on close.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): clear IME composing guard in commitFilterValue
Centralize the guard reset so every commit/clear path (composition end,
Escape, inline clear button, close button) finalizes the IME session. The
inline clear previously left filterComposingRef stuck true, blocking later
onChange commits and letting a delayed compositionend undo the clear.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): honor external filter change over stale draft on composition end
If navigation (e.g. follow-terminal-CWD) clears pane.filter mid-composition,
onCompositionEnd previously re-committed the stale composed draft, overwriting
the navigation-clear invariant. Capture pane.filter at composition start and
adopt any external change on end instead of committing the stale draft.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): supersede IME composition when navigation clears filter
Navigation can clear pane.filter mid-CJK composition. Adopt that external
clear into the draft while composing, arm a supersede latch so the common
post-compositionend onChange cannot re-commit the stale draft, and reset
composing/supersede state when the filter bar unmounts or a commit path runs.
* fix(sftp): supersede IME filter draft when path navigates
When the committed filter was already empty before composition,
directory navigation sets filter to "" again so pane.filter does not
change. Track path at composition start and supersede the draft when
the path changes so compositionend cannot re-commit stale IME text.
* fix: address Codex review on PR #2746
* fix(sftp): clear IME supersede latch if no post-change arrives
After compositionend arms the supersede latch, schedule a macrotask
fallback clear so ordinary keystrokes work when the browser never
fires the post-composition onChange that would clear it.
* fix: use ASCII punctuation in PR comments and tests
Replace em dashes with ASCII hyphens in comments/tests. Keep the guarded
setTimeout supersede-latch clear from the prior IME fix.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(plugins): address residual Codex sync secret and backup gaps
Retain provider→plugin secret bindings so disconnect can wipe credentials
after a plugin is disabled, keep overwritten secrets on failed reconnect,
and snapshot live-empty sidecars in protective backups.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): restore overwritten sync secrets on rejected reconnect
Stash prior plaintext and SecretRef id on overwrite, restore them when
connect fails, and discard the stash after a successful reconnect so
saved provider credentials keep resolving.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): make sync secret overwrite stash opt-in
Only the syncPutSecret path stashes prior plaintext/SecretRef so ordinary
secrets.set overwrites do not leave credentials in process memory.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): host-own sync provider bindings and clear failed stashes
Move provider→plugin bindings into a dedicated DB table outside
plugin-writable secrets, validate provider namespace ownership, and drop
overwrite stashes when replacement writes fail.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): keep overwrite stash until restore write succeeds
Only drop the prior plaintext after encrypt+upsert succeeds so a failed
restore can still retry and recover the saved SecretRef.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): preserve overwrite stash across retries and prefix deletes
Do not replace an existing restore stash on later puts, and clear matching
stash entries when deleteByKeyPrefix wipes sync credentials on disconnect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(plugins): add sync providers and encrypted sidecar sync
Implement PR 8 of the plugin platform (#2269): namespaced sync Providers
with provider.sync, encrypted-object storage operations, WebDAV through the
shared storage surface, dynamic cloud provider IDs, and non-cascade sidecars
for sync:true plugin settings plus account/CRDT baselines.
Related to #2269
* fix(plugins): wire sync providers and sidecars into production paths
Close PR 8 integration gaps: dynamic plugin provider registry and
getConnectedAdapter createPluginStorage, WebDAV through EncryptedObjectStorage,
cloud payload collect/apply for pluginSidecars, and IPC/preload bridges.
Related to #2269
* fix(plugins): preserve WebDAV auth reuse and resourceId through EOS wrap
encryptedObjectStorageAsCloudAdapter now reports initiallyAuthenticated when
config exists (matching raw WebDAVAdapter) so getConnectedAdapter reuses the
cached instance, and preserves/refreshes resourceId from the backing adapter
instead of always forcing the default object key.
Related to #2269
* fix(plugins): address Codex PR 8 review findings
Carry pluginSidecars through merge/convergent uploads, lazy-connect before
object I/O with revision-aware writes, fail closed on sidecar collect errors,
expose connectPluginProvider, preserve remote timestamps, and drop deleted
installed-plugin settings from collection.
Related to #2269
* fix(plugins): close remaining Codex P1/P2 sync issues
Lazy stream cancel for inline sync reads, must-not-exist conditional writes,
three-way sidecar merge for local resets, validated apply path for settings,
and merge remote sidecars into convergent uploads.
Related to #2269
* fix(plugins): harden sidecar collect/apply and write verification
Preserve last-known sidecars when the host is offline, apply remote
deletions for installed plugins, force convergent upload on sidecar
divergence, verify plugin object writes, and pick plugin providers in
auto-sync startup checks.
Related to #2269
* fix(plugins): close remaining Codex sidecar/sync path gaps
Include sidecars in auto-sync payload and hash, keep explicit empty bundles,
fail apply when host is offline, require getCapabilities, and carry merged
sidecars into convergent local apply/upload decisions.
Related to #2269
* fix(plugins): close Codex P1 sidecar collect/apply/merge gaps
Treat null host responses as unavailable, persist collected settings into
the non-cascade table, retain decoded convergent payloads, use three-way
sidecar merge for deletions, and fail apply only on operational errors.
Also treat non-empty plugin sidecars as meaningful sync data and re-run
auto-sync when plugin contributions change.
Related to #2269
* fix(plugins): close remaining Codex P1 sidecar and provider gaps
Replay offline-cached sidecars before collect, keep explicit empty
sidecar fields after three-way merge, thread sidecars through convergent
conflict/downgrade materialization, keep gated-off plugin providers from
joining sync as connected, and rebind plugin EOS sessions after runtime
replacement.
Related to #2269
* fix(plugins): separate pending remote sidecars from last-known cache
Replay only host-offline remote applies into the DB, keep last-known as
upload fallback, use contribution reset for deleted settings, probe real
plugin host readiness, rebind storage events for plugin providers, clear
plugin bases on reset, and swallow disconnect rejections.
Related to #2269
* fix(plugins): hydrate retained sidecars and gate missing providers
Materialize installed-plugin settings from retained sidecars before
collect pruning, clear sidecars on setting reset, fail closed when
pending remote replay fails, and only restore plugin providers that are
contribution-available. Also initialize dynamic provider counters on
connect.
Related to #2269
* fix(plugins): surface plugin sync providers and preserve conflict sidecars
Collect live sidecars before convergent conflict apply/upload, include
dynamic providers in status and disconnect paths, and list plugin sync
providers in Cloud Sync settings for connect/disconnect.
Related to #2269
* fix(plugins): per-provider sidecar baselines and safer collect/UI
Merge convergent sidecars against each provider's own baseline, keep
retained settings in sidecars without unvalidated plugin_settings writes,
guard isConnectDisabled for never-connected plugins, and fall back plugin
provider labels/icons in the status popup.
Related to #2269
* fix(plugins): correct plugin provider discovery and sidecar reverify
Read nested contribution metadata for sync provider cards, gate empty-config
connects, enforce single-provider disconnect for plugins, and re-verify after
sidecar-only convergent uploads so baselines track the post-write payload.
Related to #2269
* fix(plugins): refresh sync provider availability from live contributions
Replace the contribution-available provider ID set when plugins change so
disabled or uninstalled sync providers leave the auto-sync ready set and
drop to disconnected with config retained.
Related to #2269
* fix(plugins): preserve sidecars in backups, pending queue, and migration
Attach last-known plugin sidecars to protective local vault backups, fail
operationally when pending/last-known storage writes are rejected, carry
sidecars through convergent migration publish, and reject trailing garbage
on encrypted object parse.
Related to #2269
* fix(plugins): abort conflict collect failures and merge migration sidecars
Rethrow operational sidecar collection errors during convergent conflict
resolution, LWW-union all local/provider sidecars during convergent
migration, re-collect last-known after apply, and prefer live host collect
for protective local vault backups.
Related to #2269
* fix(plugins): restore pluginBridge tests, fix import path, union downgrade sidecars
Correct SettingsSyncTab dynamic import path so production builds resolve,
add ipcMain.on to the plugin bridge test double, union all provider
sidecars on convergent downgrade, keep empty last-known resets, and skip
force-delete when contribution reset validation rejects.
Related to #2269
* fix(plugins): abort backup collect failures and honor empty remote wipes
Only fall back to last-known sidecars when the host is unavailable during
protective backups, treat explicit empty remote sidecar bundles as
authoritative wipes, remove lint-blocking anys from harness tests, and
use a stable equal-timestamp sidecar tie-break.
Related to #2269
* fix(plugins): include sidecars in equality, preserve merge base on reconnect
Compare pluginSidecars in cloudSyncPayloadsEqual for migration freshness,
clear plugin merge state only when account/resource changes on reconnect,
cancel accepted sync read streams on failure, and stop re-connecting the
plugin EOS adapter on every I/O.
Related to #2269
* fix(plugins): drop cached adapters on contribution refresh
Invalidate plugin sync adapters when live contributions change so a
restarted runtime gets a fresh connect, take offline any non-disconnected
missing providers, and latch version-change backups only after cancellation
checks pass.
Related to #2269
* fix(plugins): preserve omitted sidecars and hydrate on plugin enable
Treat missing pluginSidecars as legacy/unsupported in three-way merge
(only explicit empty is a wipe), hydrate retained settings when a plugin
enables, and re-enable retained provider configs when contributions return.
Related to #2269
* fix(plugins): keep newer retained sidecars over older stored settings
When collection merges existing sidecars with plugin_settings rows, prefer
the newer updatedAt so a schema-rejected remote value retained only in
the sidecar table is not overwritten by a stale local setting.
Related to #2269
* fix(plugins): seal plugin provider config and clear base on config change
Encrypt opaque plugin provider configuration before persistence, clear
merge state when reconnect configuration changes, and stream sync objects
below a base64-safe inline threshold so control-plane JSON stays in budget.
Related to #2269
* fix(plugins): seal plugin configs by provider id and hydrate before start
Encrypt plugin provider configuration only for non-builtin provider IDs
so field-name collisions cannot skip sealing, hydrate retained sidecars
before plugin enable starts the runtime, and stop tearing down live
adapters on no-op availability refreshes.
Related to #2269
* fix(plugins): seal scalar configs, safe inline reads, stable config fingerprint
Encrypt any non-builtin plugin configuration JSON shape, enforce the
base64-safe inline read cutoff, and compare reconnect configurations with
order-independent fingerprints so merge bases are not cleared spuriously.
Related to #2269
* fix(plugins): thread sync credentials and harden availability checks
Pass SyncConnectPayload.credential through the object-storage and IPC host
path, treat only null/undefined config as missing, and avoid wiping the
plugin provider availability catalog on transient discovery failures.
Related to #2269
* fix(plugins): unblock schema-required sync connect and scalar configs
Add a JSON config dialog for plugin sync providers that declare a
required configurationSchema, reuse retained configs on reconnect, and
treat falsy scalar configs as present in readiness and adapter lookup.
Related to #2269
* fix(plugins): rebind sync adapters on contribution refresh
Drop cached plugin sync adapters when live contributions still include
the provider so replaced runtimes get a fresh connect, and seal falsy
scalar plugin configs with nullish presence checks end-to-end.
Related to #2269
* fix(plugins): apply sidecars under current declared setting scopes
When a plugin update changes a syncable setting scope, write under the
live declaration and drop the obsolete scope row so collection does not
republish duplicates.
Related to #2269
* fix(plugins): seal config envelopes and stabilize provider counters
Use an unambiguous host-owned sealed-config envelope, initialize
dynamic-provider sequence counters before cross-window decrypt, keep
three-way sidecar deletions during convergent migration, and create the
sidecar table for existing schema-1 plugin databases.
Related to #2269
* fix(plugins): avoid mid-sync adapter drops and sidecar resurrection
Keep plugin adapters across setting-only contribution refreshes and
rebind sessions on ensureConnected; stop preserving local-only missing
plugin sidecars when remote already mentions the plugin; compare configs
with nullish semantics across windows; swallow quick-connect failures
after toasting.
Related to #2269
* fix(plugins): restore remoteKeys for installed sidecar resets
Reintroduce the remote key set used when dropping installed-plugin
settings the remote no longer carries so applyFromSync cannot throw
after replacing the sidecar table.
Related to #2269
* fix(plugins): honor conflict policy and re-key scoped sidecars
Pass preferCloud/preferLocal into three-way sidecar merge, use
per-provider baselines on convergent downgrade so local resets are not
resurrected, and re-key applied settings under the current declared
scope coordinates.
Related to #2269
* fix(plugins): keep strategy on final sidecar merge and block dual legacy
Pass preferCloud/preferLocal through the final verified sidecar merge,
avoid auto-reactivating a retained plugin while another legacy provider
is already connected, cache plugin adapters only after initializeSync,
and preserve explicit null configuration values.
Related to #2269
* fix(plugins): strip plugin avatars and protect last-known sidecars
Do not load plugin-supplied avatar URLs in the main renderer, and avoid
overwriting last-known sidecars with an unmerged remote bundle when the
post-apply collect fails or is non-authoritative.
Related to #2269
* fix(plugins): strip top-bar plugin avatars and stop always-rebind
Hide plugin avatar URLs in the sync status popover, drop always-on
rebindSession for plugin adapters, and read convergent mode from the
local config module when deciding whether to auto-reactivate providers.
Related to #2269
* fix(plugins): use window.netcatty for sync IPC and allow null configs
Read plugin sync and sidecar APIs from the production preload global,
and treat an explicit config property (including JSON null) as present
for readiness and adapter lookup.
Related to #2269
* fix(plugins): close residual Codex P2 sync edge cases
Three-way merge sidecars during v2 convergent migration so local
resets are not resurrected, preserve explicit null plugin configs on
adapter rebuild, rebind EOS sessions after I/O failure for replaced
runtimes, and accept the public inlineObjectBytes limit on readObject.
Related to #2269
* fix(plugins): keep inline read cutoff under control-plane budget
Restore the base64-safe inline cutoff so advertised-size reads cannot
overflow the 128 KiB provider JSON envelope; providers near the public
inlineObjectBytes limit must stream.
Related to #2269
* fix(plugins): harden sidecar empty-vault, hydrate LWW, and credential seals
Close remaining Codex/review gaps for PR8: keep empty sidecar shells from
bypassing upload guards, skip startup hydrate when local settings are newer,
seal/validate durable plugin credential refs, and cover schema 1→2 migration.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): rebind plugin sync sessions and keep null configs
Address Codex P2 on 8ae52c54: re-issue connect after runtime replacement,
and treat stored config:null as present in the dashboard reconnect path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): defer empty sidecar last-known until sync succeeds
Keep prior non-empty last-known across an authoritative empty collect so the
empty-vault guard can still recognize a plugin-only reset, then commit the
empty cache after a successful upload.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): avoid restricted localStorage global in sidecar test
Bind the mock to a local binding so eslint no-restricted-globals stays clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): commit applied sidecar cache and reset orphan settings
Prefer merged payload sidecars when updating last-known after sync, and clear
syncable plugin_settings that lack sidecar rows on authoritative remote deletes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): reseal marker-shaped plugin configs instead of skipping
Exact __netcatty_plugin_config_v1 collisions no longer bypass encryption; trusted
host envelopes are unwrapped then resealed, and false collisions are sealed whole.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): keep merged sidecars in last-known after apply collect fails
Return applyFromSync entries from IPC and use them when follow-up collect fails,
so preserved missing-plugin rows are not dropped from the offline upload cache.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): delete stale-scope settings at stored coordinates on reset
After resetSetting against the current declared scope, also remove the row at
the sidecar's stored scope so scope migrations cannot republish remote deletes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): close sync-provider credential, stream, and sidecar gaps
Make SecretRef usable end-to-end via lease-bound network.request, keep
renderer/main transfers abortable and bounded, and stop sidecar
last-known/pending from corrupting backups and conflict apply.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): harden single-provider, secret schema, and conditional conflicts
Keep legacy restart from dual-connecting providers, extract writeOnly schema
secrets into OS storage, and surface conditional-write precondition failures
as conflict UI instead of a generic sync error.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): dedupe sync type imports for lint
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address Codex review on PR #2713
* fix(plugins): wait for sidecar host before version backup latch
Defer the one-shot upgrade snapshot until the plugin sidecar host is ready (or a short grace), and catch getDataHash failures so pending/baseline paths do not reject unhandled.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): require sidecar grace before version backup
Always wait for contributions/grace tick before the upgrade snapshot, and only latch after a non-cancelled attempt so cleanup cannot suppress retries.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Add an opt-in "Show menu over fullscreen apps" terminal setting
(showContextMenuOverFullscreenApps, default off). When enabled, the
right-click context menu opens even while a fullscreen app (tmux, vim,
less) holds mouse tracking, instead of the click being passed through to
the app.
Threaded via a new forceMenuInAlternateScreen flag through the
shouldSuppressMouseTrackingContextMenu predicate (single source of truth)
and its callers. Default off keeps current behavior unchanged; Shift+
Right-Click still works as the escape hatch when off.
- domain/models/terminal.ts: new field + default false
- application/syncPayload.ts: include key in synced terminal settings
- TerminalContextMenu.tsx: forceMenuInAlternateScreen through predicates + prop
- TerminalView.tsx: wire setting to prop
- TerminalBehaviorSettings.tsx: toggle row
- i18n en/ru/zh-CN/zh-TW: label + description
- TerminalContextMenu.test.ts: coverage for the opt-out
Load @xterm/addon-image in the shared xterm runtime so remote programs can
draw real raster images (kitten icat, chafa --format kitty, image previews
in file managers and TUIs) instead of ANSI approximations.
- domain/terminalInlineImages.ts owns the limits and turns TerminalSettings
into addon options; disabled settings resolve to null so the addon is not
loaded at all and the terminal keeps advertising no graphics capability.
- The addon is loaded right after term.open, before the WebGL renderer, so it
patches IRenderService.setRenderer first and its canvas layers survive every
DOM <-> WebGL swap (deferred WebGL, soft-hide suspend, context-loss rebuild).
It is disposed before term.dispose so bitmaps never outlive their terminal.
- Hibernate restores a tab from a text-only snapshot, which cannot carry image
data. XTermRuntime.hasInlineImages reads the live image cache, and Terminal
degrades such a session to soft-hide: hibernateRuntime takes the soft-hide
branch, canFinishHibernate re-checks after every async step, and an eviction
request leaves the session soft-hidden. The block lifts by itself once the
cache is emptied by a terminal reset or FIFO eviction.
- Settings > Terminal > Inline Images exposes the master switch, per-protocol
switches (Kitty / SIXEL / iTerm IIP) and the memory limits, translated for
en, zh-CN, zh-TW and ru. Protocol switches cloud-sync; the memory limits stay
local like the other per-device renderer tuning.
Co-authored-by: Wine Fox <fox@ling.plus>
Add a Settings > Appearance vault option that defaults to single-click
connect, but can switch to select-then-click-again to connect. Grid mode
shows an accent ring on focus; list/tree reuse the hover fill. Groups
follow the same pattern when the option is enabled.
Expose Settings > Terminal > Normalize terminal text on copy (default on).
When off, all copy paths use raw xterm getSelection(); when on, strip
display padding and join soft wraps as before.