A 300ms initial delay let ChatMessageList start the Streamdown import
while the user was still opening the panel to type. Restore the 4s
expand grace and drop a pending Shiki plugin setState if the message
unmounts before the idle callback lands.
Blocking hydrate on focus left history as raw markdown for as long
as the input was focused. Shiki still waits for a real idle gap.
Prose, lists, and inline code can mount after typing stops even if
the textarea still has focus.
messageResponse pulled @streamdown/code on first markdown import, so
opening a chat with history paid for the full highlighter even when
the reply was just prose.
Render with CJK only first. When a fence exists and the composer is
idle, lazily import the isolated Shiki plugin.
Typing a few characters then pausing still hitch because markdown
hydrate only checked the 800ms activity window. History rows would
mount Streamdown while the textarea still had focus.
Treat focused/composing as busy, wait 2s after the last key, and
keep idle IPC on the same predicate.
The first glyph still re-rendered the whole 1500-line ChatInput because
hasComposerText lived in the same component as the model picker. Token
usage snapshots also broke memo with a new object each time.
Move send-button emptiness onto a tiny external store so only that
control updates, and compare contextUsage by value in ChatInput's memo.
Clearing the box immediately after onSend overwrote the pending draft
while handleSend was still awaiting provider sync. A rejected sync or
stale-scope abort then left the prompt gone even though no user
message was created.
Leave the text in place until the parent clears the draft after the
send is accepted. Unmount commit still uses a ref so setter identity
changes cannot write the prompt back.
Typing still hitched because each IME commit started a 120ms
updateDraft, which re-ran the whole useAIState hook. Parent also
passed a new hosts array and providerSwitcher object on every
render, so ChatInput's memo never held. field-sizing-content
relayouted the textarea on every glyph.
Keep typed text in pending refs until send/hide/unmount. Memoize
mention hosts and the provider switcher. Use fixed field sizing
in the composer.
Send awaits provider sync before dropping the draft. During that
wait, ChatInput's onChange cleanup could fire on a new setter
identity and write the still-visible text back into the pending
draft, so the uncontrolled box kept the last prompt.
Clear the field right after onSend starts, and only commit on true
unmount via a ref so callback identity changes cannot restore it.
Codex P1: switching agent on a pristine composer called ensureDraft
then updateDraft. Missing-to-empty was treated as text-only, so the
store skipped notify and the panel kept the previous agent.
Keep first typed flush silent. Empty draft creation, including agent
switch, is a lifecycle change. Sibling empty drafts still do not
wake other panels.
New Chat wrote an empty composer-only draft, which the text-only
equality treated as identical to the previous typed draft. The panel
skipped re-render, so the uncontrolled textarea kept the old string
and send could still submit it.
Treat emptying draft text as a lifecycle change so listeners wake, and
commit the live box on ChatInput unmount so mid-IME hide still flushes.
Every keystroke was re-rendering ChatInput and the first character
created a store draft that broadcast AI state, so the first 1-2 glyphs
painted and the rest dumped after a stall. IME composition hit the
parent on every pinyin letter, and Chromium spellcheck loaded on the
first word.
Keep the textarea uncontrolled, commit parent state on idle/blur/send,
and treat missing-to-composer-only drafts as text-only so store
listeners do not wake. Draft flush, send, and hide still persist the
current box text.
The throughput test failed on CI because quiet catch-up sliced the
viewport into 32-line chunks. A 40-row terminal therefore called
refresh twice while catching up, which is not an atomic paint.
Recolor offscreen first, then refresh the visible range once after
catch-up finishes. Count post-settle compositor frames separately so
DOM's extra layout paint is not treated as a second catch-up render.
If the terminal or workspace goes away during provider/model preflight,
the awaited send must not create a session on a pruned scope. Bump a
send epoch on unmount or scope change and abort after the awaits.
Codex P2: Agent selector, New Chat, and history stayed clickable while
provider/model IPC ran. Switching agent or starting a new chat then let
the stale send finish and wipe the replacement draft. Disable those
header transitions until preflight ends, and abort if the view still
changed.
Codex P1s: isSending must end after sync so Codex App Server steering
works, and permission mode is frozen via lockTurnConfiguration during
preflight then read from a ref at launch. The first SDK send now uses
the catalog model from this same turn, not a stale curated fallback.
Codex: users could still attach files or change skills while provider
sync was in flight, and the first SDK turn could send a curated fallback
model. Disable the composer for the whole send, and load the runtime
model catalog before starting the turn.
Codex P1s: do not wipe text typed while provider sync is in flight, and
push web-search credentials before the first turn. Also resolve custom
Codex config on show instead of after the idle grace window.
Codex P2s: keep a stable pending draft identity so steer success can
clear the composer; take the send gate before awaiting provider sync;
and hydrate Streamdown after recent typing instead of waiting for blur.
Codex P1: first-launch send could race a 4s idle delay on
aiSyncProviders while main still had an empty provider list. Sync on
panel show and await it in handleSend.
Codex P2: slash-picker /stop and /compact called parent onChange('')
without updating local composerText, so the command stayed in the
textarea. Route those clears through commitComposerText.
Codex P2: focusing or typing cancelled the only scheduled callback, so
provider sync and discovery never ran if the user submitted without
blurring. Re-arm after activity and treat IPC as busy only while the
composer was recently used, not for the entire focused lifetime.
Review found three real gaps: send/new chat left pending composer text in
place, hiding the panel could leave body-portaled menus open, and idle IPC
could still start on the first frame after expand.
Discard pending text when the draft is cleared, close ChatInput and agent
menus when the panel is parked, and give composer-idle IPC the same expand
grace as Streamdown warmup.
Opening the AI panel and typing a few characters hit a main-thread stall.
Streamdown warmup, panel remount, draft-store fan-out, and expand-time
IPC all raced the first keystrokes. import() cannot be cancelled, so the
heavy renderer must not start while the composer is busy.
Keep the composer mounted on hidden retained panels, ignore composer-text
store notifications, debounce draft flushes, and defer discovery/skills/
model IPC until the composer is idle. History stays plaintext until
Streamdown is warm, then hydrates a few rows at a time.
Opening the lazy AI panel can 504 a pre-bundle. The serve plugin used
to send a full-reload, which flashed the splash and wiped renderer
state. Warn instead and pre-optimize the AI/streamdown deps.
ipcRenderer.invoke strips ssh2's status code. A server that says
"File not found" instead of "No such file" still uses code 2, so
classify absence in the main-process handler and return null.
Substring search could treat a permission error as missing if the
path contained "enoent". Unwrap the Electron invoke prefix and match
the known ssh2/Node absence kind at the start of that message.
After #2955, upload conflict checks lstat the destination first.
New files throw ssh2's "No such file"; Electron invoke strips `code`
and wraps the message, so isMissingStatError treated absence as a
hard error and every new upload toasted.
Crash-log and other system notes sat flush against the card when they
were wrapped with the card in SettingsAnchor. Use a shared SettingHint
with a top gap for every system-page footer note.
Add a clipped sheen across the active connection segments so the bar
reads as live work instead of a static slab. Skip it on error and
honor reduced motion.
The connecting dialog bar stretched, shrank, then stretched again
because the initial value was 15, the timeout effect snapped back to 5
whenever hop or phase changed, and hop updates could overwrite a higher
value. Seed once when status becomes connecting and only move forward.
The close-time animation frame survived effect cleanup. Reopening and
searching before it ran could consume the new marker and wipe the new
query. Cancel the frame and ignore it if search is open again.
Clearing the query resets highlights while the bar stays open. Closing
afterwards still needs a leftover sweep, but must not treat that stale
search as a reason to wipe a newer manual selection.
Related to #2980
SearchAddon can leak incremental-match decorations, and WebGL bakes
those yellow backgrounds into the glyph atlas. After Esc the handles
are gone but stained cells remain. Track search decorations on
registerDecoration, paint fill on the HTML overlay only, and resweep
after the search-bar close refit.
Version-only saveSyncConfig no longer check-then-writes SYNC_PREFERENCES.
That upgrade-time race could overwrite a concurrent Settings toggle with
stale autoSync=true. Legacy preference fields stay in SYNC_CONFIG until a
preference writer splits them out.
Key-chord fan-out now delivers remapped LF through handleTerminalInputData
on each peer. handlingKittyBroadcast already blocks re-broadcast, but that
made canBroadcastInput false and armed password assistance on the target.
Keep the re-fan-out guard and only arm autofill for locally originated
submits.
skipBroadcast only stops the raw-string fan-out so peers can re-resolve
the chord from their own buffer. Sudo autofill still has to treat that
key as a broadcast, or a main-buffer sudo/su submit arms password
assistance on the source session.
Pause implement, issue follow-up coding, and the Codex review loop
so Cursor only classifies new issues. Restore with
CURSOR_AUTOMATION_MODE=full and the commented schedule crons.
SearchAddon can re-select the prior active match ~200ms after a write
or resize. The revival guard now clears that selection unless the user
started a new one during the window.
#2962
AI terminal_execute used to append the wrapper to whatever the user
had already typed. Prefix interactive PTY writes with a short
line-clear sequence so the injected command starts on a clean prompt.
Raw and serial sessions are left unchanged.
* fix(terminal): avoid keyword highlight flicker
* fix: address Codex review on PR #2951
* fix: address Codex review on PR #2951
* perf(terminal): defer highlight catch-up under load
* perf(terminal): bound deferred highlight work
* fix(terminal): bound highlight catch-up backlog
* fix(terminal): mirror history wipes in highlighter
* fix(ci): load throughput baseline from PR base
* fix(terminal): preserve highlight rebuild ordering
* fix(terminal): drain highlights before exit capture
* fix(terminal): preserve line state across clear
* perf(terminal): slice history serialization
* fix(terminal): preserve parser state on recolor
* fix(terminal): preserve protocol state on recolor
* fix(terminal): retain live state during recolor
* fix(terminal): close recolor state gaps
* fix(terminal): retain saved colors across recolor
* fix(terminal): serialize recolor side effects
* fix(terminal): color keywords in place without history rebuild
The previous approach rewrote the stream and replayed a second
terminal, which forced private xterm state snapshots and still
risked flicker-adjacent reset work. Writes now stay pristine and
only dirty cells get a new foreground. Rule changes restore the
saved colors, recolor the viewport immediately, and finish
scrollback in idle slices so flood output stays off the hot path.
Related to #2784
Related to #2879
* fix(terminal): keep in-place keyword coloring correct under load
Enter no longer restyles the previous row, and write/catch-up
positions now follow xterm markers so saturated scrollback cannot
skip a just-written match. Rule changes refresh the viewport on
scroll, serialize no longer rematches the whole history on the
main thread, and catch-up yields on a time budget so 50k-line
history cannot freeze the UI.
Related to #2784
Related to #2879
* perf(terminal): keep flood and scroll off the highlight hot path
Pinned log tails were rematching the whole viewport on every
output-driven scroll, and every write allocated a marker. Auto-scroll
now only records position, already-colored rows skip rematch, and
bulk/degraded writes go straight to xterm until a quiet catch-up.
Related to #2784
Related to #2879
* perf(terminal): merge flood writes on a 16ms burst window
Electerm keeps log tails smooth by coalescing attach writes for one
frame after a flush. Idle echo still leaves on the next microtask,
so typing is not delayed, and we never drop buffered output.
Related to #2784
Related to #2879
* fix(terminal): keep keyword originals across append, clear, and alt screen
Same-line appends no longer throw away saved colors just because the
line fingerprint changed. Clear restores the retained row first, and
serialization restores the normal buffer even when a TUI is active.
Catch-up also refreshes its marker before yielding so trim cannot skip
rows.
Related to #2784
Related to #2879
* fix(ci): stop resetting highlight catch-up on every flood write
The throughput gate was comparing cell-color wrapping to raw xterm
and failing at ~18% on DOM CI. Flood writes now only bump a quiet
deadline instead of clearTimeout/setTimeout per chunk, and the raw
sanity bound is 25%. The product check remains 10% versus main.
Related to #2784
Related to #2879
* fix(terminal): rematch recycled rows and cap wrapped catch-up
Quiet catch-up now treats a recycled BufferLine as stale when its
saved highlight colors are gone, so repeated keywords on a full
scrollback still color. Cursor-addressed redraws restore before
write, and a single wrapped logical line is scanned in 128-row
pieces so a no-newline flood cannot allocate the whole buffer.
Related to #2784
Related to #2879
* fix(terminal): accept plugin #RRGGBBAA highlight colors
Plugin decorations allow 8-digit hex. Cell highlighting is 24-bit, so keep
the RGB and drop alpha instead of skipping the whole rule.
* fix(terminal): color cells for partial grapheme matches
A match that ends inside a combining cell maps both endpoints to the same
xterm column. Color that cell instead of building an empty range.
* fix(terminal): rematch flood rows after empty-line stamps
A hot-path \r\n write stamped the following empty cursor row as current.
The next flood chunk reused that BufferLine with ERROR text, and quiet
catch-up skipped it. Fingerprint the stamped content so filled or
recycled identical rows are recolored.
* perf(terminal): avoid flood-path registerMarker after trim
Once a catch-up range exists, later bypassed writes only keep the earliest
bound. A disposed marker collapses to 0 without allocating a new one on
every saturated-scrollback chunk.
* fix(terminal): recolor the row retained by clear
xterm clear keeps the cursor row after we restore originals. Recolor that
surviving line so enabled rules still apply until the next write.
* perf(terminal): stamp no-match rows without cell snapshots
Catch-up only needs a generation and fingerprint when a line has no
keyword. Allocate typed-array originals when a cell is actually colored.
* perf(terminal): treat pinned-bottom recycle as output scroll
Once scrollback is full, baseY and viewportY stay put while rows recycle.
That was treated as a user scroll, so every flood write rematched the
visible area. Keep pinned-to-bottom updates off the rematch path.
* perf(terminal): keep quiet catch-up to a single viewport paint
Catch-up already refreshes visible slices. A trailing recolorVisible()
caused a second settle paint and tripped the atomic-repaint gate.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Multi-terabyte pools rendered as raw gigabytes (118736.4G), which is hard
to read at a glance. Scale each side of the used/total pair independently
so a mostly-empty pool keeps its used figure (136.37G/6.93T instead of
0.13/6.93T), and emit a single shared suffix when both land in the same
unit so ordinary disks keep the familiar 71.12/878.15G shape.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex P2: unconditional normalize broke custom proxy prefixes such as
https://proxy.example/anthropic that already complete the SDK base.
Restrict /v1 inference to scheme+host origins (Claude Code style) and
leave other path prefixes unchanged.
Reject sudo+forced-SCP before channel open, and fall back to the standard
SFTP subsystem on exit 127 so reused SSH transports do not re-prompt MFA
on hosts without a standalone sftp-server (e.g. ESXi).
Accept realpath('/') as home only after a successful readdir so virtual/chroot
roots from #2934 still work, while non-listable root no longer skips
/home/<user> and /root probing. Also retry those candidates if listing fails.
The storage event listener uses setShowTabNumberBadgesState but
omitted it from the useEffect dependency array, which triggered
react-hooks/exhaustive-deps.
Pointermove used to setTreeWidth every pixel and re-render NotesManager
including MDXEditor. Apply live width via rAF + aside.style, commit React
state and storage only on pointerup.
Radix ScrollArea's display:table viewport child blocked flex shrink, so
narrowing the notes tree clipped long titles mid-glyph. Constrain the
tree width chain and ellipsize labels so content adapts with the panel.
container-type on .netcatty-mdx-editor makes MDX adjust fixed-anchor coords
as if a containing block exists, but browsers do not. Add a zero transform so
the fixed CB is real and the preview no longer shifts into the notes tree.
Merge seed only wrote activeSessionIdMap, so unsent draft demoted the panel
to draft mode and send created a new session while the old chat still showed.
Prefer persisted workspace active over hasDraft, and materialize panelView.
* fix(#2872): automated Cursor CLI fix
* fix: address Codex review on PR #2875
* fix: address Codex review on PR #2875
* fix(session-logs): sample alternate-screen state after save dialog
Manual session log start previously snapshotted alternate-screen mode
and the initial line before showSaveDialog resolved. Split path selection
from stream start so the renderer re-samples buffer state at activation,
avoiding stale omission/leak if the user enters or leaves a TUI while the
dialog is open.
* fix(session-logs): honor C1 CSI alternate-screen modes in text renderer
8-bit CSI (U+009B) is a valid alternate form of ESC[. Recognize it in
TerminalTextRenderer so DECSET 47/1047/1049 enter/leave still omits TUI
paint from txt/HTML session logs when remotes use the C1 encoding.
* fix(session-logs): apply log-safe RIS reset in text renderer
RIS now leaves alternate-screen mode, resets SGR to defaults, and starts
post-reset shell text on a new log line when needed so mid-line TUI
exits do not glue onto prior output or keep pre-entry colors. History is
preserved because session logs are not live screen wipes.
* fix(session-logs): token-bind dialog path and rebase after RIS
- chooseManualSessionLogPath stores the selected path in main and returns
an opaque selectionToken; startManualSessionLog redeems that token and
rejects renderer-supplied filePath so the save dialog cannot be bypassed.
- RIS rebases screenBaseRow onto a fresh log screen (history preserved)
so post-reset CSI H cannot overwrite pre-RIS text.
* fix(session-logs): seed replay sanitizer on observe attach mid-TUI
When an observe popup restores a snapshot while vim/less is already on
the alternate buffer, re-seed the connection-log sanitizer from the
restored xterm active-buffer type so subsequent TUI redraws are omitted
until leave.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(notes): domain paste policy + Codex review follow-ups
Move clipboard paste resolution to domain/notes (pure, no React) and
address post-#2868 review items: structured text/plain wins over
wrapper HTML, caret-safe insert for long pastes (no EOF append),
https/protocol-relative image src, entity decode, quote-aware img
attrs, code-region protection, nested HTML islands, hard-break
preservation, height-only image CSS, and complete small-icon widths.
* fix: address Codex review on PR #2869
* perf(notes): cut hover re-renders and heavy image :has() CSS
Reduce perceived lag in large notes by (1) coalescing link-hover
setState with equality checks and rAF, (2) replacing the combinatorial
:has(img[width]) matrix with data-note-img-size markers plus lazy
images, and (3) merging DOM decoration into one observer that debounces
in edit mode so Lexical keystrokes do not re-walk anchors every frame.
* perf(notes): render preview with Streamdown instead of MDXEditor
Vault note preview no longer mounts Lexical/MDXEditor/CodeMirror for
read-only viewing. Preview uses static Streamdown (same stack as chat)
with HTML islands for centered heroes and sized images; edit mode keeps
MDXEditor. This cuts large-note scroll and mode-switch cost while
preserving host-link click handling and compact badge image layout.
* fix(notes): render markdown inside center HTML for Streamdown preview
Streamdown/remark does not parse Markdown nested in HTML blocks, so
README-style <div align="center"> heroes showed raw # ** and badge
syntax. Preprocess preview markdown: expand centered islands via marked
GFM to HTML, and rewrite relative images so Streamdown no longer shows
"Image blocked" chips under app CSP.
* fix(notes): GitHub-style preview with react-markdown
Replace Streamdown note preview (chat-oriented Image blocked chrome and
poor README layout) with react-markdown + remark-gfm + rehype-raw/sanitize
and github-markdown-css. Center HTML islands still expand via marked so
pasted heroes render; relative images and empty links are dropped quietly
instead of alt-as-title or blocked chips.
* fix(notes): keep relative images; map Vite public/ to site root
Stop dropping non-https note images. Preserve absolute and relative src
as-is, except rewrite repo public/ assets to site-root paths
(public/icon.png → /icon.png) so Vite/Electron serve them without
/public/* warnings. Unwrap oversized center blocks so Features-style
body stays left-aligned in preview like edit mode.
* fix(notes): left-align preview body; center only README hero
Force markdown-body text-align left and direct block headings/lists left.
After the first hero thematic break, strip align=center / text-align:center
so Catty/Features sections no longer inherit full-width centering. Tighten
oversized-center heuristics (Catty Agent, lists, blockquotes) without
treating the Netcatty logo title as a body section.
* fix(notes): force preview body left-align (nuclear CSS + unwrap)
Catty/Features still rendered centered because small align=center shells
and inheritance beat partial CSS. Force every node left via * selector,
re-enable center only under [align=center], unwrap all center shells after
the hero --- band, and set inline textAlign left on the preview root.
* fix(notes): stop /public asset requests; fix preview center inheritance
Root cause of body still centering: CSS re-applied text-align:center to
all descendants of [align=center], so one large shell centered Catty/lists.
Keep * left; only the shell itself may center (inline badges).
Root cause of Vite /public/* warnings: stored notes still had /public/...
paths and MDX loaded them raw. normalizeNotePublicAssetPaths rewrites
/public/ and public/ to site-root paths for both edit and preview display.
* revert(notes): restore MDXEditor for preview (drop GitHub markdown path)
GitHub-style react-markdown preview caused layout and asset-path issues
that diverged from edit mode. Preview again uses MDXEditor with
readOnly + mode key remount, matching the pre-Streamdown/GitHub path.
Keep display-time public/ → / rewrite so Vite does not request /public/*.
* perf(notes): reuse MDX editor when switching notes
Remove key={noteId} remount that tore down Lexical/CodeMirror on every
note change. Pass noteId and call setMarkdown on switch instead, after
the existing draft flush, so selection stays snappy and only document
content is replaced.
* perf(notes): defer setMarkdown on switch so selection paints first
Yield a double rAF before Lexical import on note change, show a
blocking overlay for large notes, and ignore external sync while a
swap is in flight. Hardens flaky Suspense loading assertions in SSR tests.
* fix(notes): align task-list checkboxes and make them clickable in preview
Center GFM checkboxes on the first text line, give them a solid hit target,
and toggle markdown from preview clicks since Lexical readOnly blocks
CheckListPlugin. Pure toggle helper lives in domain/notes/taskList.
* fix(notes): close multi-agent review findings on switch/paste/tasks
Gate stale onChange during deferred note-switch import, keep draft refs
in display-markdown space, baseline paste settle on live getMarkdown to
avoid false success after muted setMarkdown, mask code regions in task
toggles, and drop abandoned GitHub/Streamdown preview module and deps.
* fix(notes): address remaining Codex review findings
Clear Lexical undo history on note switch, always inert-block during
deferred setMarkdown, re-annotate host links and image sizes on attribute
mutations, harden entity/code-region paste paths, and angle-bracket image
destinations that contain spaces.
* fix(notes): tighten Codex follow-ups on src, code masks, tasks
Prefer real img src over data-src, normalize public/ paths only outside
code, mask indented/blockquote fences, and accept 1) ordered task markers.
* fix(notes): keep swap payloads fresh; keep nested list tasks
Refresh deferred setMarkdown when the same note's value changes mid-yield,
and stop treating nested list lines as indented code so task toggles stay
aligned with the preview DOM.
* test(notes): update swap guard source assertions
* fix(notes): collision-free code masks, real href, cancel hover rAF
Use a document-unique mask sentinel, match anchor href with attribute
boundaries (not data-href), and cancel the pending link-hover frame on
mouse leave so chips cannot reappear after exit.
* fix(notes): accept longer closing fences in code masks
CommonMark allows closing fences with more backticks/tildes than the
opener; line-scan masking now matches length >= open so public/ paths and
task samples inside those blocks stay protected.
* fix(notes): harden CommonMark fence, span, and indented-code masks
Use homogeneous fence openers, multi-backtick inline spans, and list-context
aware indented-code detection so samples stay masked without breaking nested
task indexing.
* fix(notes): task spacing, angled image dests, deep blockquote fences
Require whitespace after task brackets, parse <angled> image destinations
with spaces, and mask fenced code at any blockquote depth.
* fix(notes): raw-text HTML elements and HTML comment task masks
Locate script/style closers without nested-tag walks so raw bodies with
'<' are removed, and mask HTML comments before task indexing so preview
toggles hit visible checkboxes only.
* perf(notes): drop fade animation on note switch and editor load
Use an instant solid cover instead of opacity/transform fade-in so Lexical
swaps do not pay extra composite cost that can feel more janky than a hard cut.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Restyle the ImageDialog overlay, panel, labels, inputs, dimension row,
and Save/Cancel buttons with Netcatty tokens so the form no longer
uses stock MDX light/flat chrome in the dark notes editor.
Linked shields/ko-fi badges were reduced to [Label](url) so icons vanished.
Normalize to single-line [](href) or <a><img></a>, allow relative
README image paths again, and style linked images as inline badge rows.
Wrap README-style align=center / text-align:center blocks in
<div align="center"> so MDX GenericHTML keeps layout. Restore ATX
heading markers inside centered h1–h6, skip re-turndown of center
wrappers, and add text-align CSS for align=center in the note editor.
README distro walls use width=48 SVGs that MDX stacks as blocks. Treat
common small width attrs (≤96) as inline-block on both the img and MDX
image wrapper, hide per-icon chrome until hover, and leave large
screenshots as full-width block images.
Preserve HTML img dimensions in note source (including large screenshots).
Side-panel display still uses max-width:100% and height:auto so content
fits without cropping; small logos can honor their smaller width attrs.
Product policy: notes are not a GitHub renderer. Collapse linked badge
images to text links (no ](url) debris), drop oversized screenshot width
attrs so the side panel scales with CSS instead of cropping, and keep
modest widths only for small assets. Center/align HTML remains out of scope.
Keep width/height by emitting sanitized HTML <img> (MDXEditor's HTML
image visitor understands them); plain  alone drops size.
Cap note images at max-width 100% with auto height so large README
screenshots fit the pane instead of overflowing at native pixel size.
GitHub-style pastes mix markdown with <img>/<a name> HTML. Running Turndown
on the whole string escaped headings and collapsed structure. Convert only
HTML islands (Turndown per fragment), keep markdown intact, and enable
MDXEditor imagePlugin so  from paste actually shows.
Replace the hand-rolled HTML→Markdown converter with Turndown + GFM.
Prefer text/html when present (GitHub/browser pastes) so raw <img>/<a name>
islands in text/plain no longer make MDX insertMarkdown silently no-op.
Browser/Word copies put structure in text/html while text/plain is flat.
Resolve HTML to markdown (headings, lists, links, emphasis, tables, code)
and intercept those pastes so Lexical no longer swallows rich HTML.
Structured plain markdown still wins when the user copies .md source.
Structured long paste was preventDefault'd then handed to insertMarkdown;
when Lexical no-op'd or took longer than the short settle poll, the
clipboard was swallowed with no UI change. Prefer document setMarkdown
merge for large pastes and fall back to the same path if a short
selection insert still no-ops after a size-scaled settle window.
- Use scoped historySessions for draft-force and ownership-clear guards
so out-of-scope sessions demote instead of sticking blank.
- On server-stats pause, keep the last snapshot but clear givenUp so
resume can auto-retry after hard failure.
- Fix ProcessRow undeclared vendorLabel via vendorDisplayLabel; make
GPU marks decorative (aria-hidden); attribute Simple Icons as CC0.
- Sticky icon-only when tab bar width is zero; include workspaces in
TerminalLayer ctx deps.
Prevent StrictMode draft demotion after send, keep workspace AI maintenance
mounted with a workspaces list, and keep live Streamdown markdown while
streaming. System Manager gains overflow-aware icon-only tabs, customize
layout, GPU vendor badges, and cache-preserving overview remounts. Settings
search scrolls only the content pane so traffic lights stay put.
When dissolve prefers a focused pane that did not own the inherited chat,
retarget the session and clear the old terminal selection so ownership
filters do not hide it as another scope's active thread.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mount AI maintenance even when no side-panel tools are open, and clear a departed pane's active-map entry when a live workspace retargets its chat.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep inherited terminal-scoped chats visible in a still-live multi-pane workspace by reminting the active chat onto a surviving pane.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep a single SFTP portal/transfer owner when promoting or dissolving workspace tabs so the visible panel retains live browser state.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the surviving terminal on the dissolved workspace's active session view, and remint chats whose original pane is gone so history matching still resolves them.
Co-authored-by: Cursor <cursoragent@cursor.com>
Sidebar chats are scoped to already-open terminals/workspaces; exposing
host_open let Catty expand scope mid-turn and then fail to drive the new
session. Restrict vault.host.open to global/MCP/CLI and stop prompting
Catty to open hosts itself.
Fixes#2856
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Inherit the focused terminal's AI session when tabs merge, hand it back on dissolve, and remap side-panel open/layout/SFTP state so the panel does not look cleared.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep Home, the leading root, and the ellipsis fixed while only trailing chips scroll toward the current directory, and always show the ellipsis when hidden segments exist under a budget of one.
Co-authored-by: Cursor <cursoragent@cursor.com>
Always retain the first breadcrumb segment when truncating, clamp tiny budgets, and scroll overflow viewports to the trailing edge so narrow panes still show the current directory without RTL right-align.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop zsh ghost text past the cursor when cell fg/dim differs from the
accepted input, so mid-line history edits still record full commands
without absorbing unaccepted cross-token suggestions.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drive-letter truncation kept C: visible, but WSL/UNC roots were dropped because isWindowsDrive is false for those paths. Keep the leading UNC share root the same way.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep inline SFTP actions left-aligned and push only the collapsed ⋮ control to the right of the actions row.
Co-authored-by: Cursor <cursoragent@cursor.com>
The first two-row layout right-aligned short paths via RTL clip and pushed toolbar actions with ml-auto. Keep the address on top, left-aligned, with controls underneath.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only prefer the full painted line for mid-line edits when the keystroke
buffer is already a whole token there, and keep the cursor-visible
command when an unaccepted zsh ghost continues past the cursor.
Co-authored-by: Cursor <cursoragent@cursor.com>
Deep side-panel paths were squeezed beside action buttons and clipped at the end. Split path chrome onto a dedicated row and truncate/overflow toward the current directory.
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-arm disposedRef when the autocomplete mount effect re-runs so Fast Refresh
cleanup no longer permanently kills fetchSuggestions. Allow local history/fig
matches during empty-echo lag while still blocking sensitive prompts, and fall
back pane visibility for standalone terminal mounts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the one-click hostname copy from #2818 without crowding host cards;
expose it next to copy-credentials in list/grid and tree menus instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
Hide search behind an icon toggle, surface create actions as inline icons,
and move bulk delete into a selection bar so the Library toolbar stays compact.
Co-authored-by: Cursor <cursoragent@cursor.com>
Prefer the reliable keystroke buffer under SSH echo lag so local history/fig/snippet matches appear without waiting for remote echo or path listings. Soft-budget remote path completion and keep Enter recording on the stricter alignment path.
* fix(#2826): automated Cursor CLI fix
* fix(scripts): confirm bulk delete and clear host bindings
Scripts side-panel mass-delete skipped confirmation and, when
onSnippetsChange was wired, filtered snippets without clearing host
login/connect script bindings. Confirm first, always dispatch the shared
delete event, and apply deletes via deleteSelectedSnippetsFromVault.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(scripts): harden delete handler against stale vault state
Read snippets/hosts from refs in the shared delete listener so rapid
confirms cannot resurrect already-removed items, and clear pending bulk
delete when the scripts panel hides so the confirm dialog does not
resurface after a mid-prompt hide.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(scripts): keep delete refs current within the same tick
Update snippetsRef/hostsRef immediately after applying a delete so a
same-tick second event cannot rebuild vault state from the pre-delete
snapshot and resurrect removed snippets or host bindings.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: address Codex review on PR #2828
* fix(scripts): hoist bulk-delete confirm outside toolbar popover
Portalled Dialog focus dismissed the compact Scripts popover and cleared
pendingDeleteIds before confirm. Own the confirm in TerminalToolbar and
defer via onBulkDeleteRequest when nested in that popover.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: serialize snippet saves with bulk-delete vault lock
updateSnippets wrote STORAGE_KEY_SNIPPETS without withVaultImportLock, so a
concurrent edit could land between bulk-delete's snapshot read and journal
commit and be discarded. Queue ordinary snippet writes on the shared lock and
include them in waitForPendingVaultWrites.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: rebase queued snippet saves onto latest vault snapshot
Locking updateSnippets alone still wrote a pre-delete full array, so a
popup bulk-delete could be resurrected (or a storage-event version bump
could cancel the local edit). Rebase under the vault lock and supersede
only newer local writers via snippetsWriteOwnerRef.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
* fix: address Codex review on PR #2828
---------
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>
Related to #2561.
Adds detect-first Ports/Services system manager tabs, network-appliance
read-only gating, and hardened collectors/parsers from Bugbot review.
* 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>
* Cursor: Apply local changes for cloud agent
* fix(terminal): stop first SSH fail under StrictMode remount
Defer backend boot past the double-invoke, cancel superseded vault init,
gate start on vault hydration, and harden hotkey/attach cleanup so the
first connect no longer races a stale close or empty keychain.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf: finish StrictMode mega-subscriber plan (accent isolation + docs)
Stabilize published terminal themes across custom-accent drag, apply accent
at chrome/log leaves and CSS injection only, land Phase 0 design/plan docs,
and lock Host-bag / Bugbot-clean contracts for the isolation checklist.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* docs: add StrictMode mega-subscriber design and plan artifacts
Force-add Phase 0 spec/plan under docs/superpowers (gitignored locally)
so the delivery branch carries the approved design and plan copy.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: clear remaining StrictMode doubles and notes editor jank
Gate every AI send and reject concurrent runTurn, latch tray connect flush
and settings persistMounted remounts, and stop notes content drafts from
rebuilding MDXEditor/shell on each keystroke.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf: gate hidden notes panel subscribe and skip host-link annotate on keystrokes
Hidden terminal notes mounts keep a live snapshot read but stop listening to
publishes. Host-link DOM annotation follows hosts/mode (preview MutationObserver)
instead of every markdown value change.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: latch SSH transport idle TTL notify under StrictMode
Avoid duplicate main-process notifySettingsChanged on effect double-invoke
when the idle TTL value has not changed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf: freeze inactive vault notes against hosts churn and prefetch MDXEditor
Gate the retained VaultNotesSection notesStore subscription while hidden,
ignore hosts identity in its memo compare when inactive, and warm the lazy
InlineMarkdownEditor chunk when notes become active.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: clear remaining AI StrictMode double-fire hazards
Latch Ask-AI terminal selection on requestId before draft mutation, hoist
Codex App Server IPC bridge to App (ref-counted), and make Catty
requestApproval join an existing waiter for the same toolCallId.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: remount-safe AI send latch and skip stale MCP approval IPC
Replace the component sendInFlightRef with a module key latch so StrictMode
panel remount cannot reopen a mid-flight send, and no-op resolveApproval when
the pending entry is already drained.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test: allow gated useNotesStore in VaultNotesSection memo contract
The retained notes section now passes enabled:isActive so hidden mounts do
not subscribe; update the VaultView memo source assertion accordingly.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep note drafts across mode toggle and external note publishes
Flush ref-only content before edit/preview remount, and skip prop→editor
setMarkdown while a local draft diverges from the last synced value.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* chore: strip StrictMode sermon comments after Bugbot/Comment Sicko pass
Keep effect-order and public-export contracts; drop remount/perf narration
that the latches and tests already encode.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: remount-safe Ask-AI terminal selection latch
Move consumed requestIds to a module Set so StrictMode remount cannot
append the same selection chip twice before parent state clears.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* chore: drop remount latch comment
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Split vault/session/settings into publishers and store-backed Hosts so hot
fields no longer rebuild the whole shell, and harden Terminal boot / startup
effects for StrictMode double-mounts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(system-manager): parse CANN 24.x Ascend npu-smi info tables
ModelArts / CANN 24.1 tables include Hugepages-Usage and Bus-Id chip rows
that the old regex rejected, so the GPU tab showed zero Ascend devices
even when npu-smi was present. Parse summary+chip pairs like nputop,
always collect the info dump, and fall back to info -m for NPU IDs.
Closes#2811
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(system-manager): harden Ascend parsing from nputop fixtures and polish GPU UI
Borrow real npu-smi samples from youyve/nputop (Apache-2.0) so CI covers
910B2C, 310B4, Atlas A3 multi-chip, and 310P3 process tables without
hardware. Aggregate multi-chip NPUs, parse info-dump processes, and surface
npu-smi driver versions.
GPU tab now keeps a short util/HBM sparkline history and ResourceBar uses
smooth width transitions with load-aware tones, closer to an nvtop feel
inside the existing system-manager chrome.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
downloadToTempWithProgress passed totalBytes:0, which transferBridge
treats as an explicit empty snapshot and skips the remote body, so
external editors opened empty files (fixes#2787).
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): raise autocomplete suggestion default beyond 8
Snippet keyword matches were truncated at autocompleteMaxSuggestions=8
before the floating list, so users with many matching scripts could not
scroll to the rest (fixes#2793).
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): migrate persisted autocompleteMaxSuggestions 8 to 50
Existing installs saved the old default into localStorage with no settings
UI, so raising DEFAULT alone left the popup capped at 8. Treat 8 as the
legacy sentinel while preserving intentional custom caps.
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(terminal): stop keyword highlight flicker on Enter
Idle Enter was misclassified as user scroll so overscan decorations were
pruned before writeParsed cancel (Ubuntu RTT). Also swap cursor-line
decorations atomically to avoid clear-before-create flash (fixes#2784).
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): catch up highlights after Enter protection window
While enterInputPending blocked scroll-prune, viewport scrolls were also
ignored with no dirty mark. Mark dirty during the window and trigger a
write refresh when Enter protection clears.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): persist keyword highlights across scroll (#2784)
Replace Enter scroll-skip band-aid with mam15mon/#2348 persistence:
keep decorations across scroll, sync only newly exposed rows, bound
retention (~20 viewports / max 1200). Keep Enter write-path protections
and cursor-line atomic swap. Continuous Enter may scroll; prune-on-scroll
is gone.
Co-authored-by: mam15mon <mam15mon@users.noreply.github.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): bound persistent keyword prune to scroll path
Keep Enter/write deferred pruning intact by pruning the retention cap
only after scroll viewport indexing, not on every decoration apply.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): rescan dirty overlap on persistent scroll refresh
Overlapping scroll only scanned newly exposed rows then cleared the whole
viewport dirty set, which could drop in-place redraw work when scroll
outranked a pending write refresh. Rescan write-dirtied overlap lines and
only clear dirty for ranges that were actually processed.
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: mam15mon <mam15mon@users.noreply.github.com>
Closing the active tab fell back to last workspace/solo in array order,
so opening a temp tab then closing it jumped away from the prior work
tab (fixes#2788).
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): protect title IME, surface persist failures, harden paste
Note titles rewrote controlled value during CJK composition (Sogou Wubi).
Notes localStorage writes ignored QuotaExceededError so restarts lost
content. Markdown paste intercept could preventDefault with a no-op insert
(fixes#2783).
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): commit title draft on blur for IME composition
IME-only title entry never called onCommit until compositionend; blur
could flush an empty draftTitle and then adopt the stale external value.
Commit the local draft on blur before parent flushNoteDraft.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): stash IME title drafts in refs without controlled rewrite
Composition updates now live-stash into draftTitleRef for teardown and
note-switch flushes, while onCommit still waits for idle IME. Blur syncs
superseded external titles before flush so stale drafts cannot overwrite.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): clear stashed title when IME composition is superseded
compositionEnd now live-stashes the adopted external title so pagehide
and note-switch flushes cannot persist rejected composed text.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): cancel draft flush during IME title stash
Prior idle commits could still debounce-flush mid-composition and rewrite
the controlled title. Stash now clears the timer, and external supersede
in the adopt effect live-stashes the authoritative title.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): dedupe quota failure toasts during autosave
Debounced draft flushes can hit QuotaExceededError repeatedly; rate-limit
the error toast so users are not spammed every 300ms.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Address Bugbot: do not skip every `# [` comment. Only ignore known app
debug tags (transferDiag, FileWatcher, SessionLogStream, ...), while
still failing on unknown bracket diagnostics like `# [fatal]`.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Ignore success-case TAP YAML diagnostics and normalize volatile console
noise in baseline comparison so red-but-unchanged candidate runs pass as
baseline_only. Align the session-backed fastPut expectation with live-path
uploads, harden SCP abort teardown races, drop mixed hallucinated research
citations instead of failing classify, and soften verification failure
copy so it no longer claims the patch introduced the failure.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* refactor: migrate architecture-debt hooks into application/state
Move SFTP Wave A stores/hooks and related vault/AI/terminal/systemManager
hooks out of components into application/state, update importers, and keep
thin deprecated re-exports at the old paths.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* refactor(sftp): align local bookmarks store API with global bookmarks
Export subscribe/get/set/rehydrate helpers from localSftpBookmarks to match
the globalSftpBookmarks external-store pattern.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(electron): reduce baseline memory via spellcheck/GPU/prewarm guards
Disable spellcheck on BrowserWindows and the default session, drop spare
renderers, gate aggressive GPU switches behind NETCATTY_COMPAT_GPU, and make
settings prewarm opt-in via NETCATTY_PREWARM_SETTINGS.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* Isolate notes and customAccent from TerminalLayer hot props
Route vault notes through notesStore (shellHistoryStore pattern) and
app accent through appearanceChromeStore so note edits and accent-drag
ticks no longer rebuild TerminalLayer. Notes/AI slots and Terminal leaves
subscribe locally; VaultView and open-note callbacks stay unchanged.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ui): cut unnecessary AppView/VaultView/settings re-renders
Stabilize TopTabs close handler, memo AppHostTreeLayer, exclude accent from
settingsVersion, ignore connectionLogs off the logs section, share lastConnected
host refs, and keep rename dialog drafts local so chrome domain stays stable.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* chore: package size, dead code, and consolidation cleanup
Remove unused direct deps, exclude Vite-bundled renderer packages from
electron-builder, delete high-confidence dead code/no-op transfer warm
wiring, consolidate cloud provider IDs and KnownHost/SFTP helpers, and
land UI/AGENTS quick wins (rAF text batching, notes draft debounce,
streaming plain text, tempDirBridge, diagnostics adapter, External MCP
setters).
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: stabilize rAF text batching and diagnostics tests
Polyfill requestAnimationFrame for Node, flush pending text before
steer buffering, bypass rAF while steer is in flight, and stub
global localStorage for AI panel diagnostics adapter tests.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: correct import paths after application/state hook migrations
Migrated vault/AI/system hooks still used components/-relative imports;
point them at repo-root domain/components/types/lib instead.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: address Bugbot/Codex regressions from perf cleanup
- Keep blank Enter from closing RenameDraftDialog; only reset rename state after a non-empty submit
- Flush note drafts before vault mutations so pending title/body edits are not overwritten
- Re-apply appearanceChromeStore accent on appearanceTheme in Terminal, and keep layer themes unaccented
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): flush draft before markdown export paths
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep editor-close hotkey ref fresh during render
Assign handleRequestCloseEditorTabRef in render instead of useEffect so
Cmd/Ctrl+W never hits the App stub before the effect runs.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep accent in chrome themes without TerminalLayer thrash
Restore accent in globalAppearance for chrome/injection consumers.
Keep resolveFocusedAppearance identity stable across accent drag via
accent ref, while Terminal leaf still re-applies appearanceChromeStore.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep compose chrome accent live and merge note renames
Subscribe theme-panel focusedAppearance to appearanceChromeStore so
compose-bar chrome updates during color-picker drag even when
resolveFocusedAppearance identity stays stable. Tree renames now merge
title into the post-flush note instead of replacing from a stale row.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: clear terminal accent when switching back to theme mode
Re-resolve appearanceTheme by catalog id before applyCustomAccent so a
stale baked custom cursor/selection cannot stick after accentMode returns
to theme while TerminalLayer memo ignores accent props.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: register unsaved prompt singleton during render
AppView close handlers call promptUnsavedChanges outside the old
render-prop. Assign the singleton during render so Cmd/Ctrl+W and tab
close never hit the pre-effect null window that auto-cancels.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep vault import hooks UI-free and flush notes on teardown
Move ImportOptions into application/state and inject a notifier so
useVaultImportHandlers no longer imports components. Notes drafts also
flush on pagehide/beforeunload when Electron tears down without React
unmount.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: move AI terminal session context builders into domain
buildAITerminalSessionInfo and AIPanelContext now live under domain/ so
application hooks no longer import TerminalLayerSupport and create a
UI → application → UI cycle.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: drop duplicate AIPanelContext type from TerminalLayerSupport
The type is re-exported from domain; the local definition conflicted.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep migrated state hooks free of UI-layer imports
Move aiChatStreamingSupport into infrastructure/ai and poll list helpers
into domain/systemManager so useAIChatStreaming / useSystemManager no
longer import from components. Leave thin re-exports at the old paths.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: move remaining migrated-hook deps out of components
Host-key verification types/helpers now live in domain/, and SFTP column
layout types live in application/state/sftp/columnLayout. Application
hooks no longer import those definitions from the UI layer.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: clear lint blockers and flush notes when panels hide
Move AI panel diagnostics persistence into application/state so
components stop importing localStorageAdapter. Flush note drafts when
retained NotesManager mounts become inactive, and silence related lint
warnings in theme/host-key helpers.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(notes): publish notesStore sync on vault note updates
updateNotes/updateNoteGroups now publishNotesSnapshot immediately so
AI/notes consumers do not briefly read a stale catalog after a hide
flush. Flush inactive drafts in useLayoutEffect before paint.
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): optimize growing-download verification
* fix(sftp): harden growing-download prefix verification
Skip unprivileged SSH digest commands for sudo SFTP sessions and reject
empty-input digests so root-only sources fall through to the elevated
SFTP channel. Hash remote prefixes in concurrency-sized windows so
verification keeps a bounded memory footprint instead of retaining the
full downloaded prefix.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): fall back when remote digest command times out
Treat SSH digest open/run timeouts as a miss for the optional
command-path optimization so growing-download verification can still
prove the prefix via SFTP ranges instead of failing a completed transfer.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): propagate digest open timeouts after transport invalidation
SSH_EXEC_OPEN_TIMEOUT invalidates the physical transport in
boundedSshExec, so falling through to SFTP on the same session is unsafe.
Keep run-timeout fallback to SFTP, but fail closed on open timeout.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): mark digest open timeouts as noTransferFallback
SSH_EXEC_OPEN_TIMEOUT already invalidates the transport. Tag it with
noTransferFallback so downloadFile's isolated→shared catch fails closed
instead of retrying verification on the poisoned session.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): fail closed on verify timeouts; range-hash checkpoint resumes
Mark SFTP verification request/stream timeouts with noTransferFallback so
downloadFile does not retry body transfer on a channel that still owns a
timed-out request. Allow checkpoint-complete growth verification to use
bounded prefix ranges under the session fast-download slot, including
sudo paths that skip unprivileged SSH digests.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): drop wedged SFTP channel after verification timeouts
Clear and end the cached SFTP channel when prefix verification hits an
OPEN/READ/stream watchdog or digest exec-open timeout, so later
browse/transfer work cannot reuse a channel that still owns cancelled
requests.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): use window inactivity watchdog for prefix range hashing
Reset a single per-window READ inactivity timer whenever any range
completes, instead of arming independent deadlines that all expire
together on slow or serialized SFTP servers.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): abandon channel on stalled CLOSE; share abort gate
Drop the cached SFTP channel when verification CLOSE times out or fails,
and fan out concurrent prefix READs through one shared AbortSignal gate
so the bounded window does not attach dozens of listeners.
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): 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(sftp): make fastPut pending OPEN gate wait cancelable
An isolated concurrent OPEN that dies without a callback leaves
pendingWriteOpenPathGate unresolved by design. Unconditionally awaiting
it before fastPut pinned the transfer after abort was cleared; race the
wait with cancel and bound it fail-closed so Cancel/lease cleanup can
settle without racing a still-pending truncate (#2755).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): end fastPut channel on gate wait abort
Clear the gate-wait timeout on cancel so abandoned rejects cannot fire,
and end the reopened isolated channel when the wait fails so Cancel /
fail-closed timeout cannot leak an SSH subsystem.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): poison path gate after fastPut OPEN wait timeout
When a dead write OPEN leaves truncatingSharedWriteOpenGates unresolved,
fail-closed fastPut timeout now marks that entry failed so later same-path
uploads reject promptly instead of hanging on waitForPrior. Keep the
barrier until a late OPEN release clears it; waitForPrior rejection no
longer starts OPEN.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): keep path-gate poison across successor waiters
Reinstall the poisoned truncatingSharedWriteOpenGates entry on fail() so a
same-path waiter that replaced the map slot cannot release the barrier for a
third OPEN. Propagate fail (not resolve) when abandonGateWait sees a poisoned
prior, and poison on cancel during the fastPut gate wait as well as timeout.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): let OPEN owner clear poison after successor fail
Successor waiters that propagate a poisoned prior must not reinstall
themselves into truncatingSharedWriteOpenGates. Otherwise the OPEN owner's
late release cannot delete the barrier and same-path uploads stay
fail-closed forever after the dangerous OPEN has settled.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): clear path-gate poison after transport end
Arm a 2s post-close clear when the owning isolated SFTP channel ends so a
host-keyed poison from a never-callback OPEN does not fail-closed every
later same-path upload until process restart. Keep the grace so a late
OPEN racing end() still has a barrier.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): arm path-gate poison clear without per-OPEN listeners
Install the post-poison transport-gone clear from fail() instead of
attaching end/close listeners on every write OPEN. Shared browse channels
never emit those events per transfer, so the old approach leaked listeners
and could hit MaxListenersExceededWarning.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): only OPEN owners arm path-gate poison cleanup
Track openIssued / priorEntry on truncatingSharedWriteOpenGates so a
waiter that reaches fastPut timeout poisons the OPEN-owning ancestor
instead of reinstalling itself and clearing the barrier while the prior
transport may still deliver a late truncating OPEN.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): do not arm ancestor poison cleanup from waiter fail
When a waiting upload times out and fail() walks to the OPEN-owning
ancestor, keep that ancestor's barrier until its own OPEN settles. Only
the OPEN owner entry arms releaseAfterTransportGone after its transport
is already gone.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(terminal): add disconnect/reconnect controls on session status bar
Workspace panes could not reach tab-level reconnect. Expose disconnect
(without closing the pane) and reconnect beside the host info actions so
split sessions stay operable. Closes#2769.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): abort in-flight boot on status-bar disconnect
Disconnect keeps the pane mounted, so cancel's unmount cleanup never runs.
Flip isBootActiveRef (and isCancelling) so a late startSSH/startMosh attach
cannot resurrect the session after the user disconnects mid-connect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): ignore stale start UI after status-bar disconnect
When disconnect aborts an in-flight connect, starter catch paths must not
reopen auth or repopulate errors. Gate SSH/telnet/mosh/ET/local/serial
failure UI on isTerminalBootActive and harden the boot effect awaits.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): hide disconnect/reconnect on compact popup terminals
Owned command popups use compactToolbar with a one-shot startup command.
Exposing reconnect there can re-run that command; keep the controls on
normal tabs/workspace panes only.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): use boot epoch so reconnect cannot revive aborted starts
Disconnect alone flipping isBootActiveRef is not enough: an immediate
reconnect re-arms boot, and a late startSSH from the aborted attempt can
still attach. Capture a boot epoch per attempt and ignore stale attaches/UI.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): do not let stale attach abort a newer reconnect
tryAttach failure for an expired boot epoch must close the orphan session
only. Calling abortSessionStartAfterUnmount() would force disconnected on
a reconnect that already re-armed boot.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): reject late host-key prompts after disconnect
Disconnect can land before pendingHostKeyRequestId is set. Auto-deny
onHostKeyVerification when boot is inactive or status is disconnected
so approval UI cannot reopen on an aborted pane.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): cancel wake and MFA when status-bar disconnects
Invalidate hibernation reconnect-wake tokens on disconnect so a late wake
cannot re-arm boot. Reject/cancel keyboard-interactive prompts for
disconnected panes and clear any already-queued MFA for that session.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): ignore stale SSH catch UI and password fallback
Check boot attempt currency before resetting reconnect UI state on
SSH start failure, and before launching key→password fallback after
disconnect so a replacement reconnect is not interrupted.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): guard stale SSH progress and host-key prompts
Ignore chain-progress callbacks from superseded boot attempts, and
thread bootEpoch through SSH host-key verification so a late prompt
from an aborted start cannot overwrite a replacement reconnect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): correlate MFA and jump host-keys with boot epoch
Forward bootEpoch through jump-host verification and keyboard-interactive
prompts, and reject superseded MFA when the retained tab has already
started a replacement reconnect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep replacement SSH session alive across stale starts
Skip closeSession for superseded starts while a newer boot still owns
the shared sessionId, and avoid clearing MFA wait state before the
attempt-freshness guard on SSH success.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): own shared sessionIds by boot epoch
Claim SSH/local registry slots with bootEpoch so a stale start cannot
overwrite a replacement, and make closeSession no-op when the payload
epoch does not match the live session owner.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): claim bootEpoch for all reconnectable backends
Pass bootEpoch through Telnet/Mosh/ET/serial starts, claim registry slots
the same way as SSH/local, and dispose displaced owners when a newer
boot replaces the shared sessionId.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): protect preload listeners and ET artifacts on displace
Defer epoch-scoped closeSession listener teardown until main confirms
ownership, and remove ET external-auth temp artifacts when a newer boot
displaces the previous registry owner.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): clean rejected Mosh starts and keep replacement listeners
Kill superseded Mosh/ET PTYs and auth temp files when claimSessionSlot
rejects, and never clear shared preload listeners on epoch-scoped closes.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): settle superseded boots and stop displaced log streams
Reject Telnet/SSH claim failures through Promise reject/failReuse instead of
uncaught EventEmitter/.then throws, and stop a displaced session's log stream
so a logging-disabled reconnect cannot append into the old file.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep woken runtime when disconnect cancels hibernate wake
Disconnect invalidates in-flight hibernated reconnects with keep mode so a
successful wake that already cleared hibernatedRef is not disposed into a
dead pane that can never reconnect. Unmount still disposes orphan runtimes.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): bind reused SSH shellPid only to the claimed session
Late PID discovery after Copy Tab reuse must not write onto a newer
bootEpoch that already replaced the shared sessionId registry slot.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): await plugin cancel cleanup and clear boot epochs on teardown
Track plugin start/cancel promises so Disconnect→Reconnect waits until the
external session finishes unregistering, and stop unmount teardown from
republishing boot epochs into the process-wide map.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): cancel SSH passphrase prompts on disconnect
Register a pending-boot AbortSignal for SSH starts, abort it (and session-
scoped passphrase requests) from closeSession even before a registry slot
exists, and filter/drop late passphrase modals by boot epoch / disconnect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): close owned boot epoch and scope passphrase cancels
Capture the pre-bump epoch for disconnect/teardown closeSession so live
transports are not skipped by epoch mismatch, cancel only passphrase prompts
at or below that epoch, and revalidate boot currency after async passphrase
lookups before queuing a modal.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): tag Telnet auto-login and Mosh ready with bootEpoch
Emit bootEpoch on telnet auto-login and mosh:ready events so overlapping
Disconnect→Reconnect boots ignore cross-delivered callbacks meant for an
older shared sessionId.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): update disconnect assertion and cancel pending serial opens
Align the disconnect source regression with invalidateBootEpochForClose, and
abort in-flight serialPort.open attempts via pending boot AbortSignal so
Disconnect→Reconnect cannot leave a stale open racing the replacement.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep flow-pause leases on skipped epoch closes
Worker close:await previously cleared pause ownership in finally even when
the worker returned skipped for an epoch mismatch, dropping the live
replacement's lease. Only clear on non-skipped closes; epoch-scoped
fire-and-forget close similarly defers to onSessionClosed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
When the terminal worker is disabled, startSSHSessionWrapper probed LAN
access but dropped the resolved address before startSSHSessionWithRetries.
Share attachMacLocalNetworkProbeResult with the worker path so split-DNS
FQDN first hops still get Local Network error guidance.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): harden macOS Local Network TCC trigger for LAN SSH
Replace the main-process TCP probe with Apple's TN3179 UDP discard-port
connect, resolve vault hostnames/.local before probing, hold the socket
briefly so TCC can present the alert, and annotate EHOSTUNREACH errors
that embed LAN IPs even when the host was stored as a DNS name.
Addresses recurring #2673 / #2663 / #1040 Local Network silent failures.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): address Codex Local Network review findings
Clear the UDP connect safety timer once connected so a slow .local
resolve cannot truncate the TCC hold. Mark worker-forwarded start
payloads after the main-process probe so utilityProcess skips a second
hold. Keep the new test comments ASCII-only.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): bound Local Network DNS lookup by probe timeout
Codex noted that hostname resolution before the UDP probe had no
deadline, so a hung DNS lookup could stall every non-literal SSH start.
Race the lookup against the existing probe timeout and skip probing when
it expires so the normal SSH dial can proceed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): tighten Local Network error address parsing
Ignore LAN bind addresses in Node's "- Local (ip:port)" suffix so public
SSH failures are not mislabeled as Local Network denials. Also extract
embedded IPv6 ULA/link-local remotes from unreachable errors.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): only hint Local Network when the first hop is LAN
A .local final host reached through a public proxy or jump must not force
the macOS Local Network hint when the failing dial never touched LAN.
Annotate from the first hop and remote error address instead.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): probe jump-level proxy as the first Local Network hop
Match sshBridge first-hop wiring: a usable jump.proxy overrides the
session proxy and the jump hostname. Otherwise a .local jump behind a
public proxy probes the wrong host, and a LAN proxy in front of a public
jump is missed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): retry .local Local Network probes over udp6
IPv6-only mDNS hosts never resolve on a udp4 socket, so the TCC trigger
could miss them. For unresolved hostnames try udp4 then udp6.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): share Local Network probe timeout across UDP families
udp4 then udp6 fallbacks for .local hosts now inherit one overall
deadline instead of restarting DEFAULT_PROBE_TIMEOUT_MS per family.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* chore: re-request Codex review after unanswered pass
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): skip Local Network probe for ProxyCommand hops
Treat an active jump/session ProxyCommand as a terminal first hop so we
do not fall through to a LAN hostname and spuriously prompt. Also share
one wall-clock deadline across DNS resolution and UDP family attempts.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(ssh): tolerate residual Local Network probe budgets in hold test
Shared-deadline math can arm the safety timer at 499ms instead of exactly
500ms. Identify that timer by being larger than the hold, not by equality.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(macos): preserve ProxyCommand skip during Local Network error annotation
Keep ProxyCommand's skipProbe sentinel through annotateMacLocalNetworkErrorMessage
so .local hop names do not get a false Local Network hint when TCP probing was
intentionally skipped. Split residual UDP budget evenly across remaining address
families so dual-stack and .local IPv6 attempts still get time.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(ssh): fire residual Local Network safety timers in hang probe test
Shared-deadline math arms the UDP safety timer with a residual budget
(e.g. 2999ms) rather than the exact DEFAULT_PROBE_TIMEOUT_MS constant,
so matching only the constant left the hang-socket test unable to settle.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(macos): never hint Local Network for ProxyCommand errors
skipProbe means an external ProxyCommand owns the dial. Do not append the
Netcatty Local Network TCC hint from child-process LAN addresses in the
error text — that guidance would send users to enable Netcatty for a
connection Electron never made.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(macos): ignore downstream LAN addresses behind public first hops
When a public proxy/jump owns the first dial, unreachable addresses embedded
in the error often name a private host the remote side tried to reach. Only
use those addresses as Local Network evidence when they identify the local
first-hop dial.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(macos): keep LAN hints for private DNS first hops and ASCII comments
Treat common private DNS suffixes (e.g. bastion.lan) as Local Network
evidence so hostname-based first hops still get guidance when Node reports
the resolved IP. Replace em dashes and arrows in this module with ASCII.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(macos): keep Local Network hints for unqualified first hops
Single-label jump/proxy names such as "dev-viet" resolve to LAN IPs that
appear in Node unreachable errors. Treat those hostnames as local-side
evidence so annotation still guides users to macOS Local Network settings.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(macos): carry resolved first-hop LAN addresses into error annotation
ensureAccess remembers the LAN IP it probed for a hostname first hop and
forwards it on the worker start payload. Annotation uses that resolved
address so private split-DNS FQDNs like bastion.example.com still get
Local Network guidance without treating public proxies as LAN.
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(terminal): restore hibernate scrollback before viewport
Hibernate wake was writing the viewport first and appending older
scrollback on idle. Under a finite xterm scrollback cap that trims the
just-restored newest rows, so switching back to a hibernated tab could
permanently drop the end of large output (#2762). Replay scrollback →
viewport → pending on the wake path while keeping chunked writes.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): prefer full hibernate snapshot on wake
SerializeAddon range slices omit the seam newline, so concatenating
scrollback+viewport can merge boundary lines. Prefer the coherent full
snapshot for wake history, with a scrollback-then-viewport fallback, and
cover the seam with a real SerializeAddon regression.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* style(terminal): keep hibernate wake comments ASCII-only
Address Codex P3: replace em dash / arrow punctuation in touched
comments per AGENTS.md ASCII convention.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): drain hibernate pending via take-and-clear on wake
Full-history replay widens the window where capped pending output can
grow without increasing length. Length-based slice drains then skip new
bytes. Capture and clear pending before replay, then drain pure deltas.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): final pending drain before hibernate listener teardown
After the bounded take-and-clear loop, take any remaining pending once
more before stopHibernateListeners so wake-time output is not dropped.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): stop hibernate listeners before final pending drain
Awaiting the final pending write while listeners are still live can
lose already-acked bytes that arrive during that write. Stop listeners
first, then take-and-replay any remainder.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep hibernate exit listener through final pending replay
Stop only the data listener before the final pending drain so pending
stops growing, but leave the exit listener registered until after the
reattach decision so an exit during that await is not missed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): pause backend flow across hibernate history replay
Full-history wake no longer races the 512 KiB pending cap or the 64 KiB
preload backlog. Pause-and-wait drains into pending, stop the data
listener, take pending once, replay, reattach, then resume. Removes the
multi-pass drain that still dropped ACKed bytes under sustained output.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): harden hibernate wake against drain failure and exit tails
Check setSessionFlowPausedAndWait success. On drain failure keep the
hibernate data listener with the pending cap disabled for the wake window.
After history replay, take pending once more so exit "[session closed]"
tails and uncapped live arrivals are not discarded.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): pause reconnect hibernate wakes and recheck exit after late drain
Always pause when a backend session exists (including sessionConnected=false
reconnect wakes). Drain late pending plus an exit-tail take before deciding
reattach. Resume flow only after reattach, or on wake failure; successful
reconnect wakes stay paused until cleanupSession.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): restore hibernate listeners on failed wake
On wake failure, dispose the partial xterm runtime and reinstall hibernate
listeners (preserving pending) instead of unpausing into a listener-less
backlog gap. Successful reconnect wakes still keep the pause until
cleanupSession. Add a final pending take before exit-listener teardown.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): restore take-and-cleared pending on failed hibernate wake
Track every pending take during wake and pass it to restoreAfterFailedWake
so a thrown replay/reattach cannot permanently drop already-buffered output
with the disposed partial xterm. Also take once more before exit-listener
teardown.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): drain hibernate pending until empty before teardown
After history replay, loop take-and-append until an empty take so an exit
or residual chunk that arrives during an awaited replay cannot be dropped
by stopHibernateListeners. Data listener is already stopped, so the loop
stays short in practice.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Local terminal paste with an image-only clipboard now forwards raw Ctrl+V into the PTY so nested TUIs (e.g. Claude Code) can read the OS clipboard. Text and file-path paste, and remote sessions, are unchanged.
Fixes#2758
* 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(windows): restore frameless rounded corners and tray transparency (#2505)
Windows frameless windows used a solid host backgroundColor under DWM
rounding, which showed as a dark rim, while the tray popup kept the
default opaque backdrop under CSS border-radius (square tips / 冒尖).
Use a clear transparent host with native roundedCorners for app windows,
and disable OS rounding for the CSS-shaped tray overlay. Keep html/body
transparent on the tray route so corner pixels stay see-through.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(windows): require chrome helpers outside with(ctx)
createMainWindowApi injects require from windowManager.cjs, so a relative
require inside with(ctx) resolved to electron/bridges/windowsWindowChrome.cjs
and crashed every main-window create (MODULE_NOT_FOUND). Load the helpers at
module scope in each factory so paths stay file-local.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(windows): keep resizable app windows opaque
Codex P2: Electron documents transparent BrowserWindows as not resizable,
and enabling resizable can break them. App content windows now only set
roundedCorners: true with a solid theme backdrop; the tray overlay stays
transparent because it is already non-resizable.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(tray): hide opaque splash on tray overlay route
Codex P2: the shared splash screen still painted an opaque full-window
fill when the tray panel showed before React mounted, recreating the
square-corner flash. Mark tray-window early, CSS-hide the splash, and
remove the splash node on DOM ready / tray render.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(tray): defer first show until ready; keep chrome comments ASCII
Wait to show the tray BrowserWindow until did-finish-load so the shared
opaque splash cannot flash as a square underlay. Also rewrite the new
helper file header in ASCII-only English per AGENTS.md.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(tray): cancel pending first-load show on second toggle
If the user toggles the tray again while the first renderer load is still
pending, clear trayPanelShowWhenReady so did-finish-load cannot open the
panel after an intentional dismiss.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(tray): keep shared splash transparent by default
Move the opaque splash fill behind html:not(.tray-window) so the tray
overlay route never inherits a solid full-window background from the
shared splash markup.
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(system): add GPU/NPU monitor tab when accelerators are detected
Probe nvidia-smi and npu-smi during System capabilities checks and show a
GPU sub-tab with utilization, memory/HBM, temperature, power, and compute
processes. Reuses the existing SSH exec + parse pattern (same approach as
open-source remote GPU monitors) without new dependencies.
Related to #2561
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system): address GPU monitor Bugbot findings
- Use PowerShell nvidia-smi collector for local Windows sessions
- Keep GPU tab in loading state while listAccelerators is pending
- Render compute processes even when device list is empty
- Stop treating Ascend HBM usage rate (%) as megabytes
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system): address Codex GPU collector review findings
- Wrap POSIX accelerator script with JSON.stringify so nested sed
quotes cannot break outer sh -c
- Parse Ascend proc-mem whitespace-delimited table rows
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system): show unknown GPU util/memory as -- not 0%
ResourceBar now treats null/undefined as unavailable, and the GPU
device cards pass through null metrics instead of coalescing to 0.
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(sync): rehydrate follow-app theme and guard partial groupConfig wipes
Second devices were keeping the local follow-app terminal theme flag after
cloud sync wrote peer appearance settings, which flickered black vs yellow.
Also block uploading an empty groupConfigs list while other vault data
remains so hydration races cannot clear startup-command group defaults.
Fixes#2757
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sync): narrow complete-wipe shrink guard to groupConfigs
Only treat empty groupConfigs with other vault data present as suspicious.
Wiping the last snippet/note/etc. while hosts remain stays a normal delete.
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): remove serial createReadStream bulk download path
Align downloads with upload fail-closed (#2449/#2719): use concurrent
isolated/shared READ fanout only, wait for the per-session fast slot,
and never fall back to RTT-bound serial createReadStream. Keep hash
helpers on createReadStream for verification only.
* fix(sftp): harden download slot cancel and fail-closed tests
Address multi-agent review: make session fast-slot wait cancel idempotent
with abort restore, recheck cancel after acquire, and lock fail-closed /
slot-wait cancel with condition-based waiter-depth assertions.
Hard-coded win.target arches made pack:win-x64 also emit win-arm64 and
a universal NSIS package that can leave Netcatty.exe missing on ARM
Windows (#2570). Let CLI --x64/--arm64 control packaging instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): keep cancel shared write OPEN drain gated on callback
Cancel settle must not force-complete sharedWriteOpenDrain — only channel
error does — so stage cleanup cannot race a late truncating OPEN after #2720.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): bound cancel OPEN drain and unlink late truncates
Restore cancel drain force-complete so stalled shared OPENs cannot hang
the lease, and best-effort unlink late truncating "w" opens after close so
force-complete cannot leave a recreated stage orphan.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): only unlink generated stages on cancel OPEN
Gate cancel-time truncating OPEN unlink on generatedStagePath so in-place
and symlink destinations keep their final path; cover with a late-OPEN test.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): unlink late staged OPEN after channel-error settle
Late truncating generated-stage OPENs after settle must unlink even when
transfer.cancelled is false, so channel-error force-complete cannot leave
a recreated .netcatty-upload orphan.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): skip late OPEN unlink when same-id retry owns stage
After shared write OPEN drain force-complete, a late truncating "w"
callback still closed and unlinked generated stages. Resumable retries
reuse `.netcatty-<transferId>.part`, so a stale callback could delete
the new attempt's stage. Gate unlink on activeTransfers ownership
(generated stages + truncating only), matching the in-place final
target guard.
* fix(sftp): only skip late OPEN unlink for reusable resume stages
Same-id retry ownership must not suppress unlink for every generated
stage path. Non-resumable uploads use unique .netcatty-upload-* paths;
a stale late OPEN on those must still clean orphans. Only deterministic
.netcatty-<transferId>.part resume stages are protected.
* fix(sftp): gate late OPEN unlink skip on actual staged path
Only suppress late generated-stage unlink when the same-id retry's
active.stagedRemote.path matches. Do not treat resumable+targetPath or
path shape alone as ownership for in-place retries.
* fix(sftp): compare late OPEN stage paths in the same representation
OPEN filePath may be a session-encoded Buffer while stagedRemote.path is the
logical string, so strict equality failed and could unlink a same-id retry
stage. Normalize via encodePathForSession / Buffer equals before ownership skip.
* fix(sftp): gate truncating shared OPEN against same-path retries
Serialize truncating shared-write OPENs per remote path so a same-id
retry cannot issue OPEN "w" while a prior attempt is still unsettled.
When a drain force-complete already released the gate and a late stale
OPEN re-truncates a retry-owned resume stage, invalidate that retry
instead of allowing a sparse promote. Keep late-OPEN unlink skip for
retry-owned stages.
* fix(sftp): bound best-effort unlink so cancel cannot hang
Shared/sudo cancel after OPEN "w" waited on unlink before finishCancel.
A dead channel that never invokes the unlink callback left the transfer
and its session lease active indefinitely. Cap unlink at the same 2s
grace used for shared-write drain force-complete.
* fix(sftp): make truncating shared OPEN path-gate waits cancelable
A same-path upload queued behind a prior truncating OPEN could keep its
lease/admission slot forever if cancelled before runOpen. Race the gate
wait with transfer.abort and release the gate + drain on cancel.
* fix(sftp): scope truncating OPEN path gates per SFTP session
A module-global path-only key serialized unrelated hosts that shared the
same remote pathname. Include transfer/sftp session identity in the gate
key so only same-endpoint opens wait on each other.
* fix(sftp): refuse stage promote after stale OPEN truncation
A late force-completed OPEN w from a prior same-id attempt can zero the
deterministic stage after concurrent write finishes. Latch
staleOpenTruncatedStage through assertCanPromote and uploadFileConcurrent
so promotion cannot rename a corrupt stage.
* fix(sftp): keep path gate chained when canceling a queued OPEN wait
Releasing a cancelled waiter's gate immediately cleared the barrier while
the prior truncating OPEN was still in flight. Chain the waiter's release
to waitForPrior so later same-path attempts stay serialized.
* fix(sftp): hold reusable-stage path gate past OPEN force-complete
Force-completing drain no longer frees the path gate for generated stages
immediately. Same-id retries stay serialized until the late OPEN settles
(or a 10s hard cap) so promotion cannot race a still-processing OPEN w.
* test(sftp): wait for generated-stage path gate hold on retry OPEN
After force-complete, generated stages keep the path gate for 10s more.
Align the late-OPEN retry test wait with that hold.
* fix(sftp): hold truncating OPEN path gates for in-place targets too
In-place shared OPEN force-complete no longer frees the path gate
immediately, and late OPEN invalidation applies to final-path retries so
a stale truncating OPEN cannot wipe an in-flight same-id rewrite.
* fix(sftp): keep path gate held until OPEN settles (no hard cap)
Remove the post-force-complete gate timeout so a different transferId
cannot reopen the same target while a stale truncating OPEN is pending.
* test(sftp): accept normal {transferId,totalBytes} retry success
startTransferNow returns transferId/totalBytes on success, not success:true.
* fix(sftp): reject path-gate waits when the SFTP channel errors
Queued same-path OPEN waiters now observe channel error while waiting on
the prior gate, so a dead session cannot pin lease/admission forever.
* fix(sftp): treat superseded OPEN as cancelled, not failed
Late OPEN rejections after a same-id retry must not broadcast a terminal
failed event that marks the live retry task failed in transfer center.
* fix(sftp): emit cancelled channel for superseded OPEN failures
Worker fan-out only maps transfer:error as cancelled for cancel messages.
Superseded attempts now use transfer:cancelled so retries are not failed.
* fix(sftp): silence superseded OPEN terminal events for live retries
When a same-id retry already owns activeTransfers, do not emit cancelled
or failed for the stale superseded attempt or the transfer center sticks.
* fix(sftp): release path gate on OPEN force-complete for dead channels
When the OPEN callback never arrives, free same-path waiters after drain
force-complete so later uploads do not hang forever on the gate.
* fix(sftp): hold path gate until OPEN settles; 30s dead-channel grace
Force-complete no longer frees the gate immediately. Late OPEN release
keeps retries serialized; a 30s grace frees dead channels.
* fix(sftp): never force-release path gate on OPEN drain force-complete
A dead-channel force-complete must free the transfer/lease, but keep the
path gate held until the OPEN callback actually settles. Force-releasing
lets a same-path retry write then get wiped by a late truncating OPEN.
* fix(sftp): skip upload fallback when OPEN is superseded by a retry
A same-id retry can already own the transfer while the old OPEN callback
still rejects. Mark that rejection noTransferFallback so we never truncate
the shared stage via prepareUploadFallbackCheckpoint under the retry.
* fix(sftp): bound late OPEN close before releasing path gate
After drain force-complete, a late truncating OPEN must still close the
handle before releasing the path gate — but a dead channel may never
invoke CLOSE. Race close with a 2s timeout so same-path uploads cannot
wait forever on pathGate.waitForPrior.
* chore: re-trigger Codex review automation
* fix(sftp): always suppress terminal events for superseded OPEN attempts
Superseded means this attempt lost ownership. Do not require the retry to
still own activeTransfers — if the retry already completed, emitting
cancelled for the same transferId rewrites a completed transfer.
* chore: re-trigger Codex for path-gate head
* chore: nudge Codex automation after cancelled run
* fix(sftp): keep SFTP leases when suppressing superseded attempts
Superseded OPEN cleanup must not release transferId leases — a same-id
retry shares that lease set entry and would lose its hold if we release.
* fix(sftp): do not return result.error for superseded transfer attempts
Renderer directory ops treat any startStreamTransfer error as failure.
Superseded same-id OPEN attempts should return a non-error marker so the
live retry is not marked failed.
* fix(sftp): bound CLOSE on pre-settle cancel OPEN path
Reuse the 2s close timeout for cancel-before-settle so a dead channel
cannot leave the path gate held after finishCancel never runs.
* fix(sftp): hold path gate until unlink callback settles
Remove the 2s force-complete on late shared OPEN unlink so a delayed
server unlink cannot race a same-id retry that already reopened the stage.
* fix(sftp): path-gate r+ resume OPENs; release only unused superseded leases
Serialize all shared write opens including resume r+. On superseded, release
only SFTP leases not also held by the live same-id retry.
* fix(sftp): serialize shared and isolated OPENs by host+path
Path gates keyed only by SFTP object let an isolated-channel retry race a
stale shared truncating OPEN on the same host path. Prefer targetHostId so
both channels wait on the same gate.
* fix(sftp): path-gate isolated writes; revalidate checkpoint after gate
Gate all write OPENs including isolated channels so a shared stale OPEN "w"
cannot race. After waiting on the path gate, re-stat the stage and shrink
the resume checkpoint (and flags) before issuing r+/w.
* fix(sftp): dynamic truncating flags; cross-id stale OPEN; bound unlink
Resolve r+/w at OPEN/unlink time so afterPathGate flag getters still unlink
generated stages on cancel. Invalidate any same-path active transfer (not only
same-id) when a late truncating OPEN lands, and force checkpoint restart when
the stage was wiped before OPEN accepted. Bound best-effort unlink so a dead
channel cannot hang cancel settle.
* fix(sftp): clear staleOpenTruncatedStage after gated full restart
When a late truncating OPEN zeros the stage while a retry waits on the path
gate, afterPathGate restarts from offset zero. Consume the stale flag there so
a successful full rewrite is not failed by the post-upload guard.
* fix(sftp): keep cancel active during post-gate checkpoint revalidation
After the path gate opens, afterPathGate may stat a remote stage. Keep the
transfer abort hook and channel-error listener live for that await so cancel
or a dead channel can still release the path gate and lease.
* fix(sftp): hold path gate for isolated write OPENs until settle
Isolated truncating OPENs previously released the host/path gate on cancel
as soon as sftp.end() was called, allowing a same-path retry to race a still
in-flight OPEN that can truncate the stage without invalidation. Keep the gate
until the OPEN callback (or a bounded force-release after end), and run late
OPEN invalidation for isolated channels too.
* fix(sftp): close late shared read OPEN handles after settle
Restores close of late shared/sudo OPEN "r" handles after cancel or channel
error settle. Write-only gate invalidation remains for isolated late OPENs.
* fix(sftp): copy host IDs onto transfer for path-gate keys
dedicatedTransferResume passes targetHostId with a new targetSftpId. Keep
those host IDs on the transfer object so sharedWriteOpenSessionKey stays
host-scoped across session replacement instead of falling back to sftpId.
* fix(sftp): do not complete directory children on superseded invoke
When a same-id retry steals ownership mid-OPEN, the old startStreamTransfer
returns { superseded: true }. Wait for the live owner's terminal task status
instead of treating that result as a successful child completion.
* fix(sftp): pass host IDs on background restart; scope stale-OPEN invalidation
Background agent resume now includes source/target host IDs so path gates stay
host-scoped after openSftpForSession. Stale OPEN invalidation requires a
matching host or sftp session instead of treating missing host as a wildcard.
* fix(sftp): do not unlink stage after same-transfer fastPut fallback
When concurrent isolated OPEN dies without a callback and upload falls back
to fastPut on the same generated stage, a late OPEN must not unlink that
stage. Skip unlink when the same transfer already accepted an OPEN, owns
stagedRemote, or moved to a fastPut/stream strategy.
* fix(sftp): narrow fastPut unlink skip; handle superseded in all callers
Only suppress late-stage unlink when the same transfer moved to
fastPut/stream — ordinary cancel still unlinks. Background restart and
dedicated resume wait for the live same-id owner instead of treating
superseded invokes as completed.
* fix(sftp): never force-release isolated write path gates
Hold host/path gates for isolated truncating OPENs until the OPEN callback
settles, matching shared-channel behavior. Force-releasing after sftp.end()
allowed a late OPEN to wipe a completed same-path destination with no live
transfer left to invalidate.
* fix(sftp): hold path gate until late unlink settles
Transfer cancel can settle after a 2s unlink grace so dead channels do not
hang invokes, but the host/path gate stays held until the unlink callback
actually fires so a delayed unlink cannot delete a same-path retry stage.
* fix(sftp): wait for live owner without fixed superseded deadline
Superseded invokes now wait only on the live same-id transfer's terminal
status (or cancel). A 30-minute timeout could mark a still-running long
transfer failed and stick that status after the owner later completed.
* fix(sftp): classify superseded before cancelled cleanup
When a cancelled attempt loses ownership to a same-id retry mid-OPEN, the
error is Transfer superseded while cancelled is still true. Handle supersede
first so cancel cleanup cannot delete the retry stage, free its leases, or
emit a terminal cancelled event for the live owner.
* fix(sftp): wait for pending write OPEN before fastPut fallback
Expose pendingWriteOpenPathGate on the transfer so non-resumable concurrent
isolated failures wait for their own truncating OPEN to settle before
fastPut rewrites the same stage.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(settings): 支持一键清空所有会话日志
在系统设置的会话日志卡片中新增清空按钮,删除保存目录下的
所有日志文件及主机子目录;操作前弹窗二次确认,完成后展示
删除/失败数量。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(session-logs): restrict clear-all to known log artifacts
Clearing the session logs directory no longer recursively deletes every
top-level entry. Only files matching Netcatty auto-save / export naming
patterns and host folders that exclusively contain those artifacts are
removed, so a shared Documents/Downloads save path cannot wipe unrelated
user files (Codex P1 on #2738).
* fix(session-logs): skip active auto-save streams during clear-all
Clear-all must not rm host dirs or log files still open by
sessionLogStreamManager write streams (orphan inode / silent stop).
Expose getActiveLogPaths and preserve those targets while still
deleting inactive siblings and pure host folders.
* fix(session-logs): preserve active snapshot-only host dirs on clear-all
txt/html streams may not create a file until the first snapshot, so treat
any active log path whose parent is the host directory as live work before
removing pure session-log folders.
* fix(session-logs): preserve worker-owned active streams on clear-all
Clear-all only queried the main-process sessionLogStreamManager, so
worker-owned auto-save files were invisible and could be deleted while
live. Union active paths from a running terminal worker via
netcatty:sessionLogs:getActivePaths, and skip cold-starting a stopped
worker.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(#2697): automated Cursor CLI fix
* fix: address Codex review on PR #2734
* fix(ai): keep MCP absolute approval deadline and Escape reject
Canceling idle review no longer leaves MCP approvals unbounded past the
external agent tool-call window, and Enter-only key stopPropagation lets
Escape still reject from Copy/Expand controls.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): keep wrapped CLI target context on approval cards
Parse --session from netcatty-tool-cli wrappers into execution context and
still show invocation args when the pending card displays an unwrapped command.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): surface Codex reason on pending approval cards
Keep agent justification visible in the execution-context strip and still
show extra invocation args such as commandActions beside the command block.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): bound Catty approval cancel with absolute deadline
Keep Catty confirm-mode approvals from going unbounded after review:
cancelApprovalTimeout re-arms the creation absolute deadline (MCP-style)
so a late approve cannot outlive the original timeout/stream tool budget.
Also harden pending-card Enter: extract isNestedInteractiveApprovalTarget
so Copy/Expand (and other nested controls) never Approve Once via bubble.
* fix(ai): bound Codex approval cancel and unwrap CLI wrappers
cancelInteractionTimeout now re-arms the absolute creation deadline
(Catty/MCP style) instead of leaving review unbounded. Also treat
netcatty-tool-cli.cjs/.cmd as CLI wrappers so approval Command/Copy
shows the remote command after --.
* fix: address Codex review on PR #2734
* fix(ai): separate Catty approval idle timer from hard deadline
Review activity re-arms a full idle window from now instead of the original
absolute remainder, capped by a hard deadline (3x idle, max 30m). Stream
tool budgets use the hard deadline so mid-review approvals are not cut off.
* fix(ai): re-arm Catty approval idle and sync-deny hard deadline
Review interactions kept a one-shot timeoutCancelledRef, so later
cancelApprovalTimeout calls no longer re-armed idle. Remove the one-shot
and deny synchronously when the hard deadline has already elapsed so a
late approve cannot race setTimeout(0).
* fix(ai): re-arm approval idle on args-only scroll review
Pending approvals without a display command (Codex file-change/permissions,
write tools with JSON args) only show the overflow args block. Wire the same
onScroll/onWheel markReviewing path used by the command pre so continuous
wheel/trackpad review re-arms Catty and MCP/Codex idle timers.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.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): backfill sync provider bindings after schema upgrade
Promote legacy sync-provider-map secrets into the host binding table on
open, and infer plugin ownership from sync-credential rows so disconnect
still cleans credentials when bindings were never written.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): infer sync provider owners only via pluginId. namespace
Exact pluginId===providerId is not ownership for contribution provider
ids; prefer the longest pluginId. prefix when backfilling disconnect cleanup.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(plugins): stop inferring sync owner from credential prefixes
Parent plugin ids can namespace-prefix another plugin's provider id.
Guessing ownership from sync-credential rows would let disconnect wipe
the wrong secrets. Keep host bindings and legacy map backfill as the
only ownership sources when no live contribution is present.
* fix(plugins): do not overwrite existing sync provider bindings on backfill
Legacy sync-provider-map rows from a parent plugin must not steal a
provider that already has a host binding. Skip upsert when a binding
exists; still delete the stale map secret.
* fix(plugins): pick longest owner when legacy map rows conflict
Group sync-provider-map rows by provider before binding so a shorter
parent plugin cannot win just because SQLite returned it first. Still
preserve existing host bindings and delete all leftover map secrets.
* fix(plugins): never delete secrets during sync-provider-map backfill
The map key prefix is not reserved for host internals; plugins may store
ordinary secrets under the same shape. Only seed missing host bindings
from candidate rows and leave plugin_secrets untouched.
* fix(plugins): seed sync bindings from live provider metadata
Schema-2 installs only stored sync-credential* rows. After upgrade, seed
missing provider bindings from the host contribution catalog when those
credentials exist, instead of guessing from secret key prefixes.
* fix(plugins): seed sync bindings from installed manifests, not only enabled
listProviders skips disabled/quarantined plugins. Walk installed plugin
manifests at host init so schema-2 credential rows under disabled plugins
still get provider bindings for disconnect cleanup.
* fix(plugins): seed sync bindings from all installed version manifests
Active-only listPlugins() misses providers removed/renamed on upgrade
while credentials remain under the plugin. Walk every plugin_versions
manifest so disconnect can still resolve ownership after schema upgrade.
* fix(plugins): only auto-bind single-provider plugins from credentials
Plugin-scoped sync-credential* rows are shared across providers. Binding
every historical provider would let disconnect of a stale one wipe the
active provider's secrets. Seed bindings only when exactly one sync
provider is known for that plugin.
* fix: address Codex review on PR #2752
* fix(plugins): stop resurrecting sync bindings after unbind
Consume promoted sync-provider-map markers and leave an unbound
tombstone on disconnect so constructor backfill cannot re-bind a
provider the user already disconnected.
* fix(plugins): prefer credential-backed owners for legacy map backfill
Longest sync-provider-map namespace alone could bind a nested plugin over a
parent that still owns sync-credential* secrets. Only promote candidates with
credentials; pick the longest among those, and skip when none or ambiguous.
* fix(plugins): skip ambiguous multi-credential legacy map backfill
Only auto-bind when exactly one credential-backed map candidate exists.
When parent and nested plugins both hold credentials for the same provider,
longest pluginId is not a reliable owner signal — leave unbound.
* fix(plugins): skip cross-plugin provider conflicts in live backfill
When multiple credential-backed plugins declare the same providerId via
nested namespaces, do not bind the first writer. A later missing-provider
disconnect would otherwise delete the wrong sync-credential rows.
* fix(plugins): use full installed catalog for enable-time sync backfill
Enable-time binding must see the same active+inactive manifest catalog as
startup so cross-plugin nested provider ambiguity is not missed when only
the newly enabled plugin's live providers would be passed.
* fix(plugins): count non-credential manifest claims in backfill conflicts
Live nested plugins without credentials yet must still block binding a
credential-backed legacy parent to the same providerId, or disconnect can
prefer the live owner while the stale binding remains.
* fix(plugins): skip legacy map promote on installed-manifest conflicts
Constructor-time map backfill must not bind a sole credential-backed
parent when another installed package also declares the same sync
provider, or later disconnect cannot clear the stale binding.
* fix(plugins): require single-provider maps for legacy-map promotion
A plugin with one sync-credential row and maps for multiple historical
providers must not bind all of them — disconnecting a stale provider
would delete the live credentials via the shared prefix.
* fix(plugins): count installed manifest providers in legacy-map guard
A sole stale map row must not promote when the credential owner's live
manifest also declares another sync provider under the same credentials.
* fix(plugins): backfill sync bindings before startup activation
Seed installed-manifest bindings before contributionService.initialize so
an early syncDeleteSecrets cannot miss ownership and leave orphan credentials.
* fix(plugins): seed sync bindings at host construction
Run installed-manifest backfill when the plugin host is created, before
async manager.initialize, so early syncDeleteSecrets can resolve ownership.
* fix(plugins): re-seed sync bindings after package store recovery
PackageStore.recover can add installed versions after the constructor
seed; re-run the catalog backfill when package initialize finishes.
* fix(plugins): await manager init before syncDeleteSecrets ownership
Block secret cleanup until package recovery and binding seed finish so
early disconnect cannot miss retained sync-credential rows.
* fix(plugins): re-run legacy-map backfill after package recovery
After PackageStore.recover repopulates versions, re-run legacy-map promote
so recovered manifests participate in ownership conflict checks.
* fix(plugins): defer legacy-map backfill until after package recovery
Constructor no longer promotes sole map candidates before recovered
manifests are visible. hostService re-runs backfill after package init.
* fix(plugins): defer live binding seed until after package recovery
Construction-time live-provider backfill can bind a stale owner against an
incomplete catalog before PackageStore.recover() restores plugin_versions.
Seed only after recovery; syncDeleteSecrets already awaits manager init.
---------
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(ci): dedupe same-head @codex review requests
Plain maintainer/Cursor-bot @codex review comments did not plant the
cursor-external-codex marker, so own_rerequest on synchronize posted a
second request for the same SHA. That concurrent pair produced a clean
issue comment from one job and a findings review from the other.
Skip re-request when any trusted or Cursor-bot author already requested
this head via external marker, cursor-codex-head pin, or a recent plain
@codex review.
* fix(ci): only dedupe plain @codex against the current head push
Plain unpinned requests no longer skip solely by age — that blocked
re-request after a new synchronize when an older plain @codex still
looked "recent". Callers pass the head commit time as notBefore so
only post-push plain requests suppress a duplicate.
* fix(ci): use PR head-change time for plain @codex dedupe
Commit author/committer dates predate cherry-picks and rebases, so a
plain @codex for the previous head could suppress re-request after a
new synchronize. Pass pull_request.updated_at (moves with the push)
as notBefore instead.
* fix(ci): dedupe @codex only via head-pinned markers
Drop plain-unpinned timestamp heuristics — they cannot safely know which
SHA a bare @codex review targeted. Skip re-request when a trusted author
already pinned this head with cursor-codex-head or cursor-external-codex.
* fix(ci): let maintainer @bot reclassify auto-closed issues
Author reopen disputes still stay on issue_followup when
triage:already-available/unclear remain, but an explicit maintainer
@netcatty-bot mention must re-enter issue_classify so re-triage can
run without an open bot PR.
* fix(ci): prefer maintainer @bot over author follow-up routing
When a trusted maintainer is also the issue author, route explicit
@netcatty-bot mentions as maintainer re-triage so auto-closed
already-available/unclear issues can re-enter classify.
* fix(ci): stop reopen from replaying issue triage
Already-admitted issue reopen events were routed back into full classify,
so follow-up handoffs and maintainer reopens could close again or overwrite
ready-for-human with needs-info. Skip bot reopen, hand auto-closed human
reopens to maintainers, and keep already-available disputes on follow-up.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): keep ready-for-human follow-ups off classify
After reopen handoff drops triage:already-available, actionable author
replies could still be refined into classify and auto-close again. Treat
ready-for-human like the dispute signal and keep those comments on
follow-up.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): preserve already-available dispute label on handoff
Keep triage:already-available when handing reopened auto-closed issues
to humans, and only use that label (not ready-for-human) to block
reclassify. This stops the close loop without blocking feature_defer or
other ready-for-human follow-ups from re-entering classify.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): keep unclear reopen disputes off classify
Match the already-available dispute guard for triage:unclear / unclear so
human reopen handoffs of auto-closed unclear issues cannot re-enter
classify and close again on the next author follow-up.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): revalidate auto-close labels before handoff
Queued ready_for_human_handoff must not overwrite a maintainer who already
cleared triage:already-available/unclear and moved the issue (for example
to ready-for-agent). Skip the handoff when those outcome labels are gone.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(ci): cover skipped reopen handoff without auto-close labels
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): do not reopen issues already closed before handoff
Queued ready_for_human_handoff must respect a maintainer who re-closed the
issue while the job was waiting. Skip when live state is closed instead of
forcing ensureOpen.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): reclassify non-auto-close issue reopens
After a merged automation fix, cleanup leaves triage:bug-ready/admitted on
the source issue. Human reopen of those labels must re-enter classify so a
failed fix can spawn another implementation; only bot reopen stays skipped.
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(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>
macOS copy defaults to Cmd+C. With no terminal selection, the shortcut
handler used to consume the chord as an empty clipboard write, so nested
TUIs that expect Kitty Super+C (e.g. Herdr) never received the key.
Pass plain Meta+C through the same no-selection path as Ctrl+C, while
keeping other unbound copy chords as a safe no-op (#1461).
Closes#2728
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(#2719): automated Cursor CLI fix
* fix: address Codex review on PR #2720
* fix(sftp): settle shared OPEN cancel without ending sudo session
Codex P2 on PR #2720: disposeChannel:false made pre-OPEN abort a no-op
while openSftpHandle had no cancel reject, so cancel during a stalled
OPEN hung forever and held the SFTP lease. openSftpHandleForTransfer
rejects the pending OPEN on cancel without sftp.end() on shared channels,
closes late handles best-effort, and keeps isolated-channel end behavior.
* fix(sftp): force-settle cancelled shared download READs
Codex P2 on d855e69c: shared/sudo downloads passed forceSettleOnError
false, so cancel while an sftp.read callback never arrived hung forever
(abortChannel cannot end the shared session). Downloads have no remote
WRITEs to drain, so always force-settle on cancel/error without calling
sftp.end(). Shared uploads still wait for in-flight WRITEs.
* fix(sftp): harden sudo shared download settle and fanout gates
Address Codex P2 (channel error during stalled OPEN never settled) and
multi-agent review: keep concurrent-shared READ sudo-only with a
per-session fast slot, settle OPEN sync throws without leaking listeners,
force-settle cancel without awaiting shared CLOSE, and report stream
strategy after fallback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): dispose isolated channel on CLOSE timeout
Codex P2: success-path CLOSE timeout with disposeChannel:true was
non-fatal and returned the channel to the pool. Shared/sudo stays
non-fatal; isolated downloads now surface completedWithUnhealthyChannel
so the channel is ended instead of reused.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): drain shared write OPEN before cancel cleanup
Codex P2: cancelling a pending shared upload OPEN settled immediately,
so stage cleanup could run before a late truncating OPEN recreated the
remote path. Shared write opens now wait for the OPEN callback (or a
drain timeout), close the handle, then settle.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): keep shared write OPEN drain past cancel timeout
Codex P2: the 2s cancel settle unblocked UX but cleanup could still race
a later truncating OPEN. Publish sharedWriteOpenDrain until the OPEN
callback finishes (including late opens), and await it in upload finally
before returning to stage cleanup.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): await shared write OPEN drain without timeout
Codex P2: the 10s drain cap still let cleanup race a slower late OPEN.
Await sharedWriteOpenDrain unbounded so stage cleanup cannot run until
the write OPEN callback has closed its handle.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): keep shared write OPEN drain pending on channel error
Codex P2: channel error completed sharedWriteOpenDrain before the OPEN
callback, allowing cleanup/fallback to race a late truncating OPEN.
Match the cancel path — settle the wait, but release the drain only after
the OPEN callback closes any late handle.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): force-settle stalled shared verification READs on cancel
After concurrent download ranges finish, sample verification still awaited
bare sftp.read outside forceSettleOnError. Shared/sudo cancel cannot end
the session, so a hung verification callback held the transfer and lease.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): recheck cancel after verification sample race
A responsive last-sample READ can finish after cancel before the 2s
force-settle fires; without a post-race cancelled check the download
was reported complete.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): retain verification channel errors across sample gaps
A shared-channel error between verification sample races was dropped
when rejectPending was null, so the next read could hang forever.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): bound shared write OPEN drain after channel error
Channel error kept sharedWriteOpenDrain pending for a late OPEN, but a
dead channel that never invokes the callback hung the transfer and lease.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): stop green Codex fixes failing baseline name matching
Accept exit-0 candidates when quantitative coverage does not shrink,
even if individual TAP success titles were renamed. Keep rejecting
true coverage loss, upload baseline test logs, and surface comparison
details on rejected verification runs.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): require baseline failures to be fixed by name
Address Codex P1: a red→green candidate must keep each failing test
title as a success, so deleting the failing test and adding an unrelated
passer cannot pass the quantitative clean gate.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): keep comparator comments ASCII-only
Replace the non-ASCII arrow in the red-to-green comment so the file
stays consistent with the repo ASCII convention.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ci): fail closed on incomplete baseline TAP summaries
Require a complete exact-base summary before accepting a quantitative
clean candidate, so a truncated red baseline cannot be bypassed by
deleting crashing coverage.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(ci): clarify incomplete-baseline fail-closed coverage
Keep the comparator fail-closed on truncated exact-base TAP summaries
and re-request Codex review on the current head.
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(#2737): automated Cursor CLI fix
* fix: address Codex review on PR #2739
* fix(vault): harden destination re-dedupe passphrase and counts
Keep collapsed destination imports aligned with CSV merge policy for key
passphrases, and correct plugin importer added/duplicate counters when
hosts collapse into one group.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): preserve SecureCRT and plugin hosts under destination group
Skip endpoint collapse for SecureCRT imports, and keep plugin hosts that
differ by configuration when an import destination rewrites their group.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): skip plugin imports that collide after destination rewrite
After rewriting imported hosts into the destination group, drop ones that
now share a fingerprint with existing vault hosts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): keep group case in host merge keys
Vault group paths are compared exactly, so Prod and prod must remain
distinct session identities during import/create dedupe.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): prune orphan credentials after destination collisions
When a destination rewrite drops colliding imported hosts, also remove
identities/keys that were only referenced by those skipped hosts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): only prune newly imported credentials on collision
Pass existing identity/key counts into destination handling so remapped
duplicates of vault credentials are never deleted when a host is skipped.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): merge credential refs when collapsing destination hosts
Carry identityId/telnetIdentityId/identityFileId onto the retained host
so destination-group collapse does not drop referenced auth.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): only graft credential refs onto bare collapsed hosts
Avoid copying identityId onto a retained host that already has key or
password auth, which would silently change resolveHostAuth preference.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.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>
* fix(#2727): automated Cursor CLI fix
* fix(sftp): drop external-edit retainers when browse sessions close
closeSftp already deletes registered temps on disconnect/reconnect; clear
the matching renderer retainers so hasActiveWork cannot stick on a gone file.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): clear external-edit retainers when watches force-stop
When the temp file disappears, fileWatcherBridge force-stops the watch
with localPath; drop the matching retainer so hasActiveWork cannot stick.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address Codex review on PR #2730
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): stop OpenCode turns dying on terminal tool failures
OpenCode MCP terminal_execute treated non-zero exits as bare
"Error: Operation failed" (dropping stdout), and the OpenCode driver
aborted the whole turn on tool status=error. Preserve command output
and surface tool errors as tool results so multi-step agent runs can
continue.
Closes#2718
* fix(ai): use neutral MCP text for silent terminal_execute success
Codex P2 on #2724: ok:true empty serial/raw PTY results no longer fall
back to "Error: Operation failed". Prefer Catty's "Command completed
(no output)" and keep operational failures / non-zero exits unchanged.
Also prefer non-empty OpenCode tool error/output when formatting tool
results.
Add single-entry delete on the Global history side panel, remove the
matching host autocomplete record when no duplicate row remains, and
keep the change set intentionally slim (no multi-window journal/sequence
hardening).
Fixes#2701
* fix(terminal): keep broadcast through command colons
Treat trailing colons after a confirmed shell prompt as typed command
input so workspace broadcast is not paused mid-command (#2709).
Require terminator + whitespace so mid-label shapes like Challenge #1:
stay fail-closed; standalone auth prompts remain untrusted.
* fix(terminal): require real PS1 body for colon broadcast bypass
Title-case challenge labels that embed shell markers (Challenge # 1:,
Account $ code:) must stay fail-closed. Only bare markers, user@host,
paths, shell-version PS1s, and lowercase identities count as a real
prompt prefix for the #2709 mid-command colon exemption.
* fix(hosts): keep draft connect scripts before onConnect promote
Host details queue editing pruned non-onConnect scripts immediately, so
default manual automation scripts could be selected but never appeared
in the queue. Keep an editable draft queue and only prune missing IDs
until save promotes triggers.
Closes#2705
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): promote persisted manual connect-queue scripts on save
Editable queue retention left already-saved manual script IDs unpromoted
when reopen/save compared only newly added IDs. Promote every queued
script that still needs onConnect + host targeting.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(hosts): drop unused HostDetailsPanel snippet imports
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): do not demote global scripts during queue save sync
Promote-on-save must leave targetsAllHosts onConnect scripts unchanged
when they appear in a host connectScriptIds list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(hosts): cover global connect scripts during queue save sync
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): promote connect-queue scripts on new host save
New hosts have no initialData, so queue promote-on-save was skipped and
manual scripts stayed non-onConnect after the first save.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): harden connect-queue promote against Codex review
Preserve global scope when promoting manuals, skip re-promoting scripts
demoted while the host editor is open, sync peer host queues for retained
targets, and keep test fixtures ASCII-only.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): persist peer connect queues with edited host save
Peer queue sync via onHostsChange was overwritten by onSave rebuilding
from a stale hosts snapshot. Write peers+edited host together and make
save paths apply functional host updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): keep connect queues across hydration and concurrent edits
Skip queue rewrite while snippets are still empty, and do not promote
entries whose non-onConnect trigger changed while the host editor stayed
open.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): skip connect-queue rewrite before snippets hydrate
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): preserve concurrent target removals and merge peer deltas
Drop stale queue entries when targets were unlinked while editing, and
apply peer connectScriptIds updates via functional host state merges.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): hydrate connect-queue baseline and append peer deltas
Capture the first hydrated snippet catalog as the editor baseline, and
merge peer queue updates by appending only newly queued script IDs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): snapshot opening queue and keep peer updates host-safe
Use the editor-opening connect queue as sync previous IDs, and never
overwrite the edited host inside peer delta merges.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): clear deleted queues after hydrate and honor manual unlinks
Treat a hydrated empty snippet catalog as ready for queue reconciliation,
and drop concurrently untargeted non-onConnect queue entries on save.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): clear loginScriptId when connect queue is emptied
sanitizeHost reconstitutes connectScriptIds from loginScriptId when the
explicit queue is absent, so clearing the last queued script must also
drop the legacy field.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Shell-open retries already applied to every Copy Tab reuse, but resilient
PID scans were still gated on jumpHosts. Direct bastion targets that
rate-limit session channels could reuse the shell while leaving the
copied tab without shellPid. Always retry rate-limited discovery on the
reuse path; keep the optional pre-scan pause jump-host-only.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
If the jump-host Copy Tab pre-open PID scan is rejected with channelOpen
too offen, a single delayed attempt left discovery unavailable and skipped
post-open assignment. Retry rate-limited pre-open scans so shellPid
tracking still succeeds before opening the reused shell.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
After a bastion-rate-limited shell open succeeds on Copy Tab, the follow-up
PID discovery exec can hit the same "channelOpen too offen" limit. Delay and
retry that discovery on jump-host reuse so the copied session still gets a
shellPid for unambiguous cwd lookup.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Address review feedback: permanently skipping PID discovery on jump-host
Copy Tab left shared transports without shellPid and broke cwd lookup.
Delay discovery briefly instead, and keep shell-open retries for the
bastion "channelOpen too offen" rate limit.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Bastion/jump hosts often reject rapid session channel opens with
"channelOpen too offen". Copy Tab was opening a discovery exec then a
shell on the same authenticated transport, failing reuse and falling
back to a fresh login that prompts for OTP again.
Skip shell PID discovery when jump hosts are present, and retry shell
opens with short backoff on the bastion rate-limit error so duplicated
tabs keep reusing the live connection.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
If residual/stale HKLM keys cannot be rewritten without elevation, register
per-user HKCU verbs instead so Settings can still enable Open in Netcatty
for portable and unelevated installs.
Persist and compare development app entry args so moving a checkout
re-registers Explorer shell commands instead of keeping a stale
electron.exe + old app path launch line.
Clear only ProgrammaticAccessOnly when unsuppressing keys that still have
commands, and unsuppress after a failed HKLM refresh so preference-write
rollback cannot leave the only working portable integration hidden.
When only one ProgrammaticAccessOnly write succeeds, remove just that
value (or a newly created empty key) instead of deleting the whole HKCU
shell subtree that may still hold a working portable command.
When the saved preference says enabled, still confirm both shell verbs
remain registered; otherwise re-apply so interrupted installer rewrites
or partial cleanup cannot leave a permanently missing menu entry.
When HKLM registration exists, disable only writes ProgrammaticAccessOnly.
Deleting portable HKCU install verbs before a failed suppression left users
with no working Explorer integration and an unrepairable machine path.
Record a separate probe marker for ZIP/portable no-verb defaults so a
shared AppData profile cannot suppress a later NSIS install. Register
development Explorer verbs as electron.exe + absolute app entry.
After a failed machine repair, report any surviving shell verb as enabled
so Settings can disable it. Persist enabled:false for ZIP/portable defaults
so warm starts skip repeated registry probes.
When a partial machine registration exists, keep working portable HKCU
verbs until both HKLM shell entries are current, so unelevated enable
attempts cannot destroy the user's only working context-menu path.
Treat residual portable HKCU verbs as a hard enable failure when HKLM
exists, require suppression cleanup on user-scope enable, and restore
the previous preference when apply partially mutates the registry.
Codex P2 on #2671: portable/ZIP builds leave real HKCU shell verbs that
shadow later all-users HKLM registrations. When machine keys exist,
delete the per-user Netcatty verbs so Explorer uses the current install.
Codex P2 on #2671: if only one ProgrammaticAccessOnly value is written
during disable, delete the partial key so disable never leaves a single
hidden verb while the other remains visible.
Omit sourceSessionId for sftpSudo hosts in the picker and normalize the
browse connect path so reusedConnection (spinner/overlay/logs) matches the
dedicated sudo open. Addresses Codex review on #2674.
Codex P2s on #2671: remove treats any leftover runnable verb as still
enabled, and install restores ProgrammaticAccessOnly when enabling fails
after clearing a prior per-user suppression.
Codex P2 on #2671: when no preference exists but a single leftover shell
verb remains, still run install/repair so the Settings toggle cannot stay
off while Explorer still shows one Open-in-Netcatty entry.
Skip used to mark transfers cancelled without clearing conflict metadata, so reopening SFTP rehydrated the dialog while the transfer center looked empty. Clear conflict on skip/stop, only restore attention conflicts in the dialog, and strip stale payloads when loading persisted history.
Fixes#2636
Codex P2 on #2671: unelevated refreshes must not report success merely
because non-empty HKLM commands exist. Require writes to succeed or both
verbs to already match the expected executable/command contract.
Codex P2 on #2671: treat the integration as user-suppressed only when
both folder and background keys have ProgrammaticAccessOnly, so a partial
suppress write cannot hide one menu while claiming the feature is off.
List open SSH terminals in the SFTP Connected section even when sftpSudo
is enabled. Connection still opens a dedicated sudo SFTP channel and does
not reuse the terminal shell. Closes#2656.
Address Codex P2s on #2671: disabled preferences re-suppress on every
startup so NSIS updates cannot resurrect the menu, and registration is
only considered complete when both folder and background commands exist.
Codex P2 on #2671: a partially written registry verb (key exists, no
command value) must not count as an active menu registration, so install
and Settings toggle state stay honest and repair can still run.
Persist download fingerprints as sha256:pN:hex so coverage is explicit,
treat planned zero bytes as the empty digest, and fall back to a full-file
check for legacy bare sha256:hex identities saved by older builds.
Standalone dedicated reconnect was still coercing totalBytes=0 to
undefined via ||, so the bridge re-statted a grown remote and abandoned
the empty snapshot plan. Keep finite zeros in the stream payload.
Persist the last-applied executablePath in the preference record and
re-apply shell verbs when it differs from the current launcher path so
renamed/moved portable builds keep Open-in-Netcatty working.
Drop growth-aware compact identity experiments that either lost rewrite
proof or needed renderer node:fs. Compact entries keep full size+mtime
identities (retransfer on remote growth). Preserve explicit totalBytes=0
through bridge preflight so empty download snapshots do not re-stat into
a grown remote size.
Codex P2 on #2671: electron-builder portable apps expose the stable
launcher via PORTABLE_EXECUTABLE_FILE while process.execPath points at a
temp unpack that is deleted on exit. Resolve that path for shell verbs
and bump the preference schema so existing installs re-register once.
Path-only compact identities were too weak for rewritten/truncated files.
Download trees now key entry identity to path+planned size (omit mtime),
revalidate covered compact entries against local destination sizes, and
preserve explicit zero-byte snapshot plans through resume validation.
Address Codex P2 on #2671: the Settings toggle is per-user, so disable
only clears HKCU and suppresses machine verbs via ProgrammaticAccessOnly
instead of deleting HKLM (even when elevated). Also persist a schemaVersion
so healthy warm starts skip registry work after the first migration.
Codex P2 on #2671: warm starts no longer spawn a full reg.exe write pass
when installer/user shell verbs already match the expected command, label,
and icon. Still rewrites stale --open-terminal-path entries on upgrade.
Preserve the planned snapshot size for interrupted directory download
children, use path-stable compact identities for download trees so
append-only growth does not invalidate covered prefixes, and treat
zero-byte planned snapshots as complete in the sequential fallback.
Codex review on #2670: accept remote growth only after the full planned
prefix still matches the staged file, and skip the sequential stream when
the checkpoint already covers the snapshot so grown logs do not resume
from the old EOF.
Address Codex review on #2671: when all-users (HKLM) shell verbs cannot be
deleted unelevated, hide them for the current user via ProgrammaticAccessOnly
instead of failing the Settings toggle. Also stop mirroring machine verbs into
HKCU so uninstall does not leave a broken Open-in-Netcatty entry.
Add a Settings > System switch to enable/disable the Explorer folder
context menu, and fix shell launch so Chromium does not swallow
--open-terminal-path (args after --, =path form, trailing-dot roots).
Closes#2651Closes#2655
Address Codex review: after the async clipboard read, only insert when
the find input is still document.activeElement and still connected, so
a delayed paste cannot steal focus or hit the editor body.
SFTP downloads take a fixed size snapshot at start, then rejected the
transfer if the remote file grew before completion. Live logs and other
append-only sources commonly hit that post-check and failed with a size
mismatch. Accept remote growth when the planned prefix was transferred,
and keep resume identity keyed to that snapshot so pause/resume still works.
Closes#2653
Custom Ctrl/Cmd+V handling for Electron overwrote paste while the
snippet script editor find bar had focus, so search text landed in the
script. Route find-widget paste through shared helpers and reuse them
in the text editor.
Fixes#2661
Bound the main-process TCC probe to 3s, only treat real private IPv4/IPv6
literals as LAN, prefer proxy as first hop when set, and annotate
unreachability using either the target or the first hop. Closes the
remaining merge blockers for #2663.
Show manual compact for any Catty session with a configured model, not
only when enough history exists for a large head. External agents still
do not get the command.
Gate /compact behind canCompact so the composer is not cleared as a
silent no-op, hide the usage ring until a real context snapshot arrives,
and summarize only the UI head that the persisted compaction boundary
will drop. Drop unused clearSessionMessages surface and add slash-command
regression tests.
Emit plan items as { text, completed } with top-level running|completed status so sdkAgentAdapter and the activity UI can render labels and completion instead of empty rows stuck on running.
Remove the async rpc.request hop that let promptDone win Promise.race on session/prompt JSON-RPC errors. Gate emitDone on turnCompleted, treat any incomplete process close (including exit 0) as failure, and align streaming-json toolResult emission with ACP when rawOutput is present.
Permission replies must select an optionId offered in params.options instead of inventing allow-once. Process close(null, signal) before turnCompleted is a failure for both ACP and streaming-json drivers.
Attach a stdin error listener so async EPIPE after peer exit is not an
unhandled stream error. Treat EPIPE/ERR_STREAM_DESTROYED as benign
(processErrorGuards/terminalBridge); fail the turn only for other mid-turn
write errors.
Only turnCompleted (headless end / ACP prompt success) may ignore teardown
exit codes. streamedAssistantText must not suppress errors, or mid-response
crashes look successful.
Root cause of Token ~1: onPromptComplete resolved promptDone in the same
tick as session/prompt, so Promise.race often discarded the result and the
UI fell back to estimated usage (prompt length / 4). Emit usage when the
prompt response is handled, and map live Grok _meta.usage fields including
cachedReadTokens.
Drop the inactive acp/streaming-json in-memory session when switching
runtimes so switch-back cannot resume a pre-switch thread without
intervening turns. Forward optional session/prompt usage to emitter.usage.
Tool-only turns leave no assistant text; Windows teardown often exits with
code 1. Mark turnCompleted when session/prompt succeeds so close does not
emitError or skip emitDone.
Windows .cmd/.bat installer shims fail with spawn EINVAL when launched
without shell. Route ACP, headless, and models spawns through the same
helper other managed agents use.
When a stored session id cannot resume/load, still inject renderer history
into the new session prompt so multi-turn context is not dropped. Successful
resume/load paths remain unseeded to avoid stacked prior replies.
Prefer ACP session/resume (then load/new) without replaying history into the
current turn, authenticate when Grok advertises methods, isolate acp vs
streaming-json session identities, and apply MCP local-tool lockdown plus
--no-auto-update on both transports.
Suppress session/update emissions until session/prompt starts so
session/load history replay does not fill the current assistant bubble.
Also treat Grok like CodeBuddy: when a resume session id exists, do not
also inject renderer conversation history into the prompt.
Expose a Settings toggle on the Grok Build card (like Codex App Server)
to choose ACP agent stdio or the original headless streaming-json path.
Persist grokRuntime on ExternalAgentConfig and pass it into the driver
via NETCATTY_GROK_RUNTIME on the agent env.
Grok agent stdio session/new rejects plain object env (Invalid params /
McpServer enum). Emit type:stdio servers with env as [{name,value},...]
matching the live protocol, and assert the shipped shape in unit tests.
In MCP tool-integration mode, pass --disallowed-tools for Grok built-in
shell/edit/write so remote session work goes through injected Netcatty MCP,
matching Claude-style policy. Skills mode leaves local shell available for
the Netcatty CLI skill path.
Curate GROK_MODEL_PRESETS and wire getAgentModelPresets so the chat model
picker still has a non-empty list when live grok models discovery fails,
matching the Claude/Codex fallback pattern.
Expose Grok as a managed Settings agent card, icon, coding-CLI provider,
and runtime model discovery target. Add en/zh/ru strings for in-app Grok
Build (distinct from External MCP client install).
Add grok to ManagedAgentKey/meta, discovery known-agents list, XAI_API_KEY
and ~/.grok/auth.json auth probe, and closed sdkBackend unions so the CLI
is discoverable and routable like other managed agents.
Register a headless streaming-json driver for backend key grok that maps
ACP-derived NDJSON events into the shared emitter protocol and injects
Netcatty MCP via a project .grok/config.toml merge with restore.
Codex P2: restoreWorkspace() rebuilds the workspace object field-by-field
and omitted autoTitle, so with Restore Previous Session enabled an
explicitly named "Workspace" (rename / creation dialog / snippet) lost its
autoTitle:false on restore and got relabeled with a host name.
Carry autoTitle through restoreWorkspace with a boolean guard so a
deliberate false survives and legacy workspaces stay flagless. Adds restore
coverage for both the explicit-false and legacy-absent cases.
Codex P2: resolveWorkspaceTabLabel treated any workspace whose title equals
"Workspace" as an untouched default and replaced it with a host label — so a
workspace a user renamed to "Workspace" (or a snippet named "Workspace")
lost its chosen name.
Add an explicit Workspace.autoTitle flag. It is authoritative when present:
- set false on user rename, snippet-created, named "New Workspace", and any
createWorkspaceFromTargets call given an explicit name;
- absent/true means the tab may derive a host label.
Legacy workspaces without the flag fall back to the old default-title string
check, so already-open default splits still get host labels and previously
renamed workspaces keep their names.
- domain/models/workspace.ts: add autoTitle field.
- domain/workspace.ts: thread autoTitle through createWorkspaceFromSessionIds.
- application/state/useSessionState.ts: mark rename / snippet / named
creation paths as explicit; createWorkspaceFromTargets takes an optional
name and only marks custom when one is given.
- domain/sessionTabTitle.ts: derive only when autoTitle (with legacy string
fallback); tests for explicit "Workspace" name and explicit auto-title.
Third Codex P2: submitSessionRename rewrites BOTH customName and hostLabel
(useSessionState.ts:585,597), so counting distinct hosts by hostLabel still
mislabeled a renamed same-host pane as a separate host and added a spurious
"+1".
Count by session.hostId instead — the only identity a rename leaves
untouched. All local terminals share the stable LOCAL_TERMINAL_HOST_ID and
remote sessions carry their host config id, so same-host panes collapse to
one regardless of renames. The focused pane still shows its rename as the
primary label. Tests updated to carry hostId (including a rename that
rewrites customName + hostLabel but keeps hostId).
Follow-up Codex P2: the "+N" distinct-host set used getSessionConnectionLabel
(customName || hostLabel), so two panes on the same host where one session
was renamed produced different keys and a spurious "+1", presenting the
workspace as spanning multiple hosts.
Count distinct hosts by hostLabel alone. The focused pane still shows its
rename as the primary label; only the host count ignores per-session
renames. Adds coverage for the renamed-same-host and rename-with-multi-host
cases.
Review feedback (Codex P2 + Copilot): resolveWorkspaceTabLabel derived
its label and its distinct-host "+N" count from resolveSessionTabTitle,
which folds in the dynamic shell/agent title. Two problems:
- those live titles are published only to the session presentation store,
not the plain session records passed to the tab, so the label went stale
under dynamicTabTitleMode 'agent'/'all';
- per-pane dynamic titles differ on a single host, so the "+N" suffix
fired even when every pane was on the same host.
Use getSessionConnectionLabel (rename / host label) — the stable host
identity — for both the primary label and the distinct-host set. This is
also what the tab is meant to convey (which host), and it needs no
presentation-store subscription. Drops the now-unused dynamicTabTitleMode
argument from resolveWorkspaceTabLabel.
A tab holding a split workspace showed the generic default title
"Workspace", giving no hint which host it belonged to. Derive the tab
label from the workspace's focused session when the workspace still
carries the default title: a local shell reads as its host label
(e.g. "Localhost"), an SSH session as its configured host name. If the
workspace spans more than one distinct host, the focused host is shown
with a "+N" suffix. A user-renamed workspace keeps its custom title.
- domain/sessionTabTitle.ts: add DEFAULT_WORKSPACE_TITLE sentinel and
resolveWorkspaceTabLabel(), reusing resolveSessionTabTitle so labels
match what single-pane tabs already show.
- domain/workspace.ts, application/state/useSessionState.ts: reference the
shared DEFAULT_WORKSPACE_TITLE constant for the default title.
- components/top-tabs/TopTabItems.tsx: render the derived label (with a
title tooltip) instead of workspace.title.
- domain/sessionTabTitle.test.ts: cover rename, single/multi-host, empty.
Copilot review caught that the new showContextMenuOverFullscreenApps flag
only affected the TerminalContextMenu suppression helpers, but the
capture-phase contextmenu interceptor in useTerminalEffects
(shouldInterceptMouseTrackingContextMenu) still called preventDefault +
stopImmediatePropagation on every non-Shift right-click while mouse
tracking was active. That killed the contextmenu event before it could
reach Radix, so the menu never opened even with the setting on.
Route an unmodified right-click through the same path as Shift+right-click
when the setting is on and rightClickBehavior is 'context-menu':
- shouldInterceptMouseTrackingContextMenu returns false (let contextmenu
reach Radix so the app menu opens)
- shouldStopShiftRightClickMouseTrackingMouseDown returns true (stop the
button press from reaching the TUI, so tmux/vim don't also react)
The setting is menu-only: paste / select-word right-click behaviors are
unchanged. Adds predicate coverage in middleClickBehavior.test.ts.
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
Chinese glyphs (PingFang etc.) paint outside a 1.0 em box, so leading-none
plus truncate was slicing vault tree, tabs, and dialog titles. Use safer
line-heights and slightly taller note title inputs.
The title-bar terminal icon only appeared on Vault/SFTP and vanished on
terminal surfaces with the host tree open. Drop the redundant control;
local shell remains on the host-tree toolbar and quick switcher.
Finish-path metadata checks treated mtime/ctime bumps as hard source
changes. On macOS, xattr/Spotlight can move ctime without rewriting
bytes, so repeated pause/resume often failed near 100%. Prefer digest
verification for uploads; keep strict timestamps for downloads.
After soft-resume, main may advance lifecycleEpoch past the worker-local
value. Progress fanout now stamps max(main, worker) so the transfer center
does not reject live progress as stale.
In worker-backed mode soft-resume must stamp the normalized main epoch
(broadcast by nextWorkerLifecycleEpoch), not the worker-local value, so
later progress is not rejected as stale.
Foreground Resume no longer joins the multi-minute background wait promise;
each caller waits with its own maxWaitMs, and only the final truncate is
single-flight. Failed settle attempts are not sticky on later Resume clicks.
Codex P1s: exclusive single-flight deferred truncate so background settle
cannot race resume unpause, and soft resume waits for/uses the pause-time
full SHA-256 fingerprint when available before unpausing.
- Soft resume identity: size + mtime (+ local head sample), not size-only,
so same-size in-place rewrites are rejected without full-file SHA-256.
- resumeQueuedTransfer returns lifecycleEpoch so soft-resume does not invent
a higher epoch and drop live progress.
- Ignore left-/right- SFTP pane connection ids as openSftpForSession targets.
- Pass resumeHosts (vault + ephemeral) separately from vault hosts used by tray.
Pause then resume was flaky: soft resume cleared lifecycle epochs so stale
paused events re-applied, soft failures always rehomed to dedicated-resume
(panel queue vanished, "reconnecting" then stuck paused), and concurrent
range soft-drain only waited 1.5s before "still finishing".
Soft resume now stamps bridge lifecycle epochs, only hard-reconnects when
the live stream is gone, dedicated resume reuses terminal/parked transports
(including ephemeral hosts), and deferred sparse settle runs in the
background with a longer resume wait for in-flight ranges.
Call the existing focus primitive directly on tab switch (no outer rAF
wrap). Resolve workspace Detach Session labels via per-session
usePresentedSession so dynamic titles stay live without remapping the
whole TopTabs bar.
Stop TerminalLayerWorkspaceSection from falling back to memoized ctx when
sidePanelLiveStore clears activeWorkspace (workspace → solo tab). Refuse
session-restore active-tab patches without a trusted in-memory full payload
so a connect+activate race cannot write stale disk sessions under a new
activeTabId; force full rebuild from live state instead.
Stop AppView from re-subscribing to activeTabId so shell composition no
longer rebuilds on every top-tab click; move plugin keybindings and host
editor surface visibility into leaf subscribers. Patch session-restore
activeTabId without re-serializing every session on tab switches, defer
chrome theme CSS work with fingerprint short-circuit + rAF, let terminal
workspace chrome read active workspace from the live store, apply TopTabs
presentation per tab, and keep hibernate wake chunked/idle with leaner
focus scheduling.
Encode null vs missing presentation fields for useSyncExternalStore, and
compare AI history list title chrome so renames refresh without stream thrash.
Skip clipboard writes on selection reattach, bound xterm wait polling, and
detect AI session create/delete for history lists without sibling stream thrash.
- Store null presentation tombstones so clears override stale snapshots
- Compare selected AI session identity for cross-scope history resumes
- Attach selection overlay after xterm runtime exists; skip copy-on-select
while preserveSelectionOnInput restores the selection
- Align system-sidebar hibernation source contract with resolvedSession
updateHosts now reuses unchanged host object refs instead of re-sanitizing the
whole vault, and terminal host patches use a functional update. Line-timestamp
toolbar toggles optimistically so the gutter paints before vault write work.
Auto-focus no longer claims keyboard focus when the caret is in the AI/side
panel. Selection overlay and copy-on-select move into TerminalSelectionAIOverlay
so clearing selection (e.g. focusing the AI input) does not re-render the whole
Terminal tree.
App/TopTabs consume useEditorTabChromeList so content keystrokes no longer
rebuild shell domains; dirty dots use useEditorTabDirty. AI side panels keep
the full sessions list for fuzzy history but areEqual only on exact-scope
session object refs so sibling stream ticks skip retained panels.
Stop appTerminalDomain from depending on the whole settings bag and stabilize
hotkey/capture callbacks via refs so opacity/settings/log writes do not rebuild
the terminal domain. Gate hidden History shellHistory subscriptions, use a
stable empty quick-switcher list, and move TopTabs activity dots onto per-tab
store snapshots so sibling activity no longer re-renders the whole tab strip.
useSyncExternalStore now reads a per-session snapshot string so sibling title
updates notify the store but do not re-render unaffected TerminalPane instances.
Merge main into the PR branch and keep the top-tabs local terminal control as a
fallback when the host-tree toolbar is disabled, collapsed, or not on a work
surface (Codex review).
Use the shared show/collapse/hide layout for the host-list toolbar, drop the
redundant top-tabs local-terminal button, and switch the local shell control to
the borderless Terminal glyph as the primary one-click entry.
Address Codex P2/P3 on #2628: feed live titles/providers to TerminalFocusSidebar
and TerminalPane chrome via sessionPresentationStore, and keep aiPanelContextsEqual
on domain-local types so domain does not import UI modules.
useThemeRuntime now returns a memoized bag with setters-stable callbacks, and
App domain memos depend on clear/pick/resolve members instead of the whole
runtime object so unrelated App re-renders no longer rebuild appTerminalDomain.
Split the main-window mega bag into domain-scoped AppView slices so Vault,
terminal, chrome, dialogs, and mounts only re-render when their slice identity
changes. Drive tab titles/coding-CLI icons through sessionPresentationStore
(store-only live updates + orphan map overlay), retain presentation-stable
sessions for domain memos, stabilize AI panel context Maps with structural
equal/retain, and keep shell history on shellHistoryStore so Vault host lists
ignore history-only churn.
- Memo side-panel chrome on stable equal; read live theme when shell is open
- Theme/System live subscribe only when those panels need it; scripts runs
subscribe only while Scripts is visible
- Treat workspaceById focus-only identity as stable for side panels
- Include knownHosts / add-known-host / transfer-count handlers in stable keys
- setScriptRuns publishes scriptRunsStore so automation has a single source
Address correctness and residual jank found in review:
- Expand pane session equality for connect/startup fields (pendingInitialCwd,
startupCommand, pending scripts, serial/local shell, etc.).
- Cache coding-CLI provider in the signal controller so memo-skipped
TerminalLayer renders cannot clear titles via a stale sessionsRef.
- Heal terminalCwdStore drift when the renderer ref already has the path.
- Publish shell history synchronously on write/load and via useLayoutEffect.
- Move script runs to scriptRunsStore; Scripts slot subscribes so automation
ticks no longer re-render TerminalLayerInner.
- TerminalLayerView equal uses stable side-panel keys only (cwd/theme live
via sidePanelLiveStore, not chrome re-renders).
- Compare terminalFontFamilyId / session log props; stabilize HostTreeView
callbacks and end-session-drag handler.
Decouple high-frequency UI state from TerminalLayer's memo boundary so
npm run dev is less dominated by full-layer reconciliation.
- Publish shell history through shellHistoryStore; History side panel
subscribes directly instead of receiving shellHistory props.
- Track terminal CWD in terminalCwdStore so OSC 7 updates do not setState
TerminalLayerInner; TabBridge updates side-panel live snapshots only.
- Move remote-history ownership into the History side-panel slot.
- Treat sessions as equal for pane/layer memo when only dynamicTitle or
codingCliProviderId changes (TopTabs still sees full session updates).
- Stabilize AppView terminal handlers; thin AI vault catalog props when
the AI panel is not visible; memo HostTreeView.
Keep retained AI panels mounted for session continuity, but skip
ChatMessageList/ChatInput while CSS-hidden so tab switches do not
reconcile Streamdown trees. Render assistant text as plain content
during streaming, lazy-load Streamdown only after the turn settles,
and split message layout from the markdown chunk for a lighter open path.
Bump @xterm/* betas so shared-atlas clear (#6055), no-mipmap upload
(#5987), and capacity eviction (#6043) ship in addon-webgl. Replace the
local isolation/backport postinstall mutator with a verify-only gate.
Default new-terminal scrollback to 3000 (closer to VS Code's 1000).
The post-install userns probe runs as root under dpkg/rpm, and root can
create a user namespace even when unprivileged userns is restricted (e.g.
Ubuntu 23.10+ with apparmor_restrict_unprivileged_userns=1). The probe
therefore always passes at install time and leaves chrome-sandbox at 0755,
so on machines where the app itself cannot use the userns sandbox Chromium
aborts: 'The SUID sandbox helper binary was found, but is not configured
correctly ... must be owned by root and have mode 4755'.
Set 4755 unconditionally (the historical electron/chrome default) so the
SUID sandbox works as a fallback whenever the userns + AppArmor path fails.
Refs: binaricat/Netcatty#2607
A cloned SSH session whose first connection fails before the inherited
cd is consumed lost the directory on manual retry: startReconnect(manual)
calls prepareRestoredReconnect(), which clears restoreCwdIntentRef for
non-restored sessions, and the initial-cwd prep only ran at runtime boot.
Re-run prepareInitialCwdIntent() in the manual branch; it no-ops once the
cwd was consumed or when there is no pending inherited cwd.
- hostById is a Map, not a function: my previous commit called it as
hostById(id), throwing TypeError on every Copy Tab / Split. Use
hostById.get(id), and fall back to terminalHosts for ephemeral hosts absent
from the saved-host map (P1)
- classify the DETECTED host.distro (not getEffectiveHostDistro, which returns
a cosmetic manual override) when deciding the network-device probe gate, so a
pinned Linux icon can't re-enable the exec channel on a Huawei/Cisco host (P1)
- clear pendingInitialCwd via app state when the inherited cd is consumed
(report the new cwd through onTerminalCwdChange), so shells that never emit
OSC 7 don't keep the transient seed and re-inject a stale dir on remount (P2)
Adds regression tests: Map-based hostById, network-device gate (deviceType,
detected distro, ephemeral terminalHosts).
- Local clones now inherit the live tracked cwd: expose getSessionRestoreCwd
(the OSC 7 cwd map, which is never mirrored onto session.lastCwd) and feed it
into captureInheritedCwd as the primary source, so Copy Tab / Split of a
running local terminal open in its current dir, not the launch dir (P1)
- Gate the SSH /proc probe like the app's own cwd-probe path: never probe
network devices (Huawei VRP etc. can drop the session on the extra exec
channel) and consult the SSH banner for unclassified hosts (P1)
- Mark the inherited cd consumed only when it is actually sent (from the
restore-cwd consumption callback), so a failed first connect can re-arm the
intent on retry instead of starting in the login dir (P2)
- captureInheritedCwd: race the SSH cwd probe against a 1.5s timeout so a
slow/wedged connection can't block clone/split tab creation; fall back to
lastCwd on timeout/failure
- clone factory: only set pendingInitialCwd for inject-eligible remotes
(ssh/undefined, non-mosh/et), not telnet/serial/mosh/et where it would be
dead, never-cleared state
- AppView: pass a stable useCallback for the new-local-terminal icon instead
of an inline arrow that defeated the TopTabs memo comparison
Reconnect can replace browse connection ids before save completes, so close
handlers that only matched sessionId skipped dirty editors and tore down their
writer. Remap as soon as reconnect is detected and match owners by sftpTabId.
Co-authored-by: Cursor <cursoragent@cursor.com>
- captureInheritedCwd: probe live SSH cwd before stale lastCwd snapshot (Codex P2)
- new-local-terminal icon routes through handleCreateLocalTerminal so sessions get a ConnectionLog + output capture (Codex P2)
- use single-quote import in AppHandlers (Copilot)
Reconnect during save changes the browse connection id before remapSessionId
runs, so session-id-only ownership briefly reports no owned editor and parks
the channel mid-write. Match promoted editors by sftpTabId as well.
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-resolve the pinned pane after ensureRemoteSftpId so editorTabSave
remaps session ownership to the regenerated connection id.
Co-authored-by: Cursor <cursoragent@cursor.com>
Track sftpTabId on editor tabs, remap sessionId after reconnect, and
subscribe SFTP side panels to editor presence only so typing does not
rerender every mounted panel.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop hostId-based pane fallback so non-owning SFTP writers keep throwing
"connection is no longer available" and editorSftpBridge can dispatch to the
correct owner when multiple useSftpState instances are mounted.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep browse sessions alive when promoting the built-in editor to a tab, and
reconnect the SFTP session on save when the browse mapping is missing.
Closes#2576
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(terminal): suggest Network Device Mode via a session-header tip
When a connected host is classified as a network device (auto-detected
vendor banner/os-release, or a manual distro-icon override) but Network
Device Mode is off, show a non-blocking one-line tip at the top of the
session. The user can enable the mode (persisted to the host) or dismiss
it; we only suggest once per host.
Driven by the host's effective distro so it also previews without real
hardware: set a host's device icon to a network vendor and reconnect.
- domain: shouldSuggestNetworkDeviceMode (exact vendor match, once-per-host)
- application/state: networkDeviceModeSuggestion persistence boundary
- TerminalView: header tip + reserve top offset so it never covers output
- i18n: terminal.networkDevice.tip.* for en/zh-CN/zh-TW/ru
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): gate network device mode tip to plain SSH sessions
Codex review: the suggestion tip used the host's effective distro (incl.
manual icon override), so a Mosh/ET/serial/telnet session with a
network-vendor classification would still offer it and persist a
host-level deviceType:'network'. The Host Details toggle is exposed only
for plain SSH hosts, so this could set a hidden mode that surprises later
SSH reconnects. Add an effectiveProtocol gate to shouldSuggestNetworkDeviceMode
and pass effectiveTerminalProtocol from TerminalView. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): make network device tip gate effective and popup/multi-pane safe
Addresses Codex re-review on the tip:
- The effectiveProtocol gate was dead: ctx never carried
effectiveTerminalProtocol, so it was always undefined (unrestricted).
Derive it from the host via resolveEffectiveTerminalProtocol instead.
- Suppress the tip when host updates are unavailable (detached popup has no
onUpdateHost) so Enable can't silently no-op; plumb canUpdateHost through ctx.
- Sync dismiss/enable across panes/windows for the same host via an in-process
subscription in the suggestion persistence module. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): make network device tip once-per-host, ephemeral- and multi-window-safe
Addresses Codex re-review round 3 on the tip:
- Exclude ephemeral deep-link/quick-connect hosts (isSavedVaultHost): they are
not in the persisted vault array, so Enable would silently no-op even though
canUpdateHost is true.
- Track first display separately from user interaction: record "shown" silently
on first display so simply closing the session no longer re-nags on every
reconnect, while the showing instance keeps the tip until the user acts.
- Propagate resolve/dismiss across separate BrowserWindows (#/session-window)
via a native storage-event listener that diffs newly handled host ids.
(#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): don't touch localStorage at import; keep network tip clear of speed dial
Addresses Codex re-review round 4:
- P1: networkDeviceModeSuggestion read localStorage at module scope
(knownHandledIds initializer), which threw ReferenceError under the Node
test runner and broke `npm test`. Initialize it lazily inside the existing
browser-only window guard instead.
- P2: when the host info bar is hidden and search is closed the full-width tip
strip overlapped the compact speed-dial toggle (top-right) and swallowed its
clicks. Reserve right-side clearance for the toggle via a pure
resolveNetworkDeviceTipRightInset helper (with unit test).
(#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): drop network tip once the host is classified elsewhere
Addresses Codex re-review round 5: while the tip is latched open, enabling
Network Device Mode from Host Details or a synced pane updates the host to
deviceType:'network', but the eligibility effect's early return kept the tip
(and its now-redundant Enable action) visible. Clear the latch whenever
host.deviceType becomes 'network'. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): persist only the deviceType override, not the effective host
Addresses Codex re-review round 5 (P1): the tip's Enable spread the *effective*
session host (group defaults / proxy profile already materialized) into
onUpdateHost, so mergeTerminalHostUpdate froze every inherited field
(credentials, appearance, proxyConfig) as host-level overrides and later
group/profile changes stopped propagating. Send a sparse
{ id, deviceType: 'network' } update instead. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(terminal): move network device tip lifecycle into a state hook; per-host keys
Addresses Codex re-review round 6:
- P1 (architecture): TerminalView no longer imports the persistence API or owns
the handled-state subscription/eligibility/writes. Extract
useNetworkDeviceModeSuggestion under application/state; the component consumes
{ visible, enable, dismiss } and keeps only view glue (sparse host update +
toast), per AGENTS.md's layer boundaries.
- P2 (cross-window clobber): persist one key per host instead of a single shared
array. localStorage has no atomic read-modify-write, so concurrent appends
from two windows could drop a marker; independent keys never clobber, and the
storage event recovers the host id directly from the key.
(#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Main-process progress IPC was ~10 Hz and each tick remapped the full
transfer list plus published the global center, pegging the main window
renderer at 100%+ CPU during large copies. Raise IPC throttle to 250ms/1MB
and coalesce progress-only React/store patches to one paint per frame.
Restart resume and bulk dedicated opens were still calling openSftp, which
silently borrowed a parked/shared terminal transport. That reattached
checkpoint resume to a lease-bound conn instead of an independent dial.
Dedicated transfer sessions now pass reuseTransport:false so they always
open a fresh vault SSH connection.
Remove ensureTransportShape upgrade of bare { count, conn } descriptors.
Borrow requires a registry transport from createConnectionRef/createTransport.
Update tests to construct real transports instead of hand-rolled count objects.
acquireConnectionRef / borrowTransport now ensure leases Map and registry
fields exist on pre-registry { count, conn } descriptors so older tests and
in-memory sessions do not throw on transport.leases.has.
Password-only automatic login now digests the password so vault rotation
invalidates parked reuse, while multi-material auto keeps publicKey-based
matching so key-first transfer opens still find the shared transport.
Auth material fingerprints are method-scoped so key-first / password-retry
SFTP opens still match a parked multi-material transport. Password rotation
invalidates password auth; key/cert content rotation invalidates those methods.
Digest password/privateKey/certificate/passphrase/identity paths into the
auth fingerprint so key or password rotation under the same keyId invalidates
parked transports. Secrets are not retained on the transport registry — only
the digest is indexed.
Bind-failure cleanup must remove close/error handlers while conn is still
attached; releasing first cleared conn and left stale listeners on the
shared transport after local bind errors.
Add resolveSftpTransferSourceSessionId that walks all reusable terminal
sessions instead of the picker one-per-hostId list, so transfers can still
borrow a live transport when another tab for the same hostId has a different
endpoint.
Dedicated dials that registered a transport lease were leaving the lease
held after local/dynamic server errors. Always release managed leases so
authenticated connections are not orphaned until quit.
Record remote bind target before forwardIn so cancel-during-listen always
unforwards (or discards). Shared PF transport errors discard instead of park;
identical idle TTL re-sends no longer reset idle deadlines.
Remote unforward callback errors now discard the shared transport like
timeouts, so a half-open remote listen cannot be idle-parked. Shared
port-forward reuse attaches close/error lifecycle before bind so mid-listen
transport drops cannot leave zombie local listeners.
Shell and parked-shell lookup now use endpointAllowsReuse(..., "shell") so
disabling ForwardAgent cannot reattach to a warm conn that still exposes the
local agent. SFTP/port-forward keep asymmetric channel reuse so they can still
borrow a ForwardAgent-enabled terminal transport.
Store agentForwarding on the session/transport endpoint and apply an
asymmetric reuse check: opens that need ForwardAgent reject parked conns
that never enabled it, while SFTP/port-forward can still borrow a
ForwardAgent-enabled terminal transport.
Transfer pool source sessions now require hostname/user/port parity so
multi-tab or overridden endpoints do not borrow the wrong shared transport.
Remote forward cancel-during-forwardIn and stop paths share one timed
unforward helper that discards the transport when the server listen stalls.
Avoid shell/sftp lease id collisions across reconnect generations, and
discard the shared transport when remote unforward cannot be confirmed
within the stop timeout.
If the user stops a shared local/dynamic/remote forward while listen or
forwardIn is still pending, close the listener and resolve cancelled
instead of re-registering the tunnel as active.
Track accepted forward sockets per tunnel and destroy them on stop, end
already-dead transports instead of parking them, and fingerprint
command-proxy routes so edits invalidate warm reuse.
Catch transport registration failures inside async bind callbacks so a
dead shared socket cannot crash the main process, and fingerprint jump
hops by both vault id and effective endpoint/proxy.
Include proxy/jump profile identity in transport keys, normalize loopback
remote-forward addresses, detach shared listeners on cancel, and avoid
MFA-retrying dedicated dials for local bind failures.
Attach error/close lifecycle to reused port-forward transports, match
wildcard remote binds by expected listen address, and end unusable
transports whose sockets are destroyed during lookup.
Scope transport keys by vault hostId so different profiles cannot share
an authenticated connection. Fix createConnectionRef to preserve hostId.
Route remote-forward accepts by destPort/bind address, await unforwardIn
before success, return stopAllPortForwards as a promise, and update
async cancel tests.
Introduce a main-process SSH transport registry (borrow/return leases,
ControlPersist-style idle park) shared by shell, SFTP, transfer, and
port-forward. Transfer pool becomes channel concurrency only; remove the
separate transfer-pool idle TTL UI in favor of a single SSH keep-alive
setting. Register dedicated SFTP dials, reuse parked transports, and fix
TTL 0 (park until quit), jump-host endpoint keys, and remote unforwardIn.
* fix(terminal): suppress autocomplete popup on alternate screen (#2530)
Codex CLI's TUI (and other full-screen apps like vim/htop/less) render
their own input UI on the alternate screen buffer. Netcatty's autocomplete
only gated on its shell-prompt heuristic, which codex's composer line
matches — so typing "/" inside codex popped up Netcatty's path/command
list on top of codex's slash-command menu.
Bail out of fetchSuggestions (the single query path for both popup and
ghost text) when term.buffer.active is the alternate buffer. Reuses the
existing isTerminalAlternateScreenActive helper already used by hibernate.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(terminal): keep simple alt-screen autocomplete ban (#2530)
Drop the bloated TUI-vs-multiplexer heuristics from the codex auto-fix loop.
Disable autocomplete unconditionally on the alternate screen buffer — same
simple tradeoff as other hosts; tmux/screen attach has no Netcatty completions.
* fix(terminal): drop alt-screen completions after async lookup too
Same unconditional policy as the entry gate: if the buffer switched to
alternate while getCompletions was in flight, discard results so popup/ghost
cannot paint over a TUI. #2530
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(ai): route auto-detected network devices to raw PTY exec (#2367)
AI exec paths (Catty, worker, MCP) decide whether to send commands raw
(no shell wrapping) based on meta.deviceType === "network". That field
only reflected the manual Network Device Mode toggle (host.deviceType),
so auto-detected switches (Huawei VRP, Cisco IOS, …) still went through
the POSIX shell wrapper. Their vendor CLIs reject the wrapped command,
the start marker never arrives, and the call stalls until the startup
timeout.
Report deviceType: "network" when the detected distro/vendor classifies
as a network device too, mirroring the terminal's own gating. This also
gives the AI system prompt vendor-CLI guidance before the user manually
flips the toggle. Mosh/ET sessions stay suppressed since they require a
shell-backed PTY.
Extract the pure buildAITerminalSessionInfo helper into its own module
for testability and add unit coverage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(ai): guard network-device classification against substring false positives
Address Codex review: replace non-ASCII ellipsis with ASCII per the
ASCII-only convention, and add a regression test proving classification
is exact-match against the vendor id list (so a custom distro that merely
embeds "cisco"/"huawei" is not treated as a network device).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* perf(vault): virtualize host pickers for large inventories
Virtualize Select Host, Quick Switcher, SFTP host picker, and workspace
dialogs so thousands of hosts only mount a viewport window. Shared scroll
and keyboard clamp helpers keep selection and filter-while-scrolled paths
stable under virtualization.
* fix: address Codex review on PR #2553
* fix(vault): plugin row heights and Select Host keyboard types
Give Quick Switcher plugin command/view rows a taller virtual slot so
two-line chrome does not overflow into the next absolute row. Narrow
Select Host navigable rows to group|host so Enter keyboard activation
typechecks cleanly.
* fix(vault): finish host-picker review feedback
Clip and truncate fixed-height Quick Switcher / workspace rows so long
labels never overlap the next virtual slot. Wire Select Host listbox
aria-activedescendant to stable option ids for keyboard AT feedback.
* fix: address Codex review on PR #2553
* fix: address Codex review on PR #2553
* fix: address Codex review on PR #2553
* fix: address Codex review on PR #2553
* fix(vault): clear remaining host-picker codex findings
Declare multi-select listboxes, drop empty SFTP Hosts headers when only
connected rows remain, size the SFTP list to content under a 360px cap,
and give Quick Switcher / workspace pickers a definite max scroll height
so large inventories remain reachable.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Packaged Netcatty launched from Finder often has process.cwd() === "/", so
merging .cursor/mcp.json failed silently and netcatty-remote-hosts never
mounted. Point CLI turns at Netcatty temp and fail loudly on merge errors.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: switch .gitignore to allowlist layout
Default-deny root paths so a mistaken git add cannot pick up secrets,
agent state, or build artifacts. Re-include only known source paths and
keep the existing classical denylist for nested outputs.
Closes#2534
* fix: address Codex review on PR #2554
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* feat(snippets): open quick add/edit as a centered modal
Replace the scripts side-panel AsidePanel drawer with a Dialog so
snippet create/edit no longer slides over the terminal workspace, while
keeping the same form fields and shortkey capture behavior.
* fix: address Codex review on PR #2552
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* feat(vault): optimize large imports and vault rendering (#2526)
* fix(vault): address import review findings
* fix(vault): preserve filtered selection and import consistency
* fix(vault): complete import review fixes
* fix(vault): make import cancellation concurrency-safe
* fix(vault): preserve concurrent edits during import rollback
* fix(vault): handle concurrent saves and reset import target
* fix(vault): drop empty source groups after targeted import
* fix(vault): preserve sidebar accessibility check
* fix(vault): correct group counts and selection semantics
* fix(vault): preserve imports across concurrent saves
* fix(vault): preserve concurrent group changes during import
* fix(vault): close concurrent import edge cases
* fix(vault): retry rollback and enable tree keyboard selection
* fix(vault): retain concurrent sources and visible selections
* fix(vault): merge imported sources with persisted state
* fix(vault): serialize duplicate managed imports
* fix(vault): keep imports non-blocking and grouped views responsive
* fix(vault): preserve concurrent managed sessions
* fix(vault): preserve concurrent groups and keyboard access
* fix(vault): serialize cross-window import commits
* fix(vault): make import commits atomic
* fix(vault): surface import rollback failures
* fix(vault): recover import transactions safely
* fix(vault): commit imported hosts atomically
* fix(vault): preserve unfinished import recovery
* fix(vault): wait for edits and preserve keyboard focus
* fix(vault): protect group settings and lock fallback
* fix(vault): keep virtual focus aligned
* fix(vault): complete keyboard navigation semantics
* fix(vault): finish keyboard navigation
* fix(vault): close large-vault concurrency gaps
* fix(vault): finish large-vault interaction safety
* fix(vault): harden filtered large-vault flows
* fix(vault): keep managed deletion atomic
* fix(vault): coordinate managed file deletion
* fix(vault): close cross-window managed races
* fix(vault): refresh persisted managed state
* fix(vault): preserve distinct imported sessions
* fix(vault): keep failed progress accurate
* fix(vault): keep search tree collapsible
* fix(vault): close final review gaps
* fix(vault): reveal filtered tree matches
* fix(vault): close final review edge cases
* fix(vault): preserve concurrent import changes
* fix(vault): serialize all vault saves under one cross-window lock
Hold the shared import lock for ordinary host/group/key writes and the
full import commit path so check-then-write can no longer interleave
across windows. Make the lock re-entrant in-window to avoid nested
deadlocks, and announce large tree item positions for screen readers.
* fix(vault): keep concurrent saves serialized and fix tree ARIA siblings
Stop treating same-window concurrent work as nested lock re-entry; nested
helpers skip re-acquire only when the outer critical section already owns
the lock. Announce treeitem position among true siblings at each level.
* fix(vault): require explicit lock handle for nested saves
Independent concurrent vault writes always queue on the shared lock.
Nested import/deletion helpers continue only when they pass the active
lock handle from their outer critical section.
* fix(vault): avoid lock deadlocks with encrypt-then-write saves
Encrypt outside the shared lock and only acquire it for the final write so
importers waiting on pending work cannot deadlock on lock acquisition.
Retry plugin commits outside the lock when concurrent edits intervene, and
rebase imports onto in-memory host edits when disk is still stale.
* fix(vault): release import lock before concurrent-edit retries
Drain encrypt work under the lock and full disk writes only outside it.
When an import or group deletion is superseded by a concurrent edit, release
the shared lock so that edit can finish, then retry. Functional group updates
now base on the latest in-memory snapshot while a write is still pending.
* fix(vault): tidy import dialog destination and SecureCRT prompts
Move import destination settings behind a bottom-left control with its own
step, and keep SecureCRT guidance in a dedicated chooser instead of inside
the format tile.
* test(vault): fix import dialog layout guard test under ESM
* fix(vault): simplify import destination footer control
Replace the heavy two-line destination card with a single quiet text link
that shows the current placement choice.
* fix(vault): simplify import destination settings page
Drop the nested card and duplicate headings. Show only the three placement
choices, finish immediately for preserve, and keep Done only when a group
name is needed.
* fix(vault): simplify SecureCRT chooser and iconify destination options
Remove redundant SecureCRT prompt copy while keeping the two source cards
with icons. Add small folder icons to the import-location choices so that
page is less plain.
* fix(vault): use SecureCRT logo in import chooser header
* fix(vault): drop border around SecureCRT import logo
* fix(vault): clarify import location footer with icon and label
Show a folder icon plus "Import location · current choice" so the control
is recognizable without becoming a heavy card again.
* fix(vault): remove border from import progress status icon
* fix(terminal): keep Enter reconnect working after disconnect
Disconnected sessions advertised "Press Enter to reconnect" but often
swallowed the key when focus left xterm (body blur or dialog controls),
and an open terminal search bar globally hid the hint and disabled the
shortcut. Give the disconnected overlay a focus sink + local Enter
handler, and stop treating search-open as a global reconnect gate while
still leaving real buttons/inputs alone.
Closes#2544Closes#2546
* fix(terminal): stop disconnected dialog focus theft
Only claim Enter-reconnect focus when it is already lost or still inside
this session tree, and restore xterm focus when the overlay unmounts.
Avoid stealing focus from other panes/side panels on disconnect.
* fix(terminal): restore dialog focus only when overlay leaves
Split claim/restore effects so showLogs/error rerenders no longer
yank focus off dialog buttons onto xterm mid-interaction.
* fix(terminal): restore Enter-reconnect focus in popup terminals
Popup Terminal windows lack data-session-id ancestors. Walk up from
the dialog to the local xterm root so claim/restore still work there.
* fix: address Codex review on PR #2550
* fix: address Codex review on PR #2550
* fix: address Codex review on PR #2550
* fix: address Codex review on PR #2550
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
- Remove sdkRequestRuntimes entries on chat session teardown to prevent
stale runtime references accumulating across sessions.
- Cancel pending elications (resolve with deny) when the owning chat
session closes, so elicitations never hang indefinitely.
- Use crypto.randomUUID() as fallback elicitation id when the SDK does
not provide one, avoiding collisions on empty-string keys.
- Surface interrupt failures via console.debug for easier diagnostics
without changing best-effort stop behavior.
Behavior preserved:
- Session key fingerprinting (config-based reuse) unchanged.
- Stop/interrupt flow: signal-first, then queryRef.interrupt().
- Permission mode routing (auto/confirm/observer) unchanged.
- Stream handler event protocol and dedup logic unchanged.
- Upgrade @tencent-ai/agent-sdk to 0.3.230 (elicitation support)
- Add CodebuddyElicitationCard with schema-driven form fields and
accept/decline/cancel actions; wire full event chain from driver
through shared elicitation store to ChatMessageList
- Pass agents/thinking/effort to V2 SessionOptions instead of forcing
query() fallback; include them in the session fingerprint
- Close stale V2 session before query() fallback to prevent context
divergence when resuming the same persisted conversation
- Forward codebuddyOptions from renderer config through IPC to the
main-process driver context
- Add i18n strings (en/ru/zh-CN/zh-TW) and test coverage
- Fix test process hang: stale-session test no longer triggers real SDK
import which spawned a live CodeBuddy backend and prevented exit
- Fix plugin management API: remove non-existent removePlugin, correct
enablePlugin/disablePlugin second param to marketplace string
- Wire elicitation handler in production path via session manager's
pending map; expose elicitation-response IPC to renderer
- Skip V2 Session when query-only options are configured (maxBudgetUsd,
sandbox, fallbackModel, enableFileCheckpointing, agents, outputFormat,
effort, thinking) so settings are not silently ignored
- Expose plugin/mcp-status/account-info IPC channels in preload + types
- Add renderer handling for hook, elicitation-create/complete events
OpenSSH keeps the first HostName value. Setting HostName to the jump
alias under the forced -F session config blocked later Include of
~/.ssh/config from resolving HostName aliases, and could diverge from
the vault snapshot built via ssh -G against the real config.
Drop the process-lifetime ssh -G cache so HostName/HostKeyAlias and
known_hosts path edits are observed on the next connect. Keep a
per-prepare Map only so target and jump discovery in one session can
share probes.
Reset Host context with Match all so Included user/system ssh_config
applies to every hop, not only the trailing jump Host stanza. Prepend
the ssh -F wrapper onto the session PATH from options.env so custom
ProxyCommand helpers remain available.
-F replaces both the per-user and system-wide ssh_config. Append
Include directives for the real ~/.ssh/config and system ssh_config
after session Host blocks so HostName aliases and other user settings
still apply while session ProxyJump/vault overrides keep first-match
priority.
Avoid removing the current target's system known_hosts entry when the
vault only contains unrelated pins, so strict ET stats probes keep
working for already-trusted hosts.
When rewriting vault pins under a resolved HostName, retain
[host]:port encoding for non-default ports so OpenSSH lookup finds the
vault key. HostKeyAlias continues to use the bare default-port form.
OpenSSH ignores $HOME for user config. When ET generates a private
config for ProxyJump and jump host-key policy, put a PATH-fronted ssh
wrapper that always passes -F so interactive ET and follow-up probes
both honor the vault-authoritative Host stanzas.
When the connection target is an SSH-config alias, OpenSSH records and
looks up host keys under the resolved HostName. Rewrite the vault pin
and filter selectors accordingly so accept-new cannot ignore the vault.
OpenSSH ignores $HOME for user config on POSIX, so pass -F to
execOnEtSession so ProxyJump host-key policy is actually loaded. Cache
real ssh -G probes briefly to limit main-process blocking.
Disable KnownHostsCommand when enforcing vault authority so dynamic trust
cannot override vault pins. Rewrite multi-host known_hosts lines instead
of dropping them so unpinned hop patterns remain trusted.
ET --ssh-option applies only to the destination hop. Keep vault-authoritative
UserKnownHostsFile and StrictHostKeyChecking in the ProxyJump Host stanza so
the separate jump OpenSSH process still enforces key verification.
Apply vault-authoritative known_hosts through --ssh-option so OpenSSH
enforces it even when a temp-HOME Host config is not read. Reject
fingerprint-only publicKey tokens when building vault pins.
Write vault pins under the effective HostKeyAlias when present, filter
system entries for both alias and hostname, and invoke ssh -G with -p/-l
so %p/%r known_hosts path tokens expand correctly.
Build separate authoritative known_hosts files for target and jump hops
using each hop's hostname for ssh -G discovery. Reuse the same vault
authority builder for requireTrustedHost exec probes so system pins
cannot override vault keys on stats/shell follow-ups.
Apply vault-authoritative known_hosts only on hops the vault pins, so
unpinned ET jump/target hops keep the persistent user known_hosts.
Also merge ssh -G UserKnownHostsFile paths and preserve spaced path
names when discovering configured trust files.
Discover administrator-configured global known_hosts paths via ssh -G
when building the vault-authoritative snapshot, so replacing
GlobalKnownHostsFile does not drop custom admin pins or revocations.
Evaluate OpenSSH positive/negative host patterns when filtering system
known_hosts so vault-covered hosts do not drop jump-host pins that use
negation. For ET follow-up ssh, do not prepend accept-new when the
session already set StrictHostKeyChecking (including verifyHostKeys=no).
Only switch to a session known_hosts snapshot when the vault pins the
active target or jump host, so ET accept-new still persists first-seen
keys otherwise. Filter OpenSSH wildcard patterns covering vault hosts
and keep @revoked markers so admin revocations remain authoritative.
OpenSSH accepts a match from any known_hosts source, so a rotated system
pin could override a vault pin. Build an authoritative snapshot that
keeps vault keys exclusive for vault-covered hosts, force Mosh
StrictHostKeyChecking=ask, and quote trust-file paths that contain
whitespace.
Prefer waking the terminal runtime so executor encoding (including
bracketed-paste mode checks) is used, instead of inventing paste markers
on a blind backend fallback path.
Preserve OpenSSH default global known_hosts when injecting vault pins by
merging both into a single GlobalKnownHostsFile. When verifyHostKeys is
off, neutralize UserKnownHostsFile and GlobalKnownHostsFile with an empty
file so StrictHostKeyChecking=no cannot still block password auth against
a stale pin.
When multi-tab fan-out falls back to a direct backend write (hibernated
pane), wrap multi-line paste/noAutoRun payloads so newlines do not submit
as Enter.
Mosh and Eternal Terminal bootstrap via system OpenSSH, so Netcatty vault
known_hosts were ignored and a key rotated after a prior SSH connect could
still be accepted. Inject usable vault entries as GlobalKnownHostsFile and
honor verifyHostKeys=false so key-change interception matches SSH trust.
Closes#2501
Skip terminal focus steal during multi-tab snippet fan-out and restore the
original pane afterward. Trap Tab focus inside the quick-add drawer and
restore the opener element on close.
Restore modal dialog contract for quick-add (role/data-state/escape),
harden package path validation, skip sensitive sessions on multi-tab
snippet sends, and show a single run-on-all-tabs action for plain snippets.
Address #2502: bind shortkeys when quick-adding snippets, add package
creation via a split + menu, convert quick-add to a right-side drawer,
and let code snippets run on all workspace tabs from the context menu.
Capture tabId/connectionId at paste time and resolve external folder/entry
uploads against that pane after awaits so mid-transfer tab switches cannot
retarget remaining multi-folder items to another host. Prefer the pinned
pane host when reconnecting and reconnect by tab id without clobbering the
active tab's lastConnectedHost.
PR #2509's TransferRuntime unify accidentally regressed per-task external
upload cancel (#2495), clipboard confirm close-before-upload (#2478), and
main SftpView interactive browse sessions (#2498). Restore those behaviors
on top of the process-level control plane, keep Pausing copy, and gate
broadcastGlobalTransferEvent so bare Node unit tests do not download Electron.
When Observer → Confirm/Auto → Observer, drop the inactive ask/agent
in-memory session so switch-back cannot resume a stale thread and skip
the intervening conversation history.
Update Settings copy to use `cursor-agent login` and note that a saved API
key is retained while CLI login mode is active.
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-apply the stored key explicitly when toggling CLI login and API Key so
mode switches cannot drop a previously saved credential.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stop falling back to bare `agent` on PATH so other CLIs (for example Grok)
cannot be mistaken for Cursor during auth probes, model presets, or turns.
Co-authored-by: Cursor <cursoragent@cursor.com>
Skip duplicate stream-json assistant flushes that include model_call_id, and
isolate Cursor CLI resume sessions by ask vs agent so switching Observer to
Confirm/Auto starts a writable turn instead of keeping Ask.
Only automation bot PRs re-check source-issue pending comments before
mark-ready, so maintainer Fixes-only PRs are not stuck draft. Write
WebSearch/WebFetch denials after the isolated research pass so classify
and follow-up research can still call those tools.
Pause and resume now go through one process-level control plane that survives
panel/terminal close: TransferRuntime + soft latches/epochs/walk registry,
soft-then-hard resume, dual-epoch-safe progress, and no-owner soft control.
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>
Issue titles and bodies are auto-validated; agents reading only AGENTS.md or
CLAUDE.md were missing the [Bug]/[Feature]/[Other] prefix and template
requirements. Point all three guides at the shared .github templates.
Closes#2492
When scanning the system known_hosts file or importing from a file,
exclude well-known public service hostnames (github.com, gitlab.com,
bitbucket.org, Azure DevOps) from being added to the Known Hosts vault.
These entries represent third-party services rather than user-managed
hosts and are not meaningful in an SSH host management context.
- Add PUBLIC_SERVICE_HOSTNAMES exclusion list
- Apply filter in both system scan and file import paths
- Show a toast notification when entries are filtered
- Add locale strings for filtered count (en, zh-CN, zh-TW, ru)
Make folder pause latch-first with barriers and a child pause watchdog so
workers stop claiming files after soft-drain; soft-resume live walks instead
of full rewalks. Surface path conflicts with toasts, keep pause progress
visible, and show a resume spinner. Restore verified shell rm -rf with SFTP
confirmation for fast directory delete, and prefer reconnect over wiping the
pane to the host picker when listing fails on some hosts.
Do not paint transferring when every backend resume misses after restart.
Refuse starting or resuming a second job with the same source and target
while another is still active, matching FileZilla-style single-writer
destination handling.
Track UploadController instances per transfer task so queue cancel of one
external upload does not abort another concurrent paste/drop upload.
Scope pending conflict prompts to the owning controller as well.
SFTP queue pause/resume/cancel now call sftpTransferCenterStore (same as the
global transfer center), harden action-button pointer handling, retry
"cannot be paused yet", and keep progress updates from undoing a paused row.
Paint paused optimistically, stop counting paused rows as "active", freeze
visible progress while paused, and soft-drain concurrent pause in ~50ms
without long writeStream waits on the contiguous-range path.
* fix(sftp): close clipboard upload dialog before transfer
Clear the confirmation request before awaiting the upload so the modal
overlay no longer blocks side-panel and standalone SFTP during large
clipboard transfers (issue #2478). Keep the existing background transfer
path and post-upload refresh; guard double-clicks and skip refresh on
transfer failure.
* fix(sftp): scope clipboard confirm guard to request identity
Only block double-clicks on the same confirmation request so a later paste
can still Upload while an earlier background transfer is in flight.
Prefer contiguous bridge checkpointBytes over high-water transferred for
resume offsets, read remote modifyTime for meta pause identity, clear
deferredSparseTruncate on failed pause unpause, and forward fingerprint
fields through drag-drop upload progress.
Wait for in-flight concurrent ranges on resume with a deadline and cancel
checks so stalled SFTP requests cannot hang Resume forever. Propagate
sourceFingerprint through external-upload progress updates.
Embed self-hosted light/dark star history charts after api.star-history.com
stopped serving public embeds, and add a weekly workflow that refreshes them
via RELEASE_TOKEN.
Confirm pause only after a cheap size+mtime source identity is recorded
and published for transfer-center persistence. Wait for in-flight
concurrent ranges before sparse-tail truncation on resume, and refuse
truncate after cancel during that wait.
Pause felt stuck because the panel hid the pausing state, and the
backend waited for every in-flight range plus a full source fingerprint
hash. Soft-drain concurrent ranges, skip blocking fingerprint work, and
show clear pausing feedback in the panel and transfer center.
Raise UPLOAD_TRANSFER_CONCURRENCY from 32 to 64 so the default in-flight
window is ~2MiB, matching Electerm transfer.js / ssh2-style defaults while
keeping the safe 32KiB chunk size.
Remove the unused uploadFileResumableFast alias and the always-null
activeSftpUploads.stream field from the buffer-upload cancel path after
serial WriteStream crawl was fail-closed for #2449.
On promoteLocalTransfer failure after publish, only unlink/restore when the
target still matches the published ready identity. Concurrent post-publish
replacements leave the concurrent file and backup intact.
Do not publish a staged local download over a concurrent file that appears
at the target path after the original was moved to backup. Leave the
concurrent target in place and retain the backup for recovery.
Always content-hash remote destinations before staged promotion (even when
ino/fileId is stable), pass AbortSignal through destination hashing so cancel
closes read streams, and revalidate local download backups before publish.
* fix(sftp): raise upload WRITE fanout for higher throughput
Increase UPLOAD_TRANSFER_CONCURRENCY from 8 to 32 so resumable and
fastPut uploads keep ~1MB in flight (32 × 32KB). Keeps the safe 32KB
packet size from #2022 while closing most of the gap with ssh2/Electerm
defaults on multi-ms RTT paths (#2449).
* fix(sftp): never fall straight into serial WriteStream uploads
Serial ssh2 WriteStream/put is RTT-bound (one 32KB WRITE at a time) and
is the usual cause of sub-MB/s speeds, not the fanout setting alone.
- Prefer concurrent WRITE ranges on an isolated channel, then fastPut,
then concurrent ranges on the shared browse channel; serial stream is
last resort only.
- Do not swallow post-upload size/content verification into fallback.
- uploadLocal and memory-buffer progress uploads use fastPut with the
configured fanout instead of serial put.
- Add a regression test that sudo/shared sessions stay pipelined.
* fix(sftp): fail closed instead of serial WriteStream fallback
Align with OpenSSH/Electerm/WinSCP: default SFTP uploads stay on
pipelined WRITE strategies only. When concurrent isolated, fastPut,
and concurrent-shared all fail, throw the last error — do not crawl via
serial createWriteStream/put (RTT-bound, #2449).
uploadLocal and writeSftpBinaryWithProgress require fastPut; no silent
serial put path. Tests assert createWriteStream is never invoked on
forced concurrent failure.
* fix(sftp): restore pipelined upload on session-backed clients
Session-backed SFTP clients (openForSession / terminal reuse) are not
ssh2-sftp-client instances and lacked client.fastPut after serial put
was removed. Expose fastPut via the raw channel and route uploadLocal /
writeSftpBinaryWithProgress through pipelinedUploadLocalFile so
session-backed paths keep concurrency fanout.
* fix(sftp): make pipelined fastPut abortable and throttle progress
Address codex review on the upload fanout PR:
- Prefer a disposable SFTP channel for cancelable uploads so
cancelSftpUpload / AbortSignal can end the transfer immediately.
- Honor abort signals for session-backed fastPut instead of ignoring
them after the call starts.
- Restore 100ms / 1MB progress throttling for binary-with-progress
fastPut step callbacks.
* fix(sftp): wait for shared-channel fastPut to settle on abort
When abort uses a non-disposable browse/sudo SFTP channel, do not reject
until fastPut's callback fires so temp/staged cleanup cannot race
in-flight WRITEs. Disposable channels still end immediately.
* fix(sftp): harden fastPut abort, channel errors, and pause ads
Codex re-review fixes:
- Attach SFTP channel error listeners during fastPut to avoid unhandled
EventEmitter crashes on disposable channels.
- Wait for fastPut callback (with short force timeout) after disposable
channel abort so temp files are not unlinked while still open.
- Mark pause unsupported during transferBridge fastPut fallback; restore
pause ads for concurrent-shared.
- Create AbortController before staging ArrayBuffer to disk and pass
signal to writeFile.
* fix(sftp): settle fastPut after errors and verify fastPut content
Codex P2 follow-ups:
- Channel errors during fastPut wait for the callback or a short force
timeout before settling (same pattern as cancel) so temp files are not
unlinked while still open.
- transferBridge uploadViaFastPut uses the same settle discipline so
fallback strategies do not overlap a still-running isolated fastPut.
- Resumable fastPut fallback captures and rechecks a local content
fingerprint before promoting success.
* fix(sftp): reset fastPut checkpoint and verify staged size
Codex re-review:
- After isolated fastPut fails, reset checkpointBytes so concurrent-shared
does not resume past non-contiguous progress holes.
- Rethrow noTransferFallback/sourceChanged after fastPut fingerprint check.
- uploadLocal verifies staged remote size against local before rename.
* fix(sftp): avoid hang on cancelled close and unsafe fastPut checkpoints
- Skip/timeout remote CLOSE after disposable channel abort/failure so
concurrent upload finally does not hang waiting on a dead channel.
- fastPut progress updates UI without advancing durable checkpointBytes
(non-contiguous chunk progress is not crash-safe resume state).
* fix(sftp): address remaining remote Codex review notes
- Fall back to the shared browse SFTP channel when opening a disposable
upload channel fails (MaxSessions / subsystem refused), unless aborted.
- Force-settle concurrent range uploads after cancel if in-flight WRITEs
stall on a non-disposable shared channel (2s grace).
* fix(sftp): bound shared-channel fastPut cancel with force-settle
Shared browse/sudo channels cannot call sftp.end() on abort without
killing the session, but a stalled fastPut callback must still settle.
Schedule the same 2s force-finish used for disposable channels, and
cover it with a stall test that asserts the shared channel is not ended.
* fix(sftp): force-settle shared-channel fastPut on channel error
Shared browse/sudo channels cannot be ended on error, but a stalled
fastPut callback after an error event still must not hang forever.
Schedule the same 2s force-finish for shared-channel errors without
calling sftp.end(), and cover it with a stall regression test.
* fix(sftp): stage binary uploads and cleanup shared-channel cancel
writeSftpBinaryWithProgress now uploads to a remote .part path and
renames into place, so a stalled shared-channel fastPut cannot keep
mutating the final destination after cancel returns. Shared-channel
force-settle also best-effort unlinks the in-progress remote target
without ending the browse/sudo session.
* fix(sftp): harden staged upload replace semantics
Default to stage+rename for cancel-safe finals, but:
- write symlinks in-place so the link node is not replaced
- restore previous mode bits after rename overwrite
- fall back to in-place when creating a .part is permission-denied
Covers mode restore, symlink, and parent-dir permission regressions.
* fix(sftp): encode after staging names; never unlink finals
- Build stage/backup names from the logical path, then encode (GB18030-safe)
- Existing files/symlinks write in-place to preserve inode/metadata
- Shared-channel force-settle only unlinks .netcatty-upload-*.part paths
* fix(sftp): keep AbortSignal aborted in dispose cancel handler
When cancel replaces transferControl.abort for a disposable channel,
also abort the AbortController so staged rename/promotion cannot
complete after the UI has already cancelled the upload.
* fix(sftp): stage regular overwrites; throttle fastPut progress
Regular files always stage+rename (cancel cannot mutate finals on shared
channels) and restore mode after promotion. Symlinks stay in-place.
Clip stage/backup basenames to NAME_MAX and stop forcing every fastPut
step progress event through IPC.
* fix(sftp): tighten staging plan and permission fallback
- Without native lstat, existing paths write in-place to avoid clobbering links
- Permission fallback uses SFTP/error codes only (not message substrings)
- Cover size-mismatch paths whose names contain words like "access"
* fix: address Codex review on PR #2452
* fix(sftp): write SCP uploads through existing symlinks in-place
SCP stage+rename was replacing the symlink node itself. Detect symlink
destinations first and upload through them so the link target is updated.
* fix(sftp): drain shared writes before cleanup
* fix(sftp): track generated upload stages explicitly
* fix(sftp): preserve upload recovery paths
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
- Format recovery dispatches triage before clearing invalid-format
- Shared CJK-friendly title rules; codex label terminal state machine
- Research steps in classify; agent TITLE + PR body for bot PRs
- Single @codex review via CODEX_REQUEST_GITHUB_TOKEN (binaricat)
Use TRIAGE_GITHUB_TOKEN for Codex review loop jobs. Treat convert-to-draft
and mark-ready permission errors, and closed PRs, as warnings so @codex
still runs instead of failing the whole automation job.
Public issue comments no longer show a generated-by line. Grounding stays
in code_findings/reasoning only so reply can use UI language without file
paths, symbols, or dense parentheses. Classify prompt and rewrite templates
match that tone.
Use the bot PAT for classify prepare/apply and implement needs-human
comments so issue activity shows netcatty-bot instead of github-actions.
Treat netcatty-bot as an own actor for automation PR loops.
* fix(ci): stop Cursor issue triage crashing on Search pagination
Octokit normalizes Search API pages so response.data is already the items
array. Mapping response.data.items returned undefined and then crashed on
candidate.user during daily-limit admission — so every valid new issue
failed before Cursor classify could run.
* fix(ci): classify already-available issues with how-to and auto-close
Add an already_available triage category for requests the product already
ships: after code-grounded inspection, reply with the concrete UI entry
path, then close as completed. Low confidence falls back to other instead
of closing. Keeps the Search pagination fix so new issues can classify.
* fix SFTP transfer throughput regression
* fix: address Codex review on PR #2429
* fix resumable transfer review findings
* fix remaining resumable transfer review findings
* fix: address Codex review on PR #2429
* fix(sftp): harden resumable range fallback and channel cleanup
Truncate staged sparse tails to the contiguous checkpoint before stream
fallback or pause, always release isolated download channels on cancel and
failed fast paths, and stabilize concurrency unit tests.
* fix(sftp): abort range transfers on isolated channel errors
Attach a temporary SFTP error listener during concurrent range copies so
channel resets reject cleanly instead of crashing main, and force-publish
the contiguous checkpoint when pausing.
* fix(sftp): never truncate S2S temp source on upload fallback
stagedLocalPath during SFTP→SFTP upload is the fully-downloaded temp
source, not a partial target. Only truncate partial download/local
targets and remote .part staging files.
* fix(sftp): truncate direct-download sparse tails on range fallback
Direct SFTP→local resumable transfers keep resumeStage "direct" and never
copied targetType onto the transfer object, so prepareStreamFallback skipped
truncating the staged .part after out-of-order range failure. A later stream
pause could then stat the oversized sparse file and advance past holes.
Treat any staged local path outside the S2S upload phase as a partial target,
persist target metadata on the transfer, and fix the sparse-tail test to assert
against the real staged path.
* fix(sftp): always end isolated upload channel on open failures
uploadFileResumableFast assumed callers only saw throws after the isolated
channel was already ended, but a failed local fs.open left the channel open
while the catch path cleared fastSftp without disposing it. End the channel
in a single finally covering local open, remote open, and range transfer.
* test(sftp): stabilize local-open channel cleanup regression
Make the missing-source upload case delete the file only after the isolated
channel opens, and keep stream-fallback errors handled so the suite asserts
channel disposal without unhandled stream ENOENTs.
* fix resumable transfer fallback safety
* fix(sftp): allow cancel while resumable OPEN is in flight
Install transfer.abort before awaiting local/remote handles so cancel ends
the isolated channel if the server stalls SFTP OPEN. Covers upload and
download fast paths, with a regression test for stalled upload OPEN.
* fix(sftp): detect same-size source rewrites via content samples
Metadata-only checks can miss in-place rewrites when mtime/ctime do not
advance. Sample SHA-256 digests through the open local handle before range
upload and re-check after, and do the same path-level check for stream
fallback. Harden the regression test by freezing utimes after rewrite.
---------
Co-authored-by: netcatty-cursor-bot <41898282+github-actions[bot]@users.noreply.github.com>
Classification must open real source files first. The prompt mandates
search→read→classify, JSON now requires code_paths/code_findings, and
normalizeClassification rejects ungrounded replies so agents cannot answer
from issue text alone.
Cursor's --sandbox enabled fails on ubuntu-latest due to AppArmor, so classify
and implement jobs never ran. Use --sandbox disabled and keep secret isolation
via token stripping, frozen helpers, and post-run scans.
Isolate publish onto fresh runners, harden Codex loop head scoping,
protect fork bootstrapping, and polish classification replies and
mark-ready error handling after local codex review.
Route checked out main for trusted helpers, but scripts/cursor-automation.cjs
only exists on this PR until merge, so require() failed with MODULE_NOT_FOUND.
Fetch the PR head helper when the default branch does not have it yet.
The SFTP transfer center trigger was missing the shared
--top-tabs-muted color used by adjacent utility buttons, so it
rendered as pure foreground white.
Tighten re-triage, Codex single-flight signals, protected-path checks on
commit ranges, trusted helper loading from RUNNER_TEMP, and failure
handling so own/bot loops stay safe and forks only re-@codex.
Add missing Terminal snapshot-effect deps, drop unused cursorAuthMode
deps in SettingsAITab, and remove dead test imports/state so dev lint
starts clean.
Drop Cursor CLI review for third-party/fork PRs. Initial Codex review
stays with the existing connector; after contributor synchronize we only
comment @codex review (deduped per head SHA).
Wire GitHub Actions to classify and implement issues via Cursor CLI,
open draft PRs, request @codex review for own/bot PRs, fix findings,
and only post read-only review comments on third-party PRs.
Cancel wind-down can leave child transferIds latched when they are not in
activeTransfers; clearPendingTransferCancel before reusing those ids so
startStreamTransfer does not abort immediately. Regression test models the
sticky latch.
Soft-unpause only live streams; directory dedicated pause winds down then
startFresh. Resume replace-mode under stagedTargetPath and promote on full
success. Refuse pre-stream pause, force R2R progress without corrupting stage
offsets, and cover the directory pause-resume path with a regression test.
Urgent interrupt bypasses xterm's native scrollOnUserInput. Treat Ctrl+C
as scroll-on-input so interrupting a scrolled-up stream (e.g. tail logs)
returns the viewport to the bottom when that setting is enabled.
Keep browse sessions independent of pool transfers, complete dedicated resume
for files/folders after restart, roll back partial pause safely, clamp crash
checkpoints to durable .part size, and open upload targets by connecting the
host when no terminal tab is active.
Seed erase-scrollback with open sync-block state from prior chunks so a
split H/2J redraw that returns to the live bottom does not append CSI 3 J
and wipe history (Codex review on #2415).
Keep cursor-home and strip only erase-display while reading scrollback,
and ignore 1-row sticky lag so agent TUI frames cannot append under the
previous frame (#2291).
* feat(ai): support Cursor CLI login for subscription quota
Allow the managed Cursor agent to authenticate via local `agent` CLI
login as an alternative to CURSOR_API_KEY, so Catty can use subscription
Auto quota. Keep API key and CLI login mutually exclusive, isolate
resume sessions by auth mode, and expose selectable CLI models with
auto as the default.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): harden Cursor CLI login path after review
Address multi-agent review findings: soft-cancel abort without late
stream errors, prefer cursor-agent over ambiguous agent binaries,
mode-aware availability without wiping API keys or sticky-disabling,
and concurrent MCP merge refcount restore. Add unit coverage for the
new contracts.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(terminal): batch hidden log output
* fix(terminal): preserve ordered hidden output
* fix(terminal): bound hidden output overhead
* fix(terminal): respect custom tab stops in timestamps
* fix(terminal): preserve terminal parser state
* fix(terminal): preserve hidden output boundaries
* fix(terminal): retain batching with prompt formatting
* fix(terminal): preserve prompt boundaries through transforms
* fix(terminal): handle unbatched prompt output
* fix(terminal): preserve batched prompt and time boundaries
* fix(terminal): retain background timestamp boundaries
* fix(terminal): preserve visible timestamp boundaries
* fix(terminal): separate shell output from tui frames
* fix(terminal): separate output after tui exit
* fix(terminal): preserve prompts across output slices
* fix(terminal): flush output before context reads
* fix(terminal): time-slice timestamp marker cleanup
* fix(terminal): preserve prompt columns after bare LF
* fix(terminal): preserve background write yielding
* fix(terminal): retain yields across timestamp boundaries
* perf(terminal): scan output boundaries once
* fix(terminal): track prompt cursor movement
* fix(terminal): resume prompt scan after resets
* fix(terminal): preserve stateful prompt ordering
* fix(terminal): drain close output before capture
* fix(terminal): measure control-only prompt prefixes
* fix(terminal): preserve prompt control boundaries
* fix(terminal): preserve prompt mode toggles
* fix(terminal): bound hidden batches and unfocus policy
Always drain write-coalescer size caps even when scheduled frames are gated,
keep unfocused-but-visible panes on throttled batching instead of per-chunk
force flush, compute prompt starts on post-transform display data, and model
full-width wrap so prompt-on-newline does not insert a blank line.
* perf(terminal): skip off-path timestamps and share alt-screen probes
When host line timestamps are disabled, write plain bytes without the
segmenter/marker hot path. Reuse one alt-screen CSI probe per coalesced
chunk for frame split and schedule mode, and reuse a single full-batch
visual-row measure on the single-segment batched timestamp path.
* fix(terminal): read line-timestamp enable from live hostRef
Session data handlers close over boot-time ctx.host, so toolbar/vault
toggles of showLineTimestamps were ignored. Prefer hostRef.current for
the marker hot-path gate and keep hostRef synced on every Terminal render.
* perf(terminal): per-second timestamp ledger while gutter is off
When line timestamps are hidden, keep a lightweight one-stamp-per-second
ledger without xterm markers. Opening the gutter materializes recent
ledger entries into markers for paint; while visible, keep per-line
markers for accuracy.
* perf(terminal): separate timestamp record from gutter paint
Always record a one-stamp-per-second ledger on the write path with no
xterm markers. Gutter rendering maps the ledger to visible rows, so open
and closed modes share the same cheap record path.
* fix(terminal): fill gutter gaps with previous second stamp
When painting line timestamps from the per-second ledger, blank rows
between stamps inherit the preceding stamp's label so multi-line output
in the same second is fully labeled.
* fix(terminal): stop empty gutter paint and false scrollback rebase
Only rebase ledger anchors when the buffer is actually full and baseY
grows from top trim — not when baseY rises while the buffer is still
growing (which deleted MOTD/login stamps on scroll). Cap gutter paint at
the last content line so empty viewport rows past the cursor stay blank.
* fix(terminal): soft-wrap stamp placement and sparse reflow anchors
Estimate ledger lines with visual-row measurement so narrow terminals
do not pin the next second on a wrap continuation. After each write,
attach at most one xterm marker per stamped second so reflow and
scrollback trim keep anchors honest without per-line marker storms.
* fix(terminal): ignore alt-screen rows when placing timestamp anchors
Stamp line estimates froze incorrectly through vim/less alt-screen
output, so sparse reflow markers landed far below the restored normal
buffer. Skip visual-row advancement while DEC alt-screen is active,
key one-stamp-per-second from the batch arrival date, and only attach
anchors still present in the ledger after capacity trim.
* fix(terminal): seed stamp lines from normal buffer when already on alt
When a write begins on the alternate screen, active cursor is the TUI
row — freezing that through 1049 leave mis-pinned sparse anchors and
dropped them past normal bufferLength. Seed and restore stamp estimates
from buffer.normal, and cover multi-write already-on-alt leave+prompt.
* fix(terminal): keep same-chunk pre-enter line on alt leave
Leave CSI must not always re-read pre-write buffer.normal: when enter
happened in the same walk, the frozen estimate already includes normal
advances before the TUI. Only restore buffer.normal when the write
started already on the alternate screen.
* fix(terminal): pure alt stamp transitions that never rewind lines
Stamp estimates stay in normal-buffer coordinates: seed once (active or
buffer.normal when already on alt), freeze while alt is active, and on
leave only clear altActive — never re-read the live term mid-walk.
Export applyAltScreenAction with a table-driven unit test covering
spurious leave, plus write-path cases for already-on-alt, same-chunk
pre-enter advance, and leave-without-enter.
Parent resume clears sticky per-child cancelledTasksRef entries so directory
re-walk can retry cancelled files. Route clearPendingCancel to the terminal
worker (where skipAdmission latches live), re-check cancel on live resume,
and clean per-child stage artifacts on parent cancel.
Re-check cancel inside setTransfers so late cancels are not overwritten,
keep partial progress on cancelled directory downloads, and clear the
pre-start cancel latch on intentional resume/retry.
downloadToLocal previously treated cancelled child counts as partial
failure and overwrote the parent to failed. Cancel now wins, matching
processTransfer, with a regression test for the final-status helper.
Keep replace/staging on directory adopt, skip byte size validation for
directory soft-resume, and treat per-child cancel as cancelled without
aborting sibling workers or overwriting status as failed.
Reassign parent+children together on adopt, stop spurious cancelTransfer
after failed dedicated reconnect, and clear pending-cancel when a queued
job is cancelled so same-id resume is not permanently poisoned.
When a directory transfer is re-adopted after restart, pull orphaned child
rows into the new owner so completed checkpoints are honored. Show badge
count/attention for paused and interrupted work after restore.
Server-to-server dedicated soft-fail rehomes via adopt instead of a no-op
resume, Resume/Pause All skip child rows, and orphaned failed tasks remain
controllable with a clean Retry from byte zero.
Server-to-server soft-fail from dedicated resume rehomes ownership so a
live panel can resume; pause during dedicated reconnect no longer cancels
(so quit confirmation does not destroy in-flight resume).
Pause on dedicated reconnect cancels the open, resolveConflict never
resurrects cancelled rows after async duplicate naming, and background
agent restart admits before opening session-backed SFTP handles.
Guard processTransfer against cancel after conflict stats, and make batch
Stop cancel scheduler jobs, release pause waiters, sync refs, and clean
artifacts like single-task cancel.
When the user stops a conflict batch, cancel every non-terminal sibling
(including attention conflict rows) so they do not remain stuck without
resolve UI or resume.
No-op pause on conflict rows, keep transfer/conflict refs in sync on cancel,
eagerly clear conflictsRef on resolve to stop double processTransfer, and
surface adoption failures when resolving orphan conflicts.
Keep conflictsRef updated during adopt so global conflict resolution applies
immediately, re-check cancel after adopt, gate replace/merge like the panel
dialog, and ignore dedicated progress while paused/cancelled.
Drop panel-local rows reassigned to dedicated-resume, seed conflicts on
adopt without auto-resume, cancel directory children on orphan cancel,
gate dedicated resume on !conflict, and hide invalid global merge actions.
Exclude conflict-pending transfers from dedicated resume and Resume All so
users cannot overwrite targets without resolving Replace/Skip/Duplicate.
Also skip Pause All on dedicated reconnect spinner rows.
Reassign store ownership to dedicated-resume before reconnect work so
panel publishOwner cannot clobber progress, and open vault SFTP sessions
only after the renderer admission pool grants a slot.
Directory cancel now stops all non-terminal children on the renderer
scheduler, and transfer workers re-check cancel before acquiring pool
leases so cancelled bulk jobs do not open dedicated sessions.
Remember cancels before startTransferNow registers the id, cancel the
renderer scheduler on orphan cancel, avoid pause completion resurrecting
cancelled rows, forward skipAdmission for temp downloads, and persist
progress checkpoints on background events.
Share dedicated resume with the renderer admission pool, preserve queued
pause checkpoints, refuse to resurrect cancelled agent rows, and complete
dedicated resumes that finish after a reconnect-spinner pause.
Settle directory streams on cancel return values, detach idle pool slots
before close, make force hard-close lease-aware, honor skipAdmission on
the worker IPC path, fix MCP non-worker admission, and block dedicated
resume from resurrecting cancelled/paused rows.
Keep soft-close sticky across re-acquire, re-check before hard-close,
holder-aware pool discard, admit-then-lease for uploads, directory/cancel
resume edge cases, MCP skipAdmission, and vault-based reconnect identity.
Add dedicated transfer connections (1–2 per host) so bulk uploads/downloads
no longer block browse sessions. Also cover session leases, resume after
restart, auto-reconnect, skipAdmission, and transfer-center UX fixes for #2396.
* Improve keychain identity layout
* Preserve key search results with identities
* Show keychain search empty state
* Show all matching keychain results
* Restore full keychain key view
- Add computeOptionsFingerprint to detect option changes between turns;
close stale session and create fresh one when model/env/mode/etc change
- Store resolved binPath in sdkRequestRuntimes so steer builds the same
session key as runTurn (fixes steer returning 'unsupported')
- Register abort listener that calls session.interrupt() immediately,
so Stop reaches the CLI even while parked in stream().next()
- Update tests for { session, fingerprint } map structure (35 passing)
* fix(sftp): follow terminal cwd on macOS/BSD remotes and first open
The "follow terminal directory" probe read the interactive shell's cwd
only via /proc, which does not exist on macOS/BSD remotes, so
getSessionPwd fell back to the home directory (or failed) and the SFTP
panel never tracked the terminal. Add an lsof fallback (read_shell_cwd)
so the probe works on non-Linux remotes; Linux keeps using /proc.
Also fix the first open: while the panel is closed the per-command cwd
probe does not run, so activeTerminalCwd can be stale (login home) even
after the terminal cd-ed elsewhere. On that stale value the follow sync
saw currentPath === terminalCwd and did nothing, leaving the panel at
home. Force one fresh backend probe when the panel first becomes visible
for a connected remote, and re-sync on reopen.
Fixes#2335
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* fix(sftp): keep first-open follow from snapping back to stale cwd
Addresses PR review: the first-open resync navigated to the freshly
probed terminal cwd but left the stale `activeTerminalCwd` cache (login
home) in place. Because the one-shot ref was already set, a later
dependency change that re-runs the normal follow sync (e.g. closing an
editor toggles hasActiveWork) would navigate the pane back to that stale
home. Mark the stale cached cwd as already handled before navigating so
the normal sync no-ops until the live cwd catches up;
invalidateInFlightFollowSync clears the guard once activeTerminalCwd
actually changes, letting follow resume.
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* fix(sftp): re-check follow eligibility before first-open navigation
Addresses PR review: the first-open resync awaited a fresh terminal-cwd
probe but only re-validated the connection afterward. If the panel was
hidden or interactive work started (editor/dialog) while the probe was
in flight, it still navigated, moving the pane while the follow feature's
own visible/work guards say it should be paused.
Add a live followStillEligible() check (generation, follow enabled/
allowed, visible, no active work, same connection) that gates both the
post-probe continuation and the navigateTo call via shouldApply, matching
the normal follow path. Reset the one-shot guard when bailing mid-probe
so the resync can retry once follow is eligible again.
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* test(host-details): align auth row assertion with unified row height
The HostDetailsSection row was unified to `flex h-12 ... px-3` (#2378)
but HostDetailsConnectionSections.test still expected the old
`min-h-12 ... px-3 py-2` class, failing lint-and-test on main. Update the
regex to match the shipped markup.
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* fix(sftp): harden terminal cwd resync
---------
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(ui): open AI silent sessions in an attach terminal popup
Tray jumps for hidden MCP sessions attach the existing terminal popup
to the live PTY instead of activating a tab-less main-window surface.
Output is rebound to the popup and restored when the popup closes.
* fix(terminal): register rebindOutput handlers under worker mode
Worker-enabled builds returned early from terminalBridge.registerHandlers
without registering rebind/restore IPC, so the first AI silent-session
attach popup always failed with "No handler registered". Register those
handlers in both paths and rebind display routing via terminalWorkerManager.
* fix(terminal): harden AI attach popup for worker mode lifecycle
Address codex review P1s: resume flow control before restoring output,
route exit/events to the rebound renderer, and replay home scrollback
via a snapshot request before rebinding display output.
* fix(terminal): restore attach routing on popup close and dual-notify exit
Hardening for AI observe popups: restore output from BrowserWindow closed
lifecycle (not only React cleanup), keep attach home for exit events so
main/tray lifecycle updates, and resume paused backend flow on attach.
* fix(terminal): resume flow on attach restore and close snapshot gaps
Resume backend output when attach popups are force-closed, fan out
lifecycle exit events to home+display in non-worker mode, and pause
during snapshot→rebind so live bytes are not dropped from the popup.
* fix(terminal): harden attach restore and worker lifecycle edges
Prefer live display for ZMODEM dialogs after rebind, dual-notify worker
crash exits to attach-home, and avoid clearing attach-home when restore
targets a dead renderer.
* fix(terminal): sync observe-popup state home and fan out SSH exits
Push popup serializations home before restore, fall back to hibernate
snapshots, settle briefly after pause for worker handoff, and dual-notify
SSH exits to attach-home owners in non-worker mode.
* fix(terminal): fan out exits for mosh/telnet/et attach owners
Use shared fanout for in-process protocol exits so attach-home and
display renderers both receive netcatty:exit under non-worker mode.
* fix(terminal): restore attach only onto main app windows
Avoid routing silent-session output to settings/prewarm windows when the
original home renderer is gone; fall back only to registered main windows.
* fix(terminal): harden silent session popup handoff
* fix(terminal): close observed popup with session
* fix(terminal): keep observe popup from reconnecting
* fix(terminal): preserve cleared observe snapshots
* fix(terminal): route transfer prompts to observer
* fix(terminal): keep context after observe restore
* feat: fold top-bar utilities into a control panel
Collapse External MCP, window opacity, theme, and cloud sync into one top-bar control panel so the right rail only keeps AI, controls, and settings.
* fix: keep the cloud sync panel and append quick controls
Restore the original detailed cloud sync popover as the host surface, and place External MCP, window opacity, and theme controls in a footer section beneath it.
* fix: split quick controls from the cloud sync panel
Restore the original cloud sync popover and move External MCP, window opacity, and theme into a dedicated quick-controls entry so the two surfaces stay separate.
* fix: simplify quick controls panel chrome
Drop the explanatory subtitle and card-heavy rows so the panel reads as a compact control list.
* fix: separate appearance controls from External MCP
Group window opacity and theme under one appearance section, with a divider from the External MCP switch.
* fix: compact window opacity controls in the quick panel
Replace the tall slider-plus-presets block with a single-row preset selector to free vertical space.
* fix: make window opacity controls expandable
Keep the opacity row compact by default and reveal the slider plus presets only when the row is expanded.
* fix: put appearance controls above External MCP
Reorder the quick controls panel so window opacity and theme sit first, with External MCP below the divider.
* fix: harden quick controls opacity matching and drop dead copy
Compare opacity presets by percent to avoid float mismatch, and remove the unused quick-controls description strings.
* fix: clean quick controls accessibility and dead top-bar opacity code
Move Switch accessible names onto the focusable control, restore popover autofocus, delete the unused window opacity button, and drop obsolete top-bar theme copy.
* fix: tighten quick controls peer-window and switch labeling
Assert showExternalMcpToggle is forwarded into the quick panel, and name the External MCP switch from its visible label only.
* fix: add icon to quick controls panel title
Prefix the panel header with the same sliders icon used by the top-bar entry.
* fix: remove icon from quick controls panel title
Keep the header as plain text after the title icon felt unnecessary.
* feat: add external MCP top bar toggle
* fix: polish external MCP toggle accessibility
* fix: harden External MCP top-bar toggle ownership and status sync
Hide the quick toggle in peer session windows, poll runtime status at the same cadence as settings, and drop an unrelated zh-CN copy change from this branch.
* fix: prevent External MCP top-bar poll from clearing startup state
Wait for main-window startup reconcile before runtime status can turn the shared switch off, and keep peer windows from owning the main-process enable path.
* fix: await External MCP startup enable before top-bar status polling
Mark startup ready only after the async enable/disable reconcile settles so persistent mode is not cleared by a still-disabled runtime.
* fix: scope External MCP startup gate and preserve enable intent
Only main App shells wait on startup reconcile, keep waiters single-flight, re-read storage after await gaps, and avoid clearing always-on preference on start errors.
* fix: ignore stale External MCP startup enable after concurrent toggles
Bump an enable generation on top-bar changes and skip outdated startup apply work so boot reconcile cannot overwrite a newer user toggle.
* test: fix External MCP toggle lint errors in unit coverage
Remove a duplicate import and use globalThis.localStorage in tests so eslint no-restricted-globals passes.
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(sftp): preserve active transfers when panel closes
* fix(sftp): preserve completed transfer history after panel close
* fix(sftp): bound retained transfer history lifetime
* fix(sftp): close retained panel cleanup races
* fix(sftp): protect transfer history during panel reopen
Jump hosts reuse the same SSH bridge authentication flow as the target host, but the renderer did not serialize the per-host requiresMfa preference into jumpHosts. A bastion configured to prefer keyboard-interactive was therefore still authenticated with the default password-first order when used in a host chain.
Forward requiresMfa from each resolved chain host into the NetcattyJumpHost payload and extend the startSSH serialization test to cover the flag alongside existing jump host proxy and timeout settings.
The terminal search bar left the previous match decorations on screen
after the input was cleared. Two causes:
- TerminalSearchBar only called onSearch when the term was non-empty, so
clearing the field never notified the search hook.
- handleSearch's empty-term branch returned early without clearing
decorations or resetting the stored term.
Now clearing the input fires onSearch(''), and the empty-term branch
resets searchTermRef and calls clearDecorations() so highlights vanish
immediately and a subsequent find-next won't reuse the stale term.
Problem: some SSH servers require the login password and the MFA challenge through keyboard-interactive. They reject the plain SSH password authentication method even when the password is correct, which makes Netcatty report password failures and can close the session before a usable shell opens.
Fix: add an explicit per-host Prefer interactive auth setting, reuse the existing requiresMfa host flag, forward it from the renderer to the native SSH bridge, and place keyboard-interactive before password only when that setting is enabled and a password is available. Hosts that do not enable the setting keep the existing auth ordering.
Tests: add a bridge regression test for the MFA auth order and extend the terminal starter test to assert the host option is forwarded.
'Title must start with `[Bug]` or `[Feature]` followed by a short summary (at least 8 characters after the prefix). Legacy app links using `Bug: ...` are also accepted. Example: `[Bug] SFTP upload fails on Windows`'
);
}
if (body.length < 120) {
errors.push(
'Body is too short. Please use the Bug Report or Feature Request template and fill in all required fields.'
git commit -m "chore: update star history charts [skip ci]"
# Auth comes from actions/checkout (RELEASE_TOKEN), not GH_TOKEN above.
git push -u origin "HEAD:${branch}" --force
existing="$(
gh pr list \
--head "$branch" \
--base "$default_branch" \
--state open \
--json number \
--jq '.[0].number // empty'
)"
if [[ -n "$existing" ]]; then
echo "Reusing open PR #${existing}"
pr_number="$existing"
else
pr_url="$(
gh pr create \
--base "$default_branch" \
--head "$branch" \
--title "chore: update star history charts" \
--body "Automated weekly refresh of the static star history SVG charts embedded in the README. Generated by the Star History workflow via mystarhistory."
)"
echo "Created ${pr_url}"
pr_number="${pr_url##*/}"
fi
# required_approving_review_count is 0; merge immediately when possible.
if gh pr merge "$pr_number" --squash --delete-branch; then
echo "Merged PR #${pr_number}"
else
echo "Could not auto-merge PR #${pr_number}; left open for manual merge."
@@ -106,6 +106,31 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
**Harness domain (`catalog/harness.cjs`):** Catty-only surface (`surfaces.catty.toolName`). Registered in the capability catalog but executed locally in `capabilityTools.executeLocalCattyCapability` (not MCP/CLI). `harness.web.search` is omitted when web search is not configured.
## Plugin host runtime (internal preview)
The phase-2 plugin host lives under `electron/plugins/` and is disabled unless
`NETCATTY_PLUGIN_DEV=1` is present at application launch. Public wire and package
types still come only from `packages/plugin-contract/schema/`; do not add a
second private RPC shape when extending the host.
-`PackageStore` validates an immutable `.ncpkg` snapshot, extracts only into
private staging, and atomically publishes an installed version.
-`PluginManager` serializes install, enable/disable, restart and uninstall
mutations. Do not bypass it from renderer IPC.
- Ordinary plugins run in a sandboxed, offline `BrowserWindow` session and can
reach only their runtime-scoped `netcatty-plugin://` authority.
- Node-only plugins run in a dedicated `utilityProcess`; they remain an advanced
development-only path until permission and distribution phases land.
-`PluginRpcRouter` owns correlation, cancellation, deadlines, stream credit and
protocol-failure containment. Runtime identity is assigned by the host and is
never accepted from request parameters.
- App quit goes through `runPluginShutdown()` after the dirty-editor guard; do
not add another independent quit interception path.
Run `npm run test:plugin-runtime` for main-process boundaries and
`npm run test:plugin-runtime:electron` for real BrowserWindow/utilityProcess
smoke coverage. Packaged-resource changes must also pass `npm run pack:dir`.
## Extending the System
1) **New domain logic**: Add pure functions/types under `domain/`; avoid side effects.
2) **New stateful behavior**: Wrap it in a hook under `application/state/`; keep external I/O behind adapters.
@@ -117,6 +142,14 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
- **Temporary files**: All temporary files (e.g., SFTP downloaded files for external editing) must be written to Netcatty's dedicated temp directory via `tempDirBridge.getTempFilePath(fileName)`. Do not write directly to `os.tmpdir()`. This ensures proper cleanup and user visibility in Settings > System.
## Terminal Side Panel Splits
-`domain/sidePanelLayout.ts` owns the pure pane/split tree operations, including focus, unique tools, close collapse, resize, and the pane limit.
-`application/state/useTerminalSidePanelLayoutState.ts` owns per-terminal layouts and keeps the legacy focused-tool map compatible with external open paths.
-`TerminalLayerSidePanelSection.tsx` renders one shared toolbar plus the nested pane chrome. Each pane content host must remain `overflow-hidden` and layout-contained.
- Mounted tool panels use a stable portal node that moves between their pane host and the hidden parking host. Never fall back to an `absolute inset-0` side-panel root when a pane host is not ready.
- Closing the whole side panel clears that terminal's split tree. Switching terminal tabs must not reuse another tab's layout.
## Testing & Safety
- Favor unit tests for domain helpers (e.g., `workspace.ts`, `host.ts`) and hook-level tests for application state.
- When changing storage keys or schema, provide migration or backward-compat handling.
@@ -128,6 +161,16 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
- Avoid direct network/fetch in components; add a service/adaptor first.
- Maintain ASCII-only unless required by existing file content.
## Reporting Issues & PRs
Issues opened without the required format are **auto-closed** by the issue-format bot. Agents that file issues via `gh` or the API must still follow these rules:
- **Title:** start with `[Bug]`, `[Feature]`, or `[Other]`, then a short summary (≥ 4 characters after the prefix). Example: `[Bug] SFTP upload fails on Windows`.
- **Body:** use the templates under [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/) (Bug Report or Feature Request) and fill every required field. Blank issues are disabled.
Full contributor guidance (setup, commits, PR process): [CONTRIBUTING.md](./CONTRIBUTING.md).
## Review Boundaries
- Treat `electron/cli/*`, `netcatty-tool-cli`, the CLI discovery file, and the local TCP bridge as internal Netcatty integration surfaces unless a task explicitly says otherwise.
- Do not review those surfaces as public APIs by default, and do not assume they must support third-party callers, manual launches, or non-Netcatty agents.
@@ -127,6 +127,7 @@ If you regularly work with a fleet of servers, Netcatty is built for speed and f
### 🖥️ Terminal Workspaces
- **Split panes** — horizontal and vertical splits for multi-tasking
- **Session management** — run multiple connections side-by-side
- **Inline images** — render Kitty graphics, SIXEL and iTerm inline images from remote programs
### 📁 SFTP + Built-in Editor
- **File workflows** — drag & drop uploads/downloads
@@ -206,6 +207,18 @@ Download the latest release for your platform from [GitHub Releases](https://git
Or browse all releases at [GitHub Releases](https://github.com/binaricat/Netcatty/releases).
### Code signing and privacy
Netcatty is applying to the SignPath Foundation open-source program. Once
approved, covered Windows release artifacts will use **Free code signing provided by SignPath.io, certificate by SignPath Foundation**.
See the [code signing policy](CODE_SIGNING_POLICY.md) and
[privacy policy](PRIVACY.md). Windows releases may remain unsigned until the
application and integration are complete.
> **Windows portable data:** Exit Netcatty, then create a folder named `data` beside `Netcatty.exe` (zip build) or beside the portable launcher. Netcatty will store its profile there on the next launch. Saved passwords and private keys remain protected by the Windows user account that created them, so they must be re-entered after moving the folder to another computer or Windows account.
> **Open folders in Netcatty on Windows:** The installed version adds **Open in Netcatty** to Explorer's folder context menu and folder-background context menu. It opens a local terminal in that folder. On Windows 11, choose **Show more options** first. Turn the menu off or on in **Settings → System → Windows Explorer**. ZIP and portable versions do not add this menu by default.
> **macOS Users:** Current releases are expected to be code-signed and notarized. If Gatekeeper still warns, make sure you downloaded the latest official build from GitHub Releases.
### Nix / NixOS
@@ -308,11 +321,11 @@ This project is licensed under the **GPL-3.0 License** - see the [LICENSE](LICEN
'ai.providers.apiKey.placeholder':'Enter API key',
'ai.providers.apiKey.decrypting':'Decrypting...',
'ai.providers.baseUrl':'Base URL',
'ai.providers.baseUrl.anthropicHelp':'Anthropic-compatible: host with or without /v1 (for example https://gateway.example or https://gateway.example/v1). Detection and chat both use /v1/models and /v1/messages.',
'ai.codebuddy.envVars.hint':'One KEY=VALUE per line, passed to the CodeBuddy agent. Set CODEBUDDY_API_KEY or CODEBUDDY_AUTH_TOKEN here for authentication. Stored locally in plaintext.',
// AI Grok Build (in-app managed agent — distinct from External MCP client install)
'ai.grok.title':'Grok Build',
'ai.grok.description':"xAI's Grok Build coding agent CLI. Install the Grok CLI, sign in with `grok login` or set XAI_API_KEY, then select it as an external agent.",
'ai.grok.detecting':'Detecting...',
'ai.grok.detected':'Detected',
'ai.grok.notFound':'Not found',
'ai.grok.path':'Path:',
'ai.grok.notFoundHint':'Could not find grok in PATH. Install Grok Build CLI or specify the executable path below.',
'Talk to Grok over Agent Client Protocol (grok agent stdio). Injects Netcatty MCP on session/new. Turn off to use the original headless streaming-json CLI path.',
'ai.grok.runtime.streamingJson.hint':
'Using headless streaming-json (grok -p --output-format streaming-json). Project .grok/config.toml is used for MCP injection.',
// AI Default Agent
'ai.defaultAgent':'Default Agent',
'ai.defaultAgent.description':'Agent to use when starting a new AI session',
'ai.externalMcp.focusOnHostOpen':'Focus window on host_open',
'ai.externalMcp.focusOnHostOpen.description':'When an MCP client opens a host, bring the main window to the foreground. Turn off to keep working without interruption.',
'ai.externalMcp.silentSessions.description':'Sessions opened by AI stay out of your tab bar and are not restored after restart. View them anytime from the tray panel.',
'ai.externalMcp.sessionIdleTimeout.description':'Automatically close sessions opened by an AI after this many minutes without terminal or file activity.',
'settings.sessionRestore.startupLandingDesc':'Choose whether to open the host home page or a local terminal when Netcatty starts with no restored sessions.',
'settings.vault.showSftpTabDesc':'Display the standalone SFTP view in the top tab bar. When hidden, use the in-session SFTP side panel instead.',
'settings.vault.showHostTreeSidebar':'Show host list sidebar',
'settings.vault.showHostTreeSidebarDesc':'Display the host list sidebar and its top-bar toggle on terminal and editor tabs.',
'settings.vault.autoImportSystemKnownHosts':'Auto-import system known_hosts',
'settings.vault.autoImportSystemKnownHostsDesc':'When Vault loads, silently import entries from the OpenSSH known_hosts file. Turn this off to keep Netcatty known hosts separate; Scan System still imports on demand.',
// Update notifications
'update.available.title':'Update Available',
@@ -326,6 +341,7 @@ Because many styles come from Tailwind utilities, you often need \`!important\`
###Toptabs
-\`top-tabs\`
-\`top-tabs-toolbar-actions\` — Right-side menu and utility toolbar
-\`top-tabs-host-tree-toggle\`
-\`top-tabs-quick-switcher-toggle\`
@@ -340,6 +356,8 @@ Because many styles come from Tailwind utilities, you often need \`!important\`
###Terminalworkspace
-\`terminal-workspace\`
-\`terminal-toolbar\` — Terminal action toolbar
-\`terminal-toolbar-menu\` — Terminal toolbar overflow and submenu
-\`terminal-workspace-sidebar\` — Focus-mode terminal list
-\`terminal-host-tree-sidebar\`
-\`terminal-host-tree-sidebar-content\`
@@ -379,6 +397,18 @@ Because many styles come from Tailwind utilities, you often need \`!important\`
'Show the context menu even when tmux, vim or other fullscreen apps take over the mouse. When off, right-click is passed to the app (Shift+Right-Click still opens the menu).',
'settings.terminal.behavior.autoCloseOnExit':'Auto-close terminal on exit',
'When the clipboard holds an image, pasting in a remote session uploads it via SFTP to .netcatty-paste-images/ under the remote working directory and inserts the remote path instead of pasting text.',
'settings.terminal.hostInfoBar.show':'Show host information bar',
'settings.terminal.hostInfoBar.show.desc':'Show the host address and server information above the terminal. Action buttons remain available when hidden.',
'settings.terminal.hostInfoBar.titleMode':'Host information title',
'settings.terminal.hostInfoBar.titleMode.desc':'Choose whether the bar shows the connection address (user@host) or the vault host label.',
'settings.terminal.inlineImages.enabled.desc':'Draw real images sent by remote programs (kitten icat, chafa, image previews in file managers and TUIs). Changes take effect on new terminal sessions.',
'settings.terminal.inlineImages.maxMegapixels.desc':'Largest single image that will be decoded; bigger images are discarded without touching the terminal buffer (1–64 MP, 16 MP is 4096 × 4096).',
'settings.terminal.inlineImages.hibernateNote':'Tabs that have drawn an inline image keep their renderer instead of hibernating, because hibernation restores text only.',
'settings.shortcuts.shellOnlyTabNumberShortcuts.desc':'When enabled, Cmd/Ctrl+[1...9] and Ctrl+Tab switch only work tabs (terminals, workspaces, editors), not the pinned Vault or SFTP tabs.',
'settings.shortcuts.showTabNumberBadges.label':'Show tab number badges',
'settings.shortcuts.showTabNumberBadges.desc':'While holding Cmd/Ctrl, show 1-9 on tabs that match Cmd/Ctrl+[1...9], so you can jump accurately.',
'vault.import.progress.persistFailed':'Imported hosts could not be saved. Free some local storage space and try again.',
'vault.import.progress.rollbackFailed':'The import could not be rolled back safely. Some imported hosts may remain; review the Vault before retrying.',
'vault.import.sshConfig.importOnlyDesc':'One-time import. Changes won\'t sync back to the file.',
'vault.import.sshConfig.managed':'Managed Sync',
'vault.import.sshConfig.managedDesc':'Keep in sync. Changes will be saved back to the file.',
'vault.import.sshConfig.managedDestinationHint':'Managed sync always uses its own managed group; the destination above applies to one-time import only.',
'sftp.transfers.pathConflict.paused':'Another transfer for "{name}" is paused. Resume or cancel it first.',
'sftp.transfers.pathConflict.inProgress':'Another transfer for "{name}" is already in progress.',
'sftp.transferCenter.title':'File Transfers',
'sftp.transferCenter.quitConfirm':'{count} file transfer(s) are unfinished. Progress will be saved and must be resumed manually next time. Quit anyway?',
'settings.sftp.transferConcurrency.desc':'Number of files to transfer in parallel when uploading or downloading folders. Higher values may improve speed but can overwhelm some servers.',
'settings.sftp.transferConcurrency.desc':'Maximum number of files transferred in parallel for the same endpoint, including folder contents and separately selected files. Higher values may improve speed but can overwhelm some servers.',
'settings.sftp.transferPoolIdleTtl.desc':'How long dedicated transfer SFTP connections stay warm after idle. Longer values reduce cold-start delay before the next upload or download. Busy transfers are never closed.',
'settings.ssh.transportIdleTtl.desc':'How long a shared SSH connection stays warm after the last terminal, SFTP browse, bulk transfer, or port-forward lease is released. While warm, reconnecting to the same host can skip re-authentication (including MFA). Active sessions and transfers are never closed.',
'settings.explorerContextMenu.enable':'Show “Open in Netcatty” in folder context menus',
'settings.explorerContextMenu.enableDesc':'Adds a right-click item for folders and folder backgrounds so you can open a local terminal there. Applies immediately; no reinstall needed.',
'settings.jmsDeepLink.enableDesc':'Register Netcatty as the handler for JumpServer jms:// client links. May conflict with the official JumpServer client.',
'settings.sftp.compressedUpload.enableDesc':'Automatically compress folders using tar before transfer. Requires tar support on the server. Falls back to regular transfer if not available.',
'settings.sftp.skipUnchanged.enableDesc':'Skip files whose size and modification time already match the destination (rsync-style). Disabled when replacing a folder into a staging path.',
'ai.providers.baseUrl.anthropicHelp':'Anthropic-совместимый: хост с /v1 или без (например https://gateway.example или https://gateway.example/v1). Проверка и чат используют /v1/models и /v1/messages.',
'ai.providers.skipTLSVerify':'Пропустить проверку TLS-сертификата (для самоподписанных сертификатов)',
'ai.providers.defaultModel':'Модель по умолчанию',
'ai.providers.contextWindow.help':'Оставьте пустым, чтобы использовать значение из списка моделей, если оно доступно; иначе Netcatty применит безопасное значение по умолчанию.',
'ai.providers.contextWindow.error':'Введите положительное целое число или оставьте поле пустым.',
'ai.cursor.description':'Использует Cursor SDK или локальный вход Agent CLI.',
'ai.cursor.detecting':'Обнаружение...',
'ai.cursor.detected':'Доступен',
'ai.cursor.notFound':'Недоступен',
'ai.cursor.path':'Среда:',
'ai.cursor.notFoundHint':'Укажите API-ключ, чтобы включить Cursor.',
'ai.cursor.notInstalledHint':'Cursor SDK не обнаружен.',
'ai.cursor.installStatus':'Cursor SDK',
'ai.cursor.notFoundHint':'Укажите API-ключ или переключитесь на CLI login.',
'ai.cursor.notInstalledHint':'Cursor SDK / Agent CLI не обнаружен.',
'ai.cursor.installStatus':'Среда Cursor',
'ai.cursor.installed':'Обнаружено',
'ai.cursor.notInstalled':'Не обнаружено',
'ai.cursor.modeCli':'CLI login',
'ai.cursor.modeApiKey':'API Key',
'ai.cursor.modeCliHint':'Использует локальный `cursor-agent login` и квоту подписки Auto. Сохранённый API-ключ сохраняется, но в этом режиме не используется.',
'ai.cursor.modeApiKeyHint':'Использует платный Cursor API. CLI login в этом режиме игнорируется.',
'ai.cursor.cliLoginStatus':'CLI login',
'ai.cursor.cliLoginOk':'Вход выполнен',
'ai.cursor.cliLoginAs':'Вход как {{email}}',
'ai.cursor.cliLoginMissing':'Нет входа',
'ai.cursor.cliLoginHint':'Выполните `cursor-agent login` в терминале, затем нажмите Check.',
'ai.codebuddy.envVars.hint':'По одной записи KEY=VALUE на строку, передаются агенту CodeBuddy. Укажите CODEBUDDY_API_KEY или CODEBUDDY_AUTH_TOKEN для аутентификации. Хранятся локально в открытом виде.',
'ai.codebuddy.elicitation.title':'CodeBuddy требуется ваш ответ',
'ai.codebuddy.elicitation.description':'Проверьте запрос, чтобы продолжить текущий ход.',
'ai.codebuddy.elicitation.validation.invalidType':'Поле «{field}» содержит недопустимое значение.',
'ai.codebuddy.elicitation.validation.integer':'Поле «{field}» должно быть целым числом.',
'ai.codebuddy.elicitation.validation.notInteger':'Поле «{field}» должно быть целым числом.',
'ai.codebuddy.elicitation.validation.minimum':'Значение «{field}» должно быть не меньше {limit}.',
'ai.codebuddy.elicitation.validation.maximum':'Значение «{field}» должно быть не больше {limit}.',
'ai.codebuddy.elicitation.validation.minLength':'Поле «{field}» должно содержать не менее {limit} символов.',
'ai.codebuddy.elicitation.validation.maxLength':'Поле «{field}» должно содержать не более {limit} символов.',
'ai.codebuddy.elicitation.validation.minItems':'Выберите не менее {limit} вариантов для «{field}».',
'ai.codebuddy.elicitation.validation.maxItems':'Выберите не более {limit} вариантов для «{field}».',
'ai.codebuddy.elicitation.validation.format':'Поле «{field}» должно соответствовать формату {format}.',
'ai.codebuddy.elicitation.validation.option':'Выберите допустимый вариант для «{field}».',
// AI Grok Build (in-app managed agent — distinct from External MCP client install)
'ai.grok.title':'Grok Build',
'ai.grok.description':'Агент программирования Grok Build от xAI (CLI). Установите Grok CLI, выполните `grok login` или задайте XAI_API_KEY, затем выберите его как внешнего агента.',
'ai.grok.detecting':'Обнаружение...',
'ai.grok.detected':'Обнаружен',
'ai.grok.notFound':'Не найден',
'ai.grok.path':'Путь:',
'ai.grok.notFoundHint':'Не удалось найти grok в PATH. Установите Grok Build CLI или укажите путь к исполняемому файлу ниже.',
'Подключение к Grok через Agent Client Protocol (grok agent stdio). Netcatty MCP внедряется в session/new. Выключите, чтобы использовать исходный headless streaming-json CLI.',
'ai.externalMcp.focusOnHostOpen':'Активировать окно при host_open',
'ai.externalMcp.focusOnHostOpen.description':'Когда MCP-клиент открывает хост, выводить главное окно на передний план. Отключите, чтобы работать без прерываний.',
'ai.externalMcp.silentSessions.description':'Сеансы, открытые ИИ, не отображаются на панели вкладок и не восстанавливаются после перезапуска. Их можно посмотреть в любой момент через панель в трее.',
'ai.externalMcp.sessionIdleTimeout.description':'Автоматически закрывать сеансы, открытые ИИ, после указанного числа минут без операций терминала или файлов.',
'ai.safety.blocklist.reset':'Сбросить по умолчанию',
'ai.safety.blocklist.add':'Добавить шаблон',
'ai.safety.grants.title':'Память разрешений',
'ai.safety.grants.heading':'Правила для режима confirm',
'ai.safety.grants.description':'В режиме confirm действие сначала требует подтверждения. Сохранённые правила автоматически разрешают похожие действия во всех терминальных сессиях, их можно редактировать вручную.',
'ai.safety.grants.heading':'Правила для режима подтверждения',
'ai.safety.grants.description':'В режиме подтверждения действие сначала требует подтверждения. Сохранённые правила автоматически разрешают похожие действия во всех терминальных сессиях, их можно редактировать вручную.',
'ai.safety.grants.empty':'Правил пока нет. Выберите «Всегда разрешать» при одобрении или добавьте вручную.',
'settings.sessionLogs.timestampsDesc':'Добавлять локальное время в начало каждой строки в текстовых и HTML-журналах.',
'settings.sessionLogs.hint':'Журналы сессий сохраняют весь вывод терминала для диагностики и аудита.',
'settings.sessionLogs.clearAll':'Очистить все журналы',
'settings.sessionLogs.clearAllDesc':'Удалить все сохранённые файлы журналов сессий и вложенные папки хостов из папки сохранения.',
'settings.sessionLogs.clearConfirm':'Это приведёт к безвозвратному удалению всех файлов журналов сессий в папке сохранения. Это действие нельзя отменить. Продолжить?',
'settings.vault.showSftpTabDesc':'Показывать отдельный SFTP-вид в верхней панели вкладок. Если скрыто, используйте боковую панель SFTP внутри сессии.',
'settings.vault.autoImportSystemKnownHostsDesc':'При открытии хранилища тихо импортировать записи из OpenSSH known_hosts. Отключите, чтобы держать известные хосты Netcatty отдельно; кнопка «Сканировать систему» по-прежнему импортирует вручную.',
'Показывать контекстное меню, даже когда tmux, vim или другие полноэкранные приложения перехватывают мышь. Когда выключено, правый щелчок передаётся приложению (Shift+правый щелчок всё равно открывает меню).',
'settings.terminal.behavior.autoCloseOnExit':'Автоматически закрывать терминал при выходе',
'Оборачивать вставляемый текст escape-последовательностями, чтобы оболочка отличала вставку от обычного ввода. Отключите, если видите артефакты вида ^[[200~.',
'settings.terminal.behavior.autoUploadClipboardImage':'Авто-загрузка изображений буфера обмена при вставке',
'Когда в буфере обмена есть изображение, вставка в удалённой сессии загружает его по SFTP в каталог .netcatty-paste-images/ в текущем удалённом каталоге и вводит удалённый путь вместо вставки текста.',
'settings.terminal.behavior.shiftEnterNewline':'Текст для Shift+Enter',
'Выберите, когда вкладки сеансов должны следовать заголовкам окна, сообщаемым оболочкой.',
'Выберите, когда вкладки сеансов должны следовать заголовкам окна оболочки и обновлять значки coding CLI. «Отключено» также останавливает распознавание значков.',
'settings.terminal.hostInfoBar.show':'Показывать сведения о хосте',
'settings.terminal.hostInfoBar.show.desc':'Показывать адрес хоста и сведения о сервере над терминалом. Кнопки действий остаются доступными после скрытия.',
'settings.terminal.hostInfoBar.titleMode':'Заголовок сведений о хосте',
'settings.terminal.hostInfoBar.titleMode.desc':'Показывать в панели адрес подключения (user@host) или метку хоста из хранилища.',
'settings.terminal.rendering.renderer.desc':'Выберите технологию рендеринга терминала. В режиме "Авто" на устройствах с малым объёмом памяти будет использоваться DOM. Изменения применяются к новым терминальным сессиям.',
'settings.terminal.inlineImages.enabled.desc':'Показывать настоящие изображения, которые выводят удалённые программы (kitten icat, chafa, предпросмотр в файловых менеджерах и TUI). Изменения применяются к новым терминальным сессиям.',
'settings.terminal.inlineImages.iip.desc':'Принимать последовательности OSC 1337 из iTerm2 (PNG, JPEG, первый кадр GIF).',
'settings.terminal.inlineImages.storageLimit':'Кэш изображений на терминал',
'settings.terminal.inlineImages.storageLimit.desc':'Сколько памяти терминал может занимать декодированными изображениями до вытеснения самых старых (8–512 МБ).',
'settings.terminal.inlineImages.maxMegapixels':'Максимальный размер изображения',
'settings.terminal.inlineImages.maxMegapixels.desc':'Наибольшее изображение, которое будет декодировано; более крупные отбрасываются без изменения буфера терминала (1–64 Мпикс, 16 Мпикс — это 4096 × 4096).',
'settings.terminal.inlineImages.sequenceLimit':'Максимальный размер последовательности',
'settings.terminal.inlineImages.sequenceLimit.desc':'Наибольшая escape-последовательность изображения, принимаемая до начала декодирования (1–64 МБ).',
'settings.terminal.inlineImages.hibernateNote':'Вкладки, показавшие встроенное изображение, сохраняют свой рендерер и не уходят в спящий режим, потому что снимок восстанавливает только текст.',
'settings.shortcuts.disableTerminalFontZoom.desc':'Отключает быстрый масштаб текста в терминале, включая Cmd/Ctrl + колесо мыши.',
'settings.shortcuts.shellOnlyTabNumberShortcuts.label':'Сочетания без закреплённых вкладок',
'settings.shortcuts.shellOnlyTabNumberShortcuts.desc':'Если включено, Cmd/Ctrl+[1...9] и Ctrl+Tab переключают только рабочие вкладки (терминалы, рабочие области, редакторы), а не закреплённые Vault и SFTP.',
'settings.shortcuts.showTabNumberBadges.label':'Номера на вкладках',
'settings.shortcuts.showTabNumberBadges.desc':'Показывать 1-9 на вкладках при удержании Cmd/Ctrl, чтобы точнее переключаться через Cmd/Ctrl+[1...9].',
'vault.groups.deleteDialog.desc':'Группа будет безвозвратно удалена, а все хосты будут перемещены в корень.',
'vault.groups.deleteDialog.managedDesc':'Это управляемая группа SSH-конфига. При её удалении также будут удалены все хосты и снята связь с исходным файлом.',
'vault.groups.deleteDialog.deleteHosts':'Также удалить все хосты в этой группе',
'scripts.trigger.onConnect':'Запуск при подключении',
'scripts.trigger.onOutput':'Запуск при совпадении вывода',
'scripts.trigger.onOutputHint':'Срабатывает, когда вывод сервера совпадает с шаблоном (эхо нажатий клавиш игнорируется). Если целевые хосты не настроены, отслеживается текущая подключённая сессия; иначе — только указанные хосты. Не работает в приложениях альтернативного экрана, таких как vim или htop. Не запускается, пока в этой сессии выполняется другой скрипт; после его завершения проверка повторяется. Для ожидания внутри сценария используйте nct.screen.waitForText или nct.screen.waitForRegex в коде скрипта.',
'scripts.actions.save':'Сохранить',
'scripts.actions.runNow':'Запустить',
'scripts.actions.openEditor':'Открыть редактор',
'scripts.actions.openEditorHint':'Редактировать скрипт в большом окне',
'scripts.editor.modalTitle':'Редактор скриптов',
'scripts.editor.modalSubtitle':'Редактируйте метаданные и код скрипта в большом рабочем пространстве.',
'scripts.editor.lineCount':'{count} строк',
'scripts.editor.resize':'Изменить размер редактора',
'scripts.targets.hint':'Выбранные группы разрешаются динамически, поэтому новые хосты включаются автоматически.',
'scripts.targets.connectOrderHint':'Порядок запуска скриптов подключения настраивается для каждого хоста в разделе «Сведения о хосте → Автоматизация».',
'scripts.targets.currentHostMismatch':'Этот скрипт не назначен текущему хосту.',
'scripts.recording.startHint':'Записывайте действия в активном терминале и создавайте код скрипта nct',
'scripts.recording.unavailableHint':'Запись здесь недоступна — используйте панель скриптов справа',
'scripts.recording.activeHint':'Идёт запись этого терминала. Вводите команды как обычно; нажмите «Остановить» или кнопку REC на панели инструментов, чтобы завершить и сохранить запись.',
'scripts.recording.started':'Запись начата — работайте в терминале',
'scripts.recording.noSession':'Сначала подключите терминал (одну вкладку или рабочее пространство)',
'scripts.recording.alreadyActive':'В другом терминале уже идёт запись',
'scripts.recording.helpIntro':'Запись превращает ваши действия в терминале в повторно используемый сценарий автоматизации — удобно для развёртываний, проверок состояния и других повторяющихся задач.',
'scripts.recording.helpStep1':'Подключитесь к хосту. Подойдут как отдельная вкладка терминала, так и терминал в рабочем пространстве — откройте панель скриптов справа.',
'scripts.recording.helpStep2':'Нажмите «Начать запись». Красный значок REC на панели инструментов терминала означает, что запись активна.',
'scripts.recording.helpStep3':'Вводите команды в терминале как обычно. Каждое нажатие Enter сохраняется как отдельный шаг.',
'scripts.recording.helpStep4':'Закончив работу, нажмите «Остановить запись» или кнопку остановки рядом с REC на панели инструментов терминала.',
'scripts.recording.helpStep5':'Укажите название и сохраните скрипт. Выберите «Сохранить и редактировать», если хотите доработать созданный код в редакторе.',
'scripts.recording.helpTipsTitle':'Советы',
'scripts.recording.helpTip1':'Паузы между действиями дольше одной секунды записываются как время ожидания, чтобы воспроизведение не было слишком быстрым.',
'scripts.recording.helpTip2':'После каждой команды запись ждёт приглашения оболочки (например, $ или #), прежде чем перейти к следующему шагу.',
'scripts.recording.helpTip3':'Ввод пароля отмечается как конфиденциальный и не сохраняется в скрипте открытым текстом.',
'scripts.recording.helpTip4':'Для vim, меню и других интерактивных сценариев сначала запишите основные команды, а затем доработайте скрипт вручную.',
'hostDetails.automation.equivalenceHint':'Связь добавляет этот хост в список целей скрипта в разделе «Скрипты». Оба представления синхронизированы.',
'hostDetails.automation.queueHint':'Скрипты запускаются по порядку при подключении этого хоста. Сначала выполняются глобальные скрипты, затем эта очередь.',
// Network Device Mode auto-detection tip (session header)
'terminal.networkDevice.tip.message':'Похоже на сетевое устройство. Включите режим сетевого устройства, чтобы команды отправлялись как есть (без обёртки оболочки).',
'terminal.networkDevice.tip.action':'Включить',
'terminal.networkDevice.tip.dismiss':'Скрыть',
'terminal.networkDevice.tip.enabled':'Режим сетевого устройства включён для {host}',
'vault.import.progress.persistFailed':'Не удалось сохранить импортированные хосты. Освободите место в локальном хранилище и повторите попытку.',
'vault.import.progress.rollbackFailed':'Не удалось безопасно отменить импорт. Некоторые хосты могли остаться в хранилище; проверьте его перед повторной попыткой.',
'vault.import.sshConfig.managedDestinationHint':'Управляемая синхронизация всегда использует отдельную группу; выбранная выше группа применяется только к разовому импорту.',
'vault.import.sshConfig.managedSuccess':'Импортировано {count} хостов. Файл теперь находится под управлением.',
'vault.import.sshConfig.alreadyManaged':'Этот файл уже находится под управлением.',
'vault.import.sshConfig.alreadyManagedDesc':'Этот файл уже управляется в группе "{group}". Если хотите импортировать его заново, сначала удалите существующий управляемый источник.',
'sftp.transfers.pathConflict.paused':'Другая передача «{name}» приостановлена. Сначала возобновите или отмените её.',
'sftp.transfers.pathConflict.inProgress':'Другая передача «{name}» уже выполняется.',
'sftp.transferCenter.title':'Передача файлов',
'sftp.transferCenter.quitConfirm':'Незавершённых передач: {count}. Прогресс будет сохранён, а после следующего запуска передачу нужно продолжить вручную. Выйти?',
'settings.sftp.transferConcurrency.desc':'Количество файлов, передаваемых параллельно при загрузке или скачивании папок. Более высокие значения могут ускорить работу, но способны перегрузить некоторые серверы.',
'settings.sftp.transferConcurrency.desc':'Максимальное число файлов, одновременно передаваемых для одной цели, включая содержимое папок и отдельно выбранные файлы. Большие значения могут ускорить передачу, но перегрузить некоторые серверы.',
'settings.sftp.transferPoolIdleTtl.desc':'Как долго простаивающие SFTP-соединения пула передачи остаются открытыми. Большее время снижает задержку перед следующей передачей. Активные передачи не закрываются.',
'settings.ssh.transportIdleTtl.desc':'Как долго общее SSH-соединение остаётся открытым после освобождения последнего lease терминала, SFTP-браузера, пакетной передачи или проброса портов. Пока оно тёплое, повторное подключение к тому же хосту может обойти повторную аутентификацию (включая MFA). Активные сессии и передачи не закрываются.',
'settings.ssh.transportIdleTtl.1m':'1 минута',
'settings.ssh.transportIdleTtl.5m':'5 минут',
'settings.ssh.transportIdleTtl.15m':'15 минут',
'settings.ssh.transportIdleTtl.30m':'30 минут',
'settings.ssh.transportIdleTtl.never':'До выхода из приложения',
'settings.sftp.defaultOpener':'Приложение для открытия по умолчанию',
'settings.sftp.defaultOpener.desc':'Выберите приложение по умолчанию для открытия файлов без конкретной ассоциации',
'settings.explorerContextMenu.enable':'Показывать «Open in Netcatty» в контекстном меню папок',
'settings.explorerContextMenu.enableDesc':'Добавляет пункт в меню папок и фона папки, чтобы открыть локальный терминал в этом каталоге. Применяется сразу, переустановка не нужна.',
'settings.jmsDeepLink.enableDesc':'Зарегистрировать Netcatty как обработчик клиентских ссылок JumpServer jms://. Может конфликтовать с официальным клиентом JumpServer.',
'settings.sftp.compressedUpload.enableDesc':'Автоматически сжимать папки с помощью tar перед передачей. Требует поддержки tar на сервере. Если она недоступна, будет использована обычная передача.',
'settings.sftp.skipUnchanged.enableDesc':'Пропускать файлы, у которых размер и время изменения уже совпадают с назначением (в стиле rsync). Отключается при замене папки во временный путь.',
// Quick Switcher
'qs.search.placeholder':'Поиск хостов или вкладок',
'hostDetails.agentForwarding.agentNotRunningHint':'SSH Agent не обнаружен. Включите OpenSSH Authentication Agent в службах Windows или используйте совместимый агент, например Bitwarden, 1Password или gpg-agent.',
'hostDetails.agentForwarding.agentNotRunningHint':'SSH-агент не обнаружен. Включите OpenSSH Authentication Agent в службах Windows или используйте совместимый агент, например Bitwarden, 1Password или gpg-agent.',
'hostDetails.legacyAlgorithms.desc':'Включить устаревшие SSH-алгоритмы (diffie-hellman-group1, ssh-dss, 3des-cbc и т. д.) для подключения к старому сетевому оборудованию.',
'hostDetails.legacyAlgorithms.warning':'У этих алгоритмов есть известные слабые места безопасности. Включайте только для устаревших устройств, которые не поддерживают современную криптографию.',
'hostDetails.skipEcdsaHostKey.desc':'Некоторые старые коммутаторы Huawei / Cisco выдают нестандартные подписи ECDSA host-key, из-за чего соединение падает с "signature verification failed". Включение этой опции убирает все ecdsa-sha2-* из предложения клиента, и согласование переходит к RSA / Ed25519.',
'hostDetails.algorithms.advanced.desc':'Заменить предлагаемый список алгоритмов для любой категории для конкретного хоста. Не трогать категорию = использовать значение по умолчанию; выбранное подмножество полностью заменяет список по умолчанию. Неверные значения могут сделать хост недоступным.',
'hostDetails.keepalive.desc':'Использовать для этого хоста собственную политику keepalive вместо глобальной настройки. Полезно для старых маршрутизаторов и коммутаторов, чей SSH-сервер не отвечает на запросы keepalive@openssh.com. Установите интервал 0, чтобы полностью отключить keepalive для этого хоста.',
Loaded 100 of 1702 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.