Reuse the selection-token snapshot comparison for list and install, including a rejected stale or withdrawn selection. Preserve synchronous unavailable-source validation before the asynchronous list operation. The loaded-row regression rejects installation without downloading or executing a package.
Apply the exact six-file patch from implement_plugin_row_layout, based on 0c05a96fa1. Patch SHA-256: 5c58150fc3a7f72169b1a23ad1410cf37c51051b29bd6600bf30e848a9f366d6. Native visual acceptance remains pending.
A peer FIN can leave readyState closed while destroyed is still false.
Concurrent tool calls wrote into that socket, hit writeAfterFIN, and
failed with "TCP connection lost" instead of sharing one reconnect.
Treat that socket as down, and make later callers join the in-flight re-auth.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Keeping the restore guard for registration-time chrome setup is right, but
it also swallowed explicit setTheme/setBackgroundColor pushes: windows that
had never enabled blur stopped repainting their native backing, leaving the
color stale after theme changes on every platform.
Separate the two call kinds with an explicit forceBackgroundColor flag so
registration still preserves the window's creation-time color while theme
and background-color pushes always land.
Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses review feedback on the frosted glass background setting.
- Detached #/session-window renderers run with settings sync disabled, so
`applySharedWindowSetting` now forwards the blur key. Previously the main
process put native vibrancy on those windows while their renderer class
stayed stale, leaving them opaque with blur on and double-applying opacity
with blur off.
- Only settings-owning windows persist the blur preference and drive
`setWindowBackgroundBlur`, matching the other OS-effect guards. Detached
windows still apply the transparent backing class locally.
- Search entries can declare `platforms`; the macOS-only blur setting is
filtered out on Windows/Linux so a hit never jumps to a tab whose anchor is
not rendered (including the default empty-query list).
- Restoring chrome only rewrites the background of windows this module made
transparent, falling back to the active theme color when no theme color has
been pushed yet.
Co-authored-by: Cursor <cursoragent@cursor.com>
The two-line idle prompt check only accepted a classic user@host host
line, so Oh My Zsh ys (`# user @ host ...` then a bare $/#) never became
ready and terminal_execute sent nothing. Match that host line narrowly
and still reject a lone marker.
Fixes#4007
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): support ChaCha20 in Electron crypto runtimes
* test(ssh): verify packaged cipher transport across platforms
* test(ssh): isolate unused Electron UI APIs in packaged transport harness
* test(ssh): isolate packaged dependency resolution from the checkout
* fix: clear keychain ref when remembering KI password change
Keyboard-interactive saves wrote the new host password but left
identityId attached. resolveHostAuth prefers the shared identity
password, so the next login kept the stale secret.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: snapshot keychain username when remembering KI password
resolveHostAuth prefers identity.username. Detaching the identity
without copying that username left the host's stale account next to
the new password.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: fill blank username only when detaching KI keychain identity
detachEffectiveHostIdentity copies the effective username only when the
host username is blank. Always replacing a set username went past that
helper.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: snapshot keychain username when remembering KI password
Clearing identityId left a different host username next to the new
password. Copy the identity username onto the host whenever it is
non-empty, matching the vault password detach.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(ssh): exercise KI remember input and reconnect
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Alternate-screen programs still skip line timestamps; collapse the empty left gutter while alt buffer is active and restore on normal screen.
Fixes#3972
Related #3958 / follow-up to #3961.
hasExpectedPromptSuffix must match live PTY \r\r\n against a cached two-line idle prompt (CR→LF form) so cancel/completion matching works after readiness.
No release / no tag.
Fixes#3958. extractTrailingIdlePrompt now accepts two-line user@host[:path] + bare $/ # idle prompts (including CR-normalized live PTY form) so MCP terminal_execute getReadyPrompt no longer times out.
No release/tag.
Idle SSH Bash prompts that start with a Python virtualenv, conda, or BT-panel name such as `(3.12.12) ` were not recognized. Recognition now strips one optional leading `(name) ` prefix, then tests the existing POSIX and Alpine prompt patterns.
Fixes#3947
Keyword highlighting no longer overwrites cells whose PTY foreground is already 16/256/truecolor (Starship directory segments). Default-foreground matches still get the unix-path purple.
Fixes#3940
Add `sfmono` to KNOWN_MONOSPACE_FONTS so glued typographic family names like "SFMono Nerd Font" appear in the Settings → Terminal font picker. Matching logic unchanged.
Fixes#3929
Fixes#3927
Gate: Codex thrash (mid-CSI truncate edge) after real P2 alt-screen fix; CI lint-and-test red only on unrelated TERM-SSHD flake; smoke PASS /workspace/netcatty-smoke-shots/pr-3928-20261025-223624.
Recognize Alpine ash default host:path prompts (e.g. localhost:~# ) so AI/pty exec readiness accepts idle Alpine sessions without changing PS1.
CI lint-and-test red on tip was unrelated: TERM-SSHD failed transfer releases the physical channel (transferBridge.termSshd.test.cjs). Local shellUtils + readiness suite and synthetic Alpine prompt smoke PASS.
The Spanish locale had drifted 163 keys behind English, so those strings fell back to English through the default-locale resolution in I18nProvider. English was also missing 42 of the 43 shortcut binding labels that DEFAULT_KEY_BINDINGS declares.
Add the missing Spanish messages for App Lock, FIDO2 security keys, Kubernetes statuses, host connection test, AI provider headers, OpenCode 2, keychain generation, launch at login, terminal layout and progress, and system manager runtime errors. Add the missing English shortcut labels using the canonical label field in DEFAULT_KEY_BINDINGS.
Harden the locale tests so this cannot drift silently again: include es and zh-TW in the App Lock coverage check, include en in the shortcut label check, and add a parity test that pins es to en for keys and interpolation placeholders while keeping the remaining gaps for ru, zh-CN and zh-TW tracked in an explicit pending list.
Restored SSH tabs reconnect after app restart but skipped the host startup command because the boot effect closed over a pre-vault host snapshot. Prefer hostRef for SSH when the dial target matches.
Fixes#3905
A pending tool switch stored an intermediate focused-tool map in the ref.
An urgent open on another tab then updated only state, and the identity
guard never copied that tab back, so a later click on the old tool was
ignored. Committed entries now catch up without dropping a write that is
still ahead of the last render.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
A keep-count change during victim drain was still applied from the count
captured when the leave started, so a zero setting could soft-hide the
leaving tab with no later timer to correct it. Retention now uses the live
count, and an already soft-hidden tab re-enters hibernate when the limit hits
zero.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): soft-hide the tab that just left when hibernate keep is full
Evict the oldest soft-hidden session to a full snapshot, and keep the
session the user just left on hideRuntimeOnly. Disconnected sessions,
alternate screen, and inline images keep their existing branches. Wake
replay order is unchanged.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): full-hibernate when keep-count eviction is refused
A soft-hidden session with inline images consumes the eviction request
and stays mounted. Soft-hiding the tab that just left anyway let that
protected session pin the oldest slot, so every later tab was retained
too. Walk oldest-first, keep the leaving tab soft-hidden only after one
victim actually leaves the set, and full-hibernate it when none can.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): retain a leaving tab only after eviction hibernates
Waking a soft-hidden victim clears its marker before fullHibernateRuntime
finishes. Treating that as success soft-hid the tab that just left even
when drain, snapshot, or alternate-screen failure restored the victim.
Await hibernate completion outside the pure keep-count helper, and put a
failed victim back in the soft-hidden set so it stays a candidate.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): reserve hibernate slots outside domain policy
Keep the keep-count decision pure. The application walk reserves the
leaving tab before awaiting a victim, rechecks that slot before
soft-hide, and does not restore a victim that became visible or had
hibernate turned off while the upgrade was in flight.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* docs(terminal): keep activeTabId in workspace ctx equality
Solo tab switches already leave TerminalPanesHost equal when the sessions
ref is unchanged. Reveal flush and repaint still run on the visible pane,
so removing activeTabId would not move the measured settle time.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* docs(terminal): use an ASCII arrow in the workspace ctx comment
AGENTS.md keeps comments ASCII-only. The keep-activeTabId note used a
Unicode arrow.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf(terminal): commit side-panel tool switches in one render
Icon clicks write the focused tool and the layout together through
selectSidePanelTool. External open-tab writes still reconcile in an effect,
and that effect skips when the layout is already the selection.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): merge pending side-panel writes across urgent renders
Icon switches inside startTransition used to publish absolute maps from
refs, and the next render copied the last committed state back over
those refs. A second tab's switch could then replace the queued map and
drop the first tab's selection and split. Patch each tab onto the queued
map, and leave a pending ref in place until that render commits.
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 the notes tree into a memo pane whose equality tracks notes, groups,
selection, editing, and expansion. Hide still flushes the draft through the
existing ref and leaves the editor mounted.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Notes and SFTP already park behind a hidden class. History returned null,
so each Notes to History switch remounted the virtual list. Keep that tree
mounted, subscribe to shell history only while it is visible, and remeasure
the list when it is shown again.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): preserve successful uploads with backup cleanup warnings
Warn only on confirmed backup cleanup permission refusal after final publication. Preserve actionable warnings across transfer events, history restoration and completion ordering without masking deletion or commit failures.
Narrow follow-up to #3830; related #3879.
* fix(sftp): retain folder cleanup warnings before child completion
* feat(serial): support local Windows named pipe consoles
* test(serial): synchronize pipe acceptance and cover saved-host form
* fix(serial): preserve named-pipe paths with spaces when editing hosts
* test(serial): verify Windows pipes through native Electron IPC
* test(serial): keep Electron listener callbacks in renderer context
* test(serial): cover busy pipe cancellation and bounded slow-reader writes
* test(serial): construct single-instance fixture pipe path correctly
Preserve the original PR #3885 commits and integrate latest main.
Resolve local shell defaults, acknowledge committed workspace insertion, and preserve background readiness codes.
Fixes#3886
Sub-threshold tab pointer moves no longer lose activate: dragstart is not cancelled; reorder still arms at ≥4px; mouseup/dragend activate when click was cancelled and no reorder started.
Fixes#3886
Primary-button mousedown on plugin/editor/session/log-view top-tab close buttons now preventDefault+stopPropagation so HTML5 drag on the parent tab cannot swallow the click.
The list-models pipeline already served LKG on failure; this wires the
rest of the LKG-first, manual-refresh policy end to end.
Main: the list-models IPC accepts a force flag — manual refresh always
skips the in-memory cache and the persisted entry and goes live, and a
forced success overwrites the persisted catalog.
Renderer: the panel passes force only from the retry banner and the
picker's refresh button; panel-visibility and send-preflight loads stay
non-force and replay the renderer cache, so with a persisted catalog
main answers them without spending the live-fetch budget.
Picker: a footer refresh icon-button next to the status chip, visible
whenever any fetched catalog (live or last-known-good) or the degraded
warning is present; the spinner row remains the in-flight feedback.
Tooltip localized as ai.chat.refreshModelList in en/es/ru/zh-CN/zh-TW.
Fresh installs are unchanged (no persisted entry → live fetch as
before); existing users stop seeing jitter warnings on panel open and
pick up new CLI models via the refresh button.
A failed live catalog fetch serves the persisted last-known-good catalog
with a warning, and every TTL-expired automatic load re-attempted live —
so a backend whose discovery keeps failing re-armed the renderer's
「暂时无法更新实时模型列表」 banner forever, even though the persisted
models worked and selecting one never touched the banner state.
Serve the persisted last-known-good catalog without a live attempt on
every non-force load (the LKG-first policy, decided at the single
fetchSdkModelCatalog choke point): automatic loads now return a clean,
warning-less serve, so the next automatic load after the renderer's TTL
clears a stale banner. Warnings are reserved for an explicit force
refresh that fails, and for a first fetch with neither cache nor
persisted entry. The persisted current-id remap moves into a helper
shared by both serve paths.
Do not fall back to a fresh GUI login when a dedicated connection rejects.
Capture capability selection before awaiting so source cleanup cannot change
the failure policy. Preserve ordinary reusable-channel fallback and cover
both paths with real bridge regression tests.
Preserve PR #3875's original author commits and scoped TERM-SSHD transfer policy.
Copy persisted filename encoding into dedicated file connections. Capture only
host identities actually accepted by the terminal route, including jump hops,
and fail closed on later unverified changes without orphan host-key prompts.
Add first-trust, changed-key, route-scope and encoding regressions.
Deduplicate terminal reveal and focus recovery while preserving changed-layout and explicit resume/fullscreen recovery. Add behavioral regressions.
Related to #3825; this rendering optimization does not establish that the original Windows/macOS issue is resolved.
A managed tool call that races bridge shutdown was still in flight, so
the socket error rejected it with "TCP connection lost". Managed
children do not reread discovery; report that race as not connected
and leave external in-flight wording unchanged.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
When NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE is set, rpcCall recovers on demand via ensureConnected(): re-read discovery, reset TCP buffer, connect to current port, auth/verify with refreshed token. Concurrent callers coalesce. Close/error reject in-flight RPCs without replaying. Managed SDK children keep fail-fast.
Fixes#3871
* feat(shortcuts): toggle the host list with Ctrl/Cmd+Shift+B
Ctrl/Cmd+B already switches broadcast mode and stays available to a focused terminal. The new shortcut reuses the existing host-tree toggle.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(shortcuts): let the host list chord through Monaco and idle terminals
Editor focus inside Monaco can toggle the host list. The chord is consumed only when that toggle runs, matching broadcast.
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(terminal): split compose-bar send target from toolbar broadcast
Toolbar broadcast still fans out keystrokes, paste, and terminal-path
snippets. The compose-bar indicator is now a toggle for whether Enter
sends to broadcast peers or only the active session. Turning the toolbar
switch on or off resets that target to follow it; later flips stay
independent.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep active-only compose sends off the toolbar fan-out
The focused workspace executor omitted broadcast, so executeSnippetCommand
fell back to the toolbar switch and still wrote every peer. Pass
broadcast: false on that path.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf(vault): store connection-log replay blobs in IndexedDB
Move unsaved connection-log terminal replay out of the multi-megabyte
localStorage blob into one IndexedDB row per log id. Persist only the
changed ids, keep the newest 50, and signal other windows with a tiny
revision key.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): keep connection-log replay across boot races and IndexedDB failure
Re-read connection-log metadata after the IndexedDB load before pruning,
return the capture promise through TerminalLayer, and write the unpruned
log blob when the side store rejects the commit.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): keep unmigrated connection-log replays until IndexedDB commits
Copy legacy replay ids that are missing from IndexedDB before deleting
the old blob, and do not treat an uncommitted legacy map as the diff
baseline.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): migrate legacy replays without clearing newer IndexedDB rows
Insert missing legacy ids in one transaction instead of replaceAll, so a
replay committed after the empty read is not wiped.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): rebase the IndexedDB fallback onto the latest connection logs
When the side store rejects a close, attach this window's replay to the
connection-log blob currently on disk instead of writing the stale snapshot.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): rebase queued connection-log writes onto the latest logs
A persist task can run after another window changes the log list. Write
the rebased list instead of the snapshot captured when the task was queued.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): keep queued connection logs after a remote snapshot
A storage event can replace memory with another window's logs before the
queued write runs. Keep ids from the queued snapshot unless this snapshot
deleted them.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): restore only locally changed queued logs
A queued persist was putting back every id from the pre-queue snapshot,
which undid another window's delete of a log this recipe never touched.
Track the ids the recipe added or replaced, and reload IndexedDB during
boot when the revision key moves across the migrate await so that peer
replay is hydrated before this window marks the revision seen.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): rebase connection logs after the side-store await
A non-pruned write stored the pre-await snapshot and could replace logs
another window saved during the IndexedDB round trip. Rebase immediately
before that write, shrink the quota fallback until the newest replay fits,
and leave the revision unconsumed when IndexedDB cannot be read.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): ignore same-window connection-log echoes
The deferred localStorage notification replays this window's own metadata
write. Applying it replaced connectionLogsRef with an older blob and dropped
logs that were added or finalized while that write was still queued.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): do not restore a queued log deleted from memory
A queued add or update was written back when the id was already gone from
memory, which let another window keep that row after a later local delete.
Rebase now applies a changed id only while memory still has it.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Attach close preparation clears the popup session ref, then unmount
cleanup called closeSession. That emits netcatty:exit to the home
renderer and can kill the shared PTY. Skip that second close.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
A timed-out SFTP OPEN used to pin the connection forever when its callback
never arrived. Wait up to 2s, then force-clear the gate and admit a new open.
Late channels are still closed. After TIMEOUT or PENDING, skip the fastPut
reopen and upload on the shared channel.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
canRetryFromCheckpoint and the upload prefix digest missed ECONNABORTED,
socket hang up, ERR_SFTP_CHANNEL_CLOSED, and client-socket. Those Retries
restarted at byte 0 and stranded the staged .part.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
New-file uploads have a null existingMode, so restoreRemoteMode is a no-op.
Recheck the destination only after a real mode restore and keep one
pre-rename safety lstat otherwise.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat: open saved session logs from terminal toolbar
Add one-click access to the current automatic or manual session log, flush before opening, and reuse application selection with a remembered log preference. Preserve recording controls and current-session ownership across worker routing and reconnects.
Refs #3807
* fix(logging): preserve saved logs on failed reconnect and localize failures
Address both Codex review findings with generation- and identity-checked replacement cleanup and localized user-facing error handling.
* fix(logging): guard successful main-process session replacement
Add shared inactive-log cleanup to fallback start handlers with regression coverage. Registration activation follows separately.
* fix(logging): enable log lifecycle cleanup for main-process fallback
* fix(terminal): keep log cleanup and tuck open-log into More actions
Always end a raw session-log write stream on finalize, including when a
flush already disabled it. Keep immediate snapshot retries, and pause
them only while prepare is waiting so a disk error can be reported.
Show open, reveal, and open-with in the toolbar overflow menu when
logging is active, instead of a default toolbar button.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(logging): back off failed snapshot writes and drop retired log actions
Persistent snapshot failures retry on a growing delay instead of a tight
loop, and still flush immediately when new output arrives. Hide open-log
actions after a successful reconnect retires a stopped manual log.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep open-log actions after automatic logging is turned off
The current automatic log keeps recording until reconnect, so the menu
stays available for that connection and clears on the next successful
connect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): derive log menu availability from saved session logs
* fix(terminal): nest session log actions in one More actions submenu
Collapse Open, Show in folder, and the two open-with actions into a Session log submenu that follows the Terminal Encoding menu, and drop the remembered-application hint.
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(cli): show the final server on PuTTY bastion tab titles
PuTTY jump launches kept the bastion as the TCP host and labeled the tab
with the opaque login string. Reuse the SecureCRT tabName path so an
embedded or leftover target address is the session title, without changing
where the session connects.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(cli): validate PuTTY jump tab addresses with net.isIP
The display-host check accepted malformed colon strings such as a:b:c and
rejected IPv4-mapped and zone-scoped IPv6 targets. Use Node's IP parser so
only real addresses become the tab title.
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): close sessions when their owning renderer is destroyed
* test(terminal): keep a shared SSH transport when a copied window closes
Closing a child window must drop only the shells that window owns.
Cover several tabs on one connection split across the main window and
the copied window, for both the in-process lifetime tracker and the
worker close path.
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(browser): keep the page live beside overlay side panels
The embedded host browser is a native WebContentsView that draws above
the renderer, and its occlusion check only yielded to dialogs, menus and
poppers. Opening New host / Edit host from the terminal host tree left
the page drawn over the overlay host editor panel.
Overlay-layout aside panels now carry data-native-view-occluder. Native
views cannot be clipped by a parent View on macOS, so the pane shrinks
the live page to the rectangle left beside a panel that covers a
full-height or full-width edge strip; the page stays interactive while
the editor is open. Poppers are checked against that live rectangle, so
selects inside the panel no longer freeze the page, and pictures for
poppers over the page are offset to where it sits. A panel over the
middle of the pane, or one leaving less than 160px, falls back to a
picture.
* test(browser): provide the pane geometry helper to the first-link harness
The first-link acceptance loads useHostBrowserPanel through a VM that
strips imports and injects dependencies by name, so it threw
"resolveHostBrowserPaneBesidePanels is not defined" once the hook
started using the domain helper. Load domain/hostBrowser.ts and pass
the helper in.
* fix(vault): keep the newest connection-log replays when the side store hits quota
Once the replay side store neared the localStorage quota (~48 x 1M-char
entries) every write failed with QuotaExceededError, so the stored map
froze with the oldest sessions and newer ones never survived a restart.
On a failed write, retry without the oldest unsaved replay buffers
(dropping 1, 2, 4, ... per attempt, so retries stay O(log n)) until it
fits or none are left. Storage format, per-entry cap, 50-entry cap and
log metadata are unchanged; saved logs and other windows' ids are never
dropped.
* perf(vault): start quota retries from the newest set that last fit
* fix(vault): keep the connection-log quota hint on the vault hook
The domain helper held a process-global hint, so the same logs and map
could keep a different replay set after an earlier caller. The hint now
lives in a useVaultState ref and is passed in and returned. Drop order,
saved and unknown ids, and the size estimate are unchanged.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Grok Bot <grokbot@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Settings, tray, and the terminal popup mount useVaultState but never
show connection-log replay. Skip the bulky side-store load and storage
handlers there so a main-window persist does not JSON.parse it in those
renderers. Main-window behaviour and the write path stay unchanged.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Unsaved logs kept up to 500×1M chars of terminalData in React state while
only 50 survive a restart. After add/update/toggle, drop terminalData from
the oldest unsaved entries beyond MAX_PERSISTED_UNSAVED_TERMINAL_DATA_ENTRIES.
List metadata stays; replay beyond 50 matches restart behaviour.
When the side-store write fails (QuotaExceeded once it nears ~50 MB), keep
the same capped merged map in memory instead of accumulating every log's
replay buffer in connectionLogTerminalDataRef.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Each connection-log write parsed the closed-session terminal-data JSON
twice, merged it twice, and JSON.stringify-compared it twice. Pass the
already-merged map through and compare keys without serializing the
multi-MB replay buffers. Storage format and in-memory state stay the same.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
The alt-screen preview tracker segmented every print span and recomputed
the last grapheme on every non-ASCII append. Skip the boundary walk when
the remainder already fits the piece cap, and segment the open grapheme
only when a later span actually rejoins it.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
ssh2 re-runs hostVerifier on every key re-exchange. When the knownHosts
snapshot for the connection doesn't contain the key (first connect with
"Add and continue", "Continue" without saving), the verifier showed the
host-key dialog again and answered asynchronously. While that answer is
pending ssh2 keeps sending channel data ahead of its NEWKEYS, and OpenSSH
drops the connection ("Bad packet length"), killing the shell, rz/sz and
SFTP on that transport. Servers or gateways with a RekeyLimit hit this
after a few MB of transfer.
Like OpenSSH, accept a rekey only if it presents the key already accepted
for this connection, and answer synchronously without prompting.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Copy style state on SGR changes rather than for each printable character. Preserve historical styles, cleared screens and cursor overwrites without dropping log content.
* docs: losslessly shrink README images and mirror Trendshift badge
Recompress local images linked from the READMEs only when the file gets
smaller and decoded pixels stay identical. Copy the Trendshift badge from
README.md into the zh-CN, zh-TW, and ja-JP READMEs.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* docs: replace README screenshots with lossless WebP
Store the ten README screenshots as lossless WebP under docs/assets/readme
and point every language README at those files. Keep them out of app.asar.
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): Retry after a dropped connection resumes from the staged checkpoint
Retry on a failed live task always cloned it under a fresh transfer id with
checkpoint 0. After a connection drop that throws away the uploaded bytes, and
because the remote stage path is derived from the transfer id, the new attempt
stages a new .part while the old one stays orphaned on the server (cleanup runs
against the dead session).
When a top-level file failed with a session-loss error and has a checkpoint,
Retry now demotes it to interrupted/reconnectRequired and goes through the same
dedicated reconnect-resume path a restored task uses: same id, same stage,
truncated to the checkpoint. Content errors and orphaned rows keep the existing
restart-from-0 contract.
* fix(sftp): resume a dropped upload from the verified stage checkpoint
A failed upload never pauses, so Retry has a checkpoint and no SHA-256.
The bridge treated that as an unproven resume and restarted at byte 0,
truncating the same .part. Stat and trim the stage on the new connection
instead, and still restart when the .part does not cover the checkpoint.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): trim a resumed upload stage with SFTP setstat
ssh2 has no ftruncate and ssh2-sftp-client has no truncate, so a longer
.part after a reset always failed the checkpoint trim. SETSTAT {size},
or FSETSTAT on an r+ handle, shortens the stage on the live channel.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): require a stable local source before an unfingerprinted resume
A dropped upload has a checkpoint and no SHA-256. Retry kept that
checkpoint even after the local file was rewritten at the same size, and
the same predicate also resumed downloads and remote copies.
Remember size and mtimeMs when the local upload starts. On retry, keep
the checkpoint only when both still match; otherwise restart from 0.
Limit same-id resume to local-to-remote uploads.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): reject an unfingerprinted resume when prefix bytes changed
Size and mtime can both be restored after a same-size rewrite, so Retry
was appending the new suffix onto the old staged prefix. Keep the head
content sample, and a 64 KiB window ending at the published checkpoint
when that sample does not cover it. Resume only when those still match.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): resume an unfingerprinted upload only when the prefix digest matches
Hash the bytes actually acknowledged on the remote .part, in order, and
store that SHA-256 with the checkpoint offset. Retry re-reads the local
prefix and restarts when the digest or size differs, including a same-size
rewrite the head sample and checkpoint window both missed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): cancel prefix verification and drop unused upload digests
Pass the transfer abort signal into the retry prefix re-hash so Cancel
stops the read and the task ends cancelled. Drop the in-memory digest
when an upload is cancelled, fails without a resumable checkpoint, or
the task is cleaned up.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(quit): don't freeze the main process when the user keeps unfinished transfers
Quitting with an unfinished transfer pauses it and asks for confirmation in the
renderer. Declining was reported to main as "dirty editors", so
handleBeforeQuit raised a synchronous "Unsaved changes" message box. That box
blocks the whole main process (IPC, every SFTP transfer) until it is dismissed,
names editors that do not exist, and the paused transfer was never resumed.
The renderer now reports the refusal with reason "transfers" and resumes the
tasks it paused for the prompt; main keeps the quit cancelled but skips the
native editor dialog for that case. Real unsaved editors behave as before.
* fix(quit): report a transfer refusal before resuming
A slow resume could miss the 5s dirty-check timeout and let the app
quit anyway. Reply with reason "transfers" first, then resume only the
rows that were transferring or queued before the prompt paused them.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): round-trip jump host chains in CSV import/export
CSV export dropped hostChain, so a vault imported on another machine
lost every jump host. Persist the ordered chain as an OpenSSH
ProxyJump column and bind each hop on import.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): drop inline hops from a rejected ProxyJump chain
A self-referential ProxyJump list created unresolved hops before the
chain was skipped, so the import kept those hosts. Discard inline hosts
created for that rejected chain.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): prefer a same-group hop when binding CSV ProxyJump
When an imported host and an existing vault host both match a hop
endpoint, pick the candidate in the target host's group before falling
back to the imported match.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): prefer a same-group label when binding CSV ProxyJump
Combine imported and existing label matches before pickCsvJumpCandidate so a cross-group imported label cannot beat an existing hop in the target group.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(vault): reject CSV ProxyJump chains that contain a malformed hop
A multi-hop ProxyJump cell with any invalid hop now warns and leaves the
host chain unset, instead of saving the valid hops as a shortened route.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf(sftp): remove the per-directory barrier in folder transfers
Each directory used to run its files in a private worker pool and the walk
awaited that pool before moving on, so trees of many small directories ran
at 1-2 files in flight and paid a full drain at every directory. Files now go
through one tree-wide gate sized by the transfer concurrency setting; the
walk continues while earlier directories drain and the root awaits every
batch (also when the walk throws). Entry indexes are still assigned in walk
order, so resume checkpoints are unchanged; parent directories are created
by the walk before their files are queued.
* fix(sftp): handle folder file-batch rejection when it is queued
A batch can reject while the walk is still listing later directories.
Settle that promise immediately and surface the same error from the root drain.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Files that fit one READ fanout window (64 x 32KB = 2MB) read on the shared
browse channel without taking FAST_DOWNLOAD_CHANNELS_PER_SESSION, so folder
downloads of small files run with the configured transfer concurrency
instead of strictly one file at a time. Large files still serialize (#1507).
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf(sftp): coalesce transfer lifecycle repaints to one per frame
* test(sftp): settle coalesced lifecycle paints before asserting
Wait one animation frame before reading transfer rows, and cover the final
completion, pause, cancel, and error paint plus unmount cancellation.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(sftp): do not let a stale coalesced snapshot reopen a latched pause
Pause all could leave the folder row transferring while its children
were paused. A child lifecycle publish still carried the pre-pause
parent at the same lifecycle epoch, and the store treated that equal
epoch as Resume.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
A single -o spec with col=,col= is folded into the first column's header
on procps-ng 3.3, so cwd probes never see the su'd shell.
Fixes#3820
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
#3816 sends uploads <= SMALL_UPLOAD_SHARED_MAX_BYTES (64 x 32 KiB = 2 MiB)
straight to the shared session. #3814's tests were written on a base without
#3816 and use 128 KiB / 14 B payloads, so on main:
- 'connection loss mid-upload ...' never opens the isolated channel whose
write simulates the reset; the shared write mock never answers -> hang.
- The leaked hung transfer then makes the next three upload tests time out.
- 'isolated channel ending before OPEN ...' passes alone but vacuously.
Raise those payloads above the cutoff and add a small-upload variant that
drops the connection on the shared channel.
Co-authored-by: Qi Chen <qi@local>
* fix(sftp): fail uploads promptly when the SFTP connection is lost instead of hanging
ssh2 silently drops requests issued on an SFTP channel that has already
ended, and ssh2-sftp-client keeps its stale sftp after a TCP reset. After a
reset / sshd kill / VPN drop the concurrent-isolated upload failed, then the
fallback checkpoint stat on the dead browse channel never settled, freezing
the task at 'Transferring' forever with a leftover .part.
* fix(sftp): release the path gate when a write channel ends before OPEN
A resumable staged upload marked the path gate issued, then found the
isolated channel already closed. The write-error path left a generated
stage gate held, and the resumable flow skipped the bounded fastPut wait,
so later uploads waited forever. No OPEN was sent, so release the gate
and reject with sftpChannelClosedError.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* perf(sftp): upload files that fit one fanout window on the shared session
* test(sftp): keep small shared uploads from ending the browse session
Cancel and pause of a file that fits one fanout window must stay on the
shared channel: no isolated sftp-server, no sftp.end(), and a later upload
on that same session still completes.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: campaign <campaign@local>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Fixes#3310
Borrow the live SFTP channel on single-channel OTP banners so transfers do not hang waiting for a second keyboard-interactive prompt. Channel death fails with SFTP channel closed without OTP redial; disposable upload channels may fall back once onto a healthy shared browse channel.
Carry the existing shared session-validity guard to OpenCode v2 instruction and prompt sends after startup waits. Consume rejected SSE reader cancellation during fail-closed cleanup.
Add deterministic regressions for the provider handoffs and cancellation, with real loopback HTTP/SSE smoke coverage.
Refs #3620, #3805, #3802
Share opt-in, reversible privacy masking through main-owned policy and conversation maps across Catty, integrated agents, and External MCP. Restore managed arguments before validation, approval, and execution; fail closed on retired policy state. Add shared settings, documentation, boundary and lifecycle regressions.
Validated with deterministic regressions, production desktop smoke, clean Codex review and passing cross-platform CI.
Closes#3620
* feat(ai): support OpenCode 2.x while keeping the 1.x driver
Detect the installed opencode major version. 1.x stays on the bundled SDK and the old startup banner. 2.x uses the new serve banner, HTTP Basic auth, and the session API so model lists and chat turns work.
Fixes#3799
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): stop the OpenCode 2 event stream when a prompt is rejected
A rejected prompt does not emit a terminal SSE event. Abort that stream and close the server before waiting, and keep HTTP "not found" responses from being reported as a missing CLI.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): select OpenCode 2 catalog ids and keep context optional
Aliases expose provider/catalog id rather than the provider API model id. A rejected instruction entry warns and still sends the user prompt. The entry value stays the system-prompt string, which is valid JSON for that endpoint.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): post OpenCode 2 prompts after the event stream is open
The live /api/event stream does not replay. Wait until the server accepts the subscriber and start reading it before sending the user prompt.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): refresh OpenCode version and catalog after startup races
Key the version cache by the resolved PATH binary and its mtime, so an in-place upgrade is not stuck on the previous major. While listing 2.x models, refetch after model.updated or provider.updated until the snapshot stays quiet.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): refetch the OpenCode catalog when it changes during the first read
An update that arrives while the initial model list is in flight belongs to a newer snapshot. Record the event count before that request and fetch again when it moves.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): refetch the OpenCode catalog when the settlement budget elapses during a read
A catalog read that outlasts the quiet-window budget still has to fetch the snapshot announced by an in-flight model or provider update.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): localize OpenCode 2 failures when the server dies mid-turn
A killed event stream was surfacing undici's raw "terminated" text. Map that disconnect, a closed socket, and an SSE stream that ends before the turn finishes to the existing session-failed message.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): ignore Bun version banners and hide undici transport errors
A crashed Bun binary was cached as OpenCode 1.x, and fetch or JSON-body failures still showed raw undici text. Skip Bun and Node.js runtime versions, and map those transport errors to the existing unavailable message.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): keep the OpenCode 2 config model as a qualified string
The selected provider/model stays the original string in OPENCODE_CONFIG_CONTENT. Session create still sends Model.Ref with id and providerID.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): keep caller OpenCode config and hide transport session errors
A profile's OPENCODE_CONFIG_CONTENT is merged under Netcatty's generated settings, and a server-reported fetch failure no longer reaches the chat as raw transport text.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): drop undici transport text from OpenCode 2 details
HTTP error bodies and tool failures share the same sanitizer as session errors, so fetch failed, terminated, and other side closed never reach the chat.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): keep OpenCode transport mapping to mid-stream failures
Server-authored tool and session messages are no longer rewritten just because they match a transport string. A dead reply stream still reports the localized session-failed key.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): restore the OpenCode 2 server username
The transport-sanitizer edit replaced the opencode username constant, so a real 2.x spawn threw ReferenceError before Basic auth. Put the constant back and lock the default client username.
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-manager): add container runtime switcher and kubectl pods
Keep the existing Docker tab and select docker, podman, nerdctl, or
Kubernetes from runtimes detected on the host. Kubernetes replaces the
switcher with context and namespace selectors in the same panel.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): address container runtime review findings
Move the runtime adapter into the domain layer, keep a context namespace
when namespace listing is forbidden, and emit PowerShell or cmd startup
commands for Kubernetes shell, logs, and describe.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): cover Windows container popups and stale namespaces
Run container shell and logs through the host shell's inspect wrapper on
PowerShell and cmd, and reset the namespace when a remembered Kubernetes
context is no longer present.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): parse kubectl JSON after banners and scope inspect cache
Skip bracketed banners before kubectl JSON, and include the container
runtime in image and container inspect cache keys.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): parse Podman image lists emitted as JSON arrays
Podman applies {{json .}} to the whole image slice. Accept that array,
including Names and byte sizes, and keep Docker's JSON lines.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): parse Podman stats arrays
Accept Podman's stats JSON array and its id/cpu_percent fields, and keep
Docker's one-stat-per-line output.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): list every Podman image tag
Expand each name in a Podman image's Names array into its own row so
extra tags stay visible and searchable.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): quote kubectl list and delete for the session shell
Namespace, pod, and delete commands follow PowerShell or cmd quoting when
the session shell is not POSIX, so apostrophes in a context name survive
local PowerShell.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): drop renamed runtime launch commands from shell history
Container popups now probe with _nc_rt_err and also cover podman, nerdctl,
and kubectl. History sanitizers still required _nc_docker_err, so those
launches stayed in persisted history. Confirm-dialog resets also follow the
selected runtime.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): sum Podman interface stats and quote kubectl for the executor
Podman {{json .}} stats keep per-interface counters on Network, so the
details panel now sums RxBytes and TxBytes. Background kubectl runs in
powershell.exe on local Windows even when the terminal is cmd, so those
commands are quoted for that executor instead of the interactive shell.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): remove a selected image tag instead of the shared id
Podman rows that share one image id were all deleted with rmi <id>, which
either fails while other tags remain or drops every name. Tagged rows now
run rmi repository:tag; dangling images still use the id.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): reject cmd kubectl arguments that contain percent signs
Interactive cmd.exe expands %VAR% inside double quotes, so a context such
as prod-%USERNAME% would be rewritten before kubectl sees it. Cmd popups
now refuse that argument. PowerShell quoting still preserves the percent.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* refactor(system-manager): move runtime selection into an application hook
Persisted source, context, and namespace state plus the kubectl discovery
effects now live in useContainerRuntimeSelection. DockerManagerTab only
renders the switcher and panels.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): offer kubectl shell only for running containers
Succeeded and failed pods have no running container, and a completed init
container cannot accept exec. Shell targets now come from container
statuses that are running; logs still include init containers.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): keep Windows popup commands out of shell history
PowerShell and cmd container popups inspect then run, and kubectl popups
are typed directly. History filtering only matched the POSIX sh -c wrapper,
so those launches were stored as user commands.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): mark Windows popups instead of matching kubectl syntax
Filtering kubectl --context logs and describe also dropped commands the
user typed. Windows popups now carry __NC_POPUP__, and ordinary kubectl
lines stay in history.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): reject cmd arguments that contain delayed-expansion marks
cmd.exe with /V:ON expands !VAR! inside double quotes. Contexts such as
prod!USER! are refused for cmd popups, same as percent signs.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): hide raw nerdctl and unreachable cluster errors
Only offer nerdctl after a short info probe succeeds, or when the failure is a socket permission error that sudo can still recover. Map dead containerd and unreachable kube API errors to short localized messages and keep the CLI text in the log.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): show terminating pods and ephemeral containers
A deletion timestamp is Terminating even while the phase is still Running. Ephemeral containers are included in the logs list, and a running one is a shell target.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): keep the runtime select on Kubernetes and avoid covering it
Kubernetes uses the same compact source menu as Docker, with context and namespace beside it. Shell popups open in the free part of the window so they no longer sit on top of the pod badges.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): restore the saved engine when Kubernetes disappears
A host that was on Podman before Kubernetes keeps Podman if kubectl is gone, instead of falling back to Docker and overwriting that choice.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): avoid the visible system panel when others are parked
A hidden parking-host panel can be the first system-manager match and
has a zero-size rect. Scan every match and pass the first panel that
actually has size so the shell popup still sits beside the open panel.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): keep a runtime choice across probe gaps and show podman ports
A failed nerdctl or kubectl probe was saved as the fallback engine, so the
previous choice never returned. Reconciliation now restores the stored source
when it is detected again, and automatic fallbacks are not written back.
Podman ps port mappings and exposed ports are formatted into the same display
string the container detail strip already shows.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): show a pod's Evicted reason
Pod status.reason is the eviction signal, while the phase stays Failed and
the container often reports Error. Use that reason after a container waiting
state and before the generic phase so the badge stays Evicted.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): keep an avoided shell popup inside the work area
When the source window sits partly off-screen, clamping a slot back onto
the display could cover the system panel, and the fallback then used the
raw slot origin. Place the popup in the on-screen part of the slot instead.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(system-manager): left-align the runtime selector row
The Kubernetes context, namespace, and runtime selects sat on the right of
the header, so they did not line up with the search field. Keep the same
padding, let the context select fill the remaining width, and leave the
runtime and namespace selects at their natural width.
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(ui): use a lightning icon for quick connect
The host switcher quick-connect row now uses lucide Zap at the same size and stroke as the previous terminal icon.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ui): badge the quick-connect icon like host avatars
Reuse the DistroAvatar sm fallback classes so the Zap sits in the same 20px primary rounded box as a generic saved-host row.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ui): fill the quick-connect bolt
Zap uses fill="currentColor" so it renders solid in the primary badge, colored by the existing text-primary-foreground token.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Proxy handshake parsers consumed the whole TCP packet, so an SSH banner
that arrived in the same segment as the HTTP CONNECT or SOCKS5 reply was
never seen by ssh2. Put those trailing bytes back on the socket.
Related to #1414
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(ssh): add per-host TOTP with QR scan and MFA auto-fill
Store an optional otpauth TOTP secret on the host, encrypt and sync it
like a password, and auto-submit it once when a keyboard-interactive
prompt matches JumpServer or PAM TOTP text.
Closes#3323
Refs #3415
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): address Codex TOTP review findings
Reset authenticator editor state when the host changes, match custom
prompts with RE2, offer keyboard-interactive during connection tests
when TOTP is configured, and drop the secret when a host becomes a plugin.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): fill TOTP from the active prompt and ship re2js
Attach the connection's otpauth URI to the keyboard-interactive dialog
so Fill current code uses that attempt, including unsaved hosts. Move
re2js into production dependencies so packaged builds can match custom prompts.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): ship the shared TOTP runtime in packaged builds
electron-builder allowlists domain CommonJS modules individually, and
sshAuthHelper requires domain/totp.shared.cjs at load.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): save a pasted TOTP secret and ignore stale QR imports
Host save waits for the authenticator draft to validate before writing
the host. QR import completions from the previous host no longer change
busy state or errors on the newly selected host.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(host): ignore TOTP copy in the proxy command summary check
The assertion was matching the word secret in the authenticator UI.
It now checks that the ProxyCommand token itself is not rendered.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(ssh): simplify host TOTP setup and type codes into the terminal
Move authenticator setup into a dialog so the host form keeps a single setting row, show keyboard-interactive prompts as field labels, and add a terminal context-menu action that types the current code without Enter.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): keep TOTP fill inside the auth dialog
Remove the terminal context-menu action. Offer the verification-code button on each non-password prompt, highlight OTP prompts, and never write the code into the password field.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): keep MFA instructions and scan every screen
Show keyboard-interactive name and instructions as plain text when they are not already the field label, and scan each unmatched desktop source once instead of repeating the first display.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): keep multiline MFA prompt directions
Show lines before the last keyboard-interactive prompt as plain-text guidance, and keep that last line as the field label.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): scan TOTP QR codes at each display's full size
Request a thumbnail as wide as the widest display and as tall as the tallest, so a portrait screen is not scaled into a landscape frame.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): keep TOTP fill off menu prompts
Judge the fill action from the last prompt line and the shared menu, SMS, and confirmation exclusion list. Use a secondary button on the label row, and simplify the host authenticator row.
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): treat bare bash PS2 as a confirmed shell prompt
Broadcast input latched on the default ">" continuation prompt, so the
final Enter of a heredoc paste reached only the focused session.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): trust bare PS2 only inside a heredoc
A global bare ">" confirmation cleared the sensitive-input latch for
echo-disabled prompts such as read -s -p '> '. Trust that line only when
the preceding command is a confirmed shell prompt containing <<.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): coalesce a CRLF split across prompt-security chunks
A chunk that ends in CR completes the heredoc opener. The next chunk's
leading LF is the rest of that break, so dropping it keeps the opener
and a following bare PS2 stays trusted.
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(ssh): opt temporary connections into network device mode
Saved hosts and group-backed clones keep their own device type. SSH
connections with no saved host, including external launches and quick
connect, can start in network device mode when the new Terminal setting
is on. The setting stays off unless the user turns it on.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(terminal): quick connect typed addresses from the tab switcher
The tab-bar quick switcher now offers the typed address when it already
parses as a connection target. Choosing that row opens the existing
quick connect wizard, including from a terminal tab where the vault
surface is hidden.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): keep temporary Mosh and ET off network device mode
Those transports are stored as SSH plus a flag. Stamping network device
mode onto them made raw-host checks skip the copy-session working
directory probe. Plain temporary SSH still opts in.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ssh): read temporary connection settings at connect time
VaultView keeps a stale onConnect callback because its memo ignores that
prop. Read both temporary-connection toggles from a ref when connecting
so turning them on or off applies in the same session.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): stop word queries from opening quick connect
Show the switcher quick-connect row only for address-like input, and
place it after matching hosts so Enter still connects to a saved host.
Translate the Hosts, Tabs, and New Workspace labels.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep matching tabs ahead of quick connect
An address that matches an open session or workspace was still the
default Enter target when no saved host matched. Put that row after
those tabs so Enter switches to the existing one.
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(vault): keep the New Host local shell icon at 14px
The dropdown was a fixed w-44 flex row. "Local Shell (CMD)" is wider than
the sibling labels, so the icon SVG flex-shrunk below size 14 (and longer
shell names collapsed it entirely). Pin every menu icon with shrink-0 and
let the menu grow with the label.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ui): keep toast text on the app theme surface
Palette fills used a prefers-color-scheme dark variant, so an app-dark session with a light OS left light text on a pale background.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Saved hosts keep their own setting. External launches and quick connect
with no saved host can use the same legacy algorithm list when the new
global setting is turned on. It stays off by default.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat: add optional Streamable HTTP for External MCP
Serve the existing catalog tools and approval path on a loopback HTTP
endpoint so clients that cannot spawn the stdio launcher, such as Codex
in WSL, can connect with a bearer token. The listener stays off until
the user enables it.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: close HTTP sockets before stop and export the Codex bearer
Destroy tracked connections before awaiting server.close so disable does not hang on a half-open request. Prefix the Codex snippet with export so the child process receives the token.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: advertise a real client host and return 413 intact
Wildcard binds no longer copy 127.0.0.1 into client configs; the URL stays empty until a reachable client address is set. Oversized POSTs receive a JSON-RPC 413 before the socket is closed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: recheck the bearer token after the HTTP body arrives
A half-open POST already passed auth before readBody. Recheck the live token before dispatch so a rotation during that wait cannot still run a tool.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: expire idle Streamable HTTP sessions
JSON responses do not keep a socket open, so a client that never sends DELETE used to pin an MCP session until the listener stopped. Sweep sessions that have been idle for 15 minutes, and skip any session with a request still in flight.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: return 404 when an HTTP session id is unknown
GET and DELETE with a nonempty session id that is no longer active now return 404, matching POST and the Streamable HTTP session contract. A missing session id stays 400.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: accept bracketed IPv6 origins
Node reports URL.hostname as [::1], so an otherwise valid loopback or wildcard Origin was rejected. Compare the address with the brackets removed.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: release rejected HTTP handshakes that never open a session
A 406 or 415 from the SDK returns normally before onsessioninitialized. Detach the approval client and close the MCP server when that happens so rejected initializes do not accumulate.
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(terminal): click archive names to fill extraction command
Paste an absolute tar or unzip command when a Linux session
Ctrl/Cmd-clicks a printed archive name. Cwd comes from OSC 7
markers only, so a later directory change cannot retarget an
old relative name.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): anchor wrapped archive links on the continuation row
A token that starts on the next physical row was mapped to the previous
row's exclusive end cell. Inclusive offsets now move to the next segment,
while an end-exclusive offset stays on the row where the match stops.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): separate archive fill from text after the cursor
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): separate archive fill across a wrapped draft
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
The tool CLI now re-reads discovery.json for up to ~1.2s (300ms + 900ms) when it
connects during an app restart. It covers a missing or partially written
file, a dead port, and a stale token (AUTH_FAILED or "Authentication
required"), plus a socket that closes during auth/verify. Identity is
port + token + pid, so a bridge restarted on the same port is retried. When
every attempt fails, the original error is surfaced unchanged.
Supersedes #3785. Refs #3443.
Co-authored-by: GOUKI9999 <11640942+GOUKI9999@users.noreply.github.com>
Mid-line snippet accept deletes only the current word (DEL on POSIX, backspace on Windows), and program prompts like Email:/Username: offer snippet candidates while password/passphrase prompts stay blocked.
Fixes#3622
* fix(terminal): forward Sogou latin commits after keyCode 229
Windows Sogou in Chinese mode commits latin text with a late insertText
after keyCode 229 and no composition events. xterm's 0ms check misses
that text, then _keyDownSeen drops the input so only CR reaches the PTY.
Fixes#3185
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): do not resend IME text the deferral already committed
A punctuation deferral that outlives its keyup still commits the later
insertText. The keyCode 229 guard was clearing xterm's dedupe flag, so
the same text was written again.
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): migrate the saved Shift+Enter newline once
Existing installs kept the pre-#3770 "\\n" default, so Shift+Enter still
submitted in TUI CLIs. Rewrite only that stored value to ESC+CR, and let
xterm's native CSI u own the chord when an app has opted in.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep the Shift+Enter migration local
The one-time rewrite of a saved "\n" does not need a sync flag or a
three-way merge. An imported or synced old newline can stay as stored.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): keep native kitty Shift+Enter on broadcast peers
When a target has negotiated xterm kitty keyboard and the custom
protocol setting is off, resolve the broadcast chord as CSI 13;2u
instead of the ESC+CR fallback.
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: add a global default proxy with an explicit direct override
Resolve the proxy as the host setting, then the nearest group default,
then one optional saved profile from synced settings, then a direct
connection. A host can choose direct and skip both inherited defaults.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep global default proxy persistence across settings instances
Unregister only the setter owned by the unmounting useSettingsState instance so closing Settings still writes the cleared default through the remaining AppLockGate setter.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: round-trip explicit direct mode in vault CSV
Write the Proxy cell as direct when a host sets directProxy, and import that literal back as an explicit direct connection. An empty Proxy cell still inherits.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: keep CSV proxy export from embedding the global default
Grouped hosts ran through applyGroupDefaults and picked up the ambient
global proxy, while ungrouped hosts exported an empty inheriting cell.
Both paths now opt out of that default so inheritance stays inheritance.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: follow global default proxy changes in peer session windows
Session windows disable settings IPC sync, which also skipped the
storage listener and left the resolver singleton at its mount-time id.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* refactor: clear a deleted global default proxy at the call site
Drop the pass-through field on removeProxyProfileReferences. The profile
manager already knows the active id and clears it directly.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test: share the global default proxy settings harness
The peer-window case and the multi-instance cleanup case were mounting
the same JSDOM fixture twice.
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: inherit X11 forwarding from group defaults
Hosts that leave the toggle unset pick up the nearest group's X11
forwarding setting through applyGroupDefaults. An explicit host value
still wins, and the default stays off.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: show inherited X11 from the effective host transport
The host editor treated a raw group X11 default as on even when
applyGroupDefaults suppresses that inheritance for Mosh, ET, or a
non-SSH protocol. Drive the switch and its visibility from that result.
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: add --open-terminal-shell launch flag
Queue local-terminal launches as { path?, shell? } so a discovered shell
id or executable can open a tab on its own or with --open-terminal-path.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: serialize open-terminal launches during shell discovery
Await shell discovery on an ordered queue so a later path-only request
cannot create its tab first, including when App Lock drains.
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): skip paste residual EL on the alternate screen
Vim often finishes a paste chunk with the cursor mid-line. Injecting
ESC[K there erases the rest of the row and is not redrawn. Clear the
pending flag so the cleanup cannot run after leaving the alt screen.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): skip paste EL after a same-batch alt-screen exit
Remember the pre-write alternate buffer so a paste echo and ESC[?1049l
in one chunk cannot inject ESC[K] into the restored normal screen.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Fixes#1952. When restore-terminal-cwd is on, local POSIX shells probe the PTY process cwd (/proc on Linux, lsof on macOS) and re-probe when the prompt returns so slow commands like `sleep 1; cd /tmp` land correctly. SSH restore path unchanged.
A 3s login-shell probe on the interactive client called Client.end() when
the exec channel failed to open, dropping every tab on that connection.
Preserve the terminal transport and keep teardown for dedicated Mosh/ET
helpers.
Fixes#1181
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(terminal): show per-GPU usage in the server stats bar
Keep the existing single nvidia-smi poll and carry each card's index, name,
utilization, and VRAM into the same hover-card layout used for CPU cores.
Closes#3745
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(terminal): wrap GPU names and stretch a single card
Names use two lines plus the native title instead of an 88px clip, and one
GPU fills the hover card so the bar sits with the title.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Follow-up to #3773: Traditional Chinese (Taiwan) autocomplete description dictionary via OpenCC s2twp, lazy-loaded like zh-CN.
Closes#3157.
Real-app smoke PASS on 4d2bd32e.
Overlay bundled Fig description strings when the UI locale is zh-CN.
Cached specs stay in English, and missing entries fall back to the original text.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat: alert when an external MCP approval is waiting
Show a system notification and request taskbar or Dock attention so confirm-mode approvals are not missed before the existing timeout.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: stop MCP approval attention when a later window focuses
Watch app browser-window-focus while a flash or dock bounce is active so a
window created after the alert starts can clear it.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Keep the default top-tab colors, and expose .top-tab[data-active] plus
--top-tab-active-bg/fg/border so Appearance custom CSS can recolor the
selected tab.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(terminal): default Shift+Enter text to the legacy Alt+Enter encoding
* fix(terminal): do not treat ESC+CR Shift+Enter as command submission
getShiftEnterSubmittedInput classified every CR-terminated value as a
submit. The ESC+CR default (legacy Alt+Enter) therefore recorded a
command and fired onCommandSubmitted on each Shift+Enter.
Co-authored-by: GOUKI9999 <11640942+GOUKI9999@users.noreply.github.com>
* fix(terminal): detect Alt+Enter line breaks without a control regex
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* test(terminal): expect ESC+CR when Shift+Enter broadcast falls back
Non-preserving Kitty targets now receive the new default Alt+Enter
encoding instead of a bare newline.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: GOUKI9999 <gouki9999@Hotmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: GOUKI9999 <11640942+GOUKI9999@users.noreply.github.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
An open compose bar no longer hides the disconnected Enter-reconnect hint
or blocks the terminal and dialog paths. An empty compose draft reconnects
the same way; a non-empty draft still sends.
Fixes#3767
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Typed request compression ran before every Catty request (routine pre-turn and every step) and replaced image/file parts with a text placeholder, so attached images never reached the model.
preserveAttachments now keeps image attachments on the routine pre-turn and step paths. Non-image files stay placeholders (chat providers reject e.g. text/plain; the model reads them via list_attachments/read_attachment). The explicit 413 retry still trades attachments for placeholders.
Verified in the real Electron app: image reaches the provider as image_url (fake OpenAI-compatible server and DeepSeek deepseek-flash, which described the test image correctly); text-file turns still succeed via attachment tools; request prefix stability across turns unchanged vs main (DeepSeek cache hits ~98% on follow-up turns).
Fixes#3764
Co-authored-by: yangfan <1413737839@qq.com>
* fix: dial SFTP separately on network-device SSH sessions
Huawei and Ruijie switches drop the shell when SFTP opens on the terminal TCP session. Use the existing dedicated login for network-device mode and network-vendor banners without marking those hosts single-channel bastions.
Fixes#3631
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: block follow-cwd probes on dedicated SFTP logins
Network-device mode and vendor SSH banners already dial SFTP separately,
but follow-terminal-cwd still opened an exec channel on the terminal
transport. Gate that probe with the same dedicated-login decision.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix: skip terminal pwd exec for network-vendor banners when opening SFTP
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Fixes#3588
Regular follow waits for a trusted live cwd instead of racing the post-command null gap; include connectionPath in follow effect deps so aborted navigates can resync.
Codex clean on 239bda3; CI green; smoke PASS (follow on/off, first open, go-to).
Fixes#3757
preventDefault on mousedown for Minimize/Maximize so the xterm textarea keeps focus after minimize/restore. Close left unchanged so inline rename can still blur/commit.
Bun-compiled OpenCode-family CLIs answer --version slowly: measured
'mimo --version' at 3.9-5.1s warm (6s+ cold) and 'opencode --version'
at 6s cold on Windows. The 5s probe timeout killed these healthy CLIs
mid-probe, so agent discovery dropped them (exitCode null ->
not plausible) and the settings card showed them as unavailable. Raise
the version-probe timeout to 15s; a missing command still fails spawn
immediately, so only existing-but-slow CLIs pay the extra wait.
* ci: hide only risky download links in issue comments
Stop deleting comments that merely mention an archive or executable
name. Match http(s) URLs whose path contains a risky extension, skip
the issue author as well as maintainers and bots, and minimize matches
as spam instead of deleting them.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* ci: relay edited issue comments with truncated bodies
Forward issue_comment edited events and include comment identity,
association, timestamps, and bodies clipped to 4000 characters.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
The v1.1.84 working-directory query exceeds Dropbear's 9000-byte request limit, causing an authenticated OpenWrt terminal and its entire SSH transport to close with `String too long`.
Remove explanatory comment lines before quoting the static probe. Preserve all directory discovery and lsof validation behavior. Cover request size with known and unknown shell PIDs, and run the lsof compatibility fixtures for both server identities.
Verified on real OpenWrt 24.10.5 / Dropbear 2024.86, including the exact 9000/9001-byte boundary, old-release comparison, full macOS Electron connection, terminal commands, and SFTP follow-directory. Full CI: 14036 passed, 47 skipped, zero failures. Two independent local reviewers and the current-head remote reviewer are clean.
Fixes#3731
* refactor(snippets): combine creation actions in a dropdown
* fix(snippets): support keyboard navigation in creation menu
* fix(snippets): retain focus when choosing a creation action
Keep renderer-only libraries available as development dependencies so Vite
bundles their runtime code without shipping their dependency trees again.
Exclude native compilation helpers from packaged resources, and prune
node-pty/serialport prebuilds for other platforms and architectures while
preserving matching universal binaries and rebuilt runtime files.
Validation:
- npm run lint; 33 packaging tests pass.
- npm run pack:dir, real packaged application UI, and 29 packaged runtime
smoke checks pass (SDK imports, PTY, serial port listing, SQLite query).
- All 999 UI files retain their code/content; differences are generated
asset references and the embedded dependency classification metadata.
- Dependency versions are unchanged.
- macOS arm64 app: 394,959,135 -> 360,740,625 bytes.
- macOS arm64 ZIP: 150,847,369 -> 143,151,622 bytes; ZIP integrity passes.
- Full suite: 14,035 pass, 36 skipped, 5 fail. The four FIDO helper failures
and transfer FileHandle-GC failure reproduce on unchanged main with Node 26.
Deliver PTY job interrupts as terminal data instead of sending SSH signal channel requests. This addresses the background-cancellation log noise reported in #3577 while preserving the existing cancellation retries and result semantics.
Add regression coverage asserting cancellation writes ETX without invoking the stream signal method.
Add explicit confirmation, on-disk cleanup and failure retry for local vault backup history, while preserving active and interrupted recovery snapshots.
* feat(ssh): restore FIDO2 security key support (#2308)
Consolidate the restored feature and its review fixes before rebasing onto current main. Original PR head: 5d0a0c6c9a550364d3ec5c2331395cedfc9ea7ae.
* fix(ssh): verify and repair FIDO authentication with simulated hardware
Preserve complete OpenSSH SK signature payloads, classify combined PIN/presence prompts correctly, expand IdentityFile tokens, retain certificate selectors, and route shared-agent signing prompts to their originating window. Dismiss prompts when notification helpers exit. Add a software SK provider and opt-in real OpenSSH integration coverage for both algorithms, certificates, SFTP, forwarding, and concurrent windows.
* refactor(ui): refine security key forms and authentication motion
Use shared Select and Switch controls, concise localized copy and compact token-based dialogs. Add scoped enter/exit, field transition and presence animations with reduced-motion support. Preserve raw prompt details and visible PIN errors.
* fix(ssh): align security key prompts with hardware presence and verify Docker logins
* fix(ui): keep security key dialog above its remounted overlay
* fix: address Codex review on PR #2823
* feat(ssh): guide security key recovery and explicit authentication fallback
* test(ssh): exercise security key authentication on native Windows
* test(ssh): verify Windows fixture signatures and explicit fallback
* test(ssh): configure Windows OpenSSH toolchain and document coverage
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* Polish monitoring panels and preserve host icons
* Simplify monitoring layout and show every disk partition
* Simplify list rows and add resizable monitoring overlays
* feat(monitor): add modal drawers, sampled hover values and faster refresh
* feat(monitor): reset run history and support saved drag ordering
* perf(monitor): stream history deltas and keep fast samples lightweight
* fix(monitor): separate chart histories across connection identities
* refine(monitor): show disk capacity on hover and use inline sidebars
* fix(monitor): omit unchanged stats from polling deltas
* test(monitor): freeze time in full-history delta checks
* perf(monitor): bound chart paths and reuse unchanged histories
* Codex worktree snapshot: startup-cleanup
Co-authored-by: Codex
* fix(browser): close malformed proxy connections without a reply
* feat(browser): embed host browsing in terminal side panels
* style(browser): blend compact browser chrome into terminal panels
* feat(browser): open terminal links internally with a system browser preference
* fix(browser): retain the first terminal link during panel mount replay
* fix(browser): release contexts when their split pane closes
* style(browser): remove the downloads panel shadow
* fix(browser): ignore late downloads after panel closure
* fix(browser): mount browser content on terminal auto-open
* fix(browser): preserve empty-vault recovery with bookmarks
* fix(browser): expose actions for supported terminal types
* fix(browser): retain open requests before the panel mounts
* fix(browser): align vault actions with supported hosts
* fix(browser): share download controls across host contexts
* fix(browser): keep vault browser requests on visible sessions
* fix(browser): release contexts after workspace detach
* fix(browser): enforce WebRTC routing on page contents
* fix(browser): skip unsupported sessions in open requests
* style(browser): encode glyphs in ASCII-safe source
Make provider disconnect visible and explain the local credentials removed and the vault, backup history, shared sync password and cloud copies retained. Report failed disconnects and preserve a retry path instead of implying success.
Validation: focused sync and dialog checks, isolated Electron cancel/failure/retry/reload against WebDAV, two independent final full-diff reviews, exact-head Codex review and current GitHub checks; the main integration tree also passed focused checks.
Related to #3688. Device handover cleanup remains outside this patch and the issue remains open.
Deleting saved hosts now removes their jump references from surviving hosts and group defaults in all ordinary deletion flows. Host-only bulk deletion uses the existing vault mutation boundary, and explicit empty chains retain their original group-override meaning.
Validation: 282 focused checks, lint/build, two independent full-diff reviews, latest-head Codex review, current GitHub checks, and actual isolated Electron deletion/restart/native SSH reconnection.
Fixes#3691
Add the Global history clear-all action with confirmation and five-language copy. Clear local shell history, autocomplete and compose recall while preserving drafts, keeping peer windows consistent on clears, and allowing new commands afterwards.
Verified with 116 focused checks, two clean independent final reviews, real Electron multi-window SSH-fixture interaction and restart, current-head Codex review, and both required CI jobs.
Co-authored-by: fyhax1ki <fyhax1ki@gmail.com>
Withdraw the unreleased archive-command shortcut from #3682. Real first-party SFTP locate,auto-run multiline snippets and paste controls showed old filenames preparing commands in a changed directory. The attempted fragment-based tracking required growing state guards,so defer this implementation rather than expand the feature.
The final diff only removes the original seven files/248 added lines.141 related checks,lint,build and real full-app normal-input,SFTP,snippet,existing-link and draft controls passed. Current-head remote review and both required CI jobs passed. Existing AI repairs are untouched.
Related to #3269 and #3682. The feature request stays open. No release.
Stopping paced AI input could leave user shell hooks disabled. Remove the unproven PS0 mutation while preserving the existing combined-output fix.
Independent validation:310 checks pass,7 skipped;24 real Bash/SSH normal,cancellation and combined-output controls. Current-head remote review,automated tests and all platform packages passed.
Related to #3683 and #3485. Original reporter acceptance remains pending. No release.
* fix(port-forwarding): preserve exhaustion across duplicate failures
* fix(port-forwarding): reset exhausted state on active adoption
* fix(port-forwarding): confirm adoption before clearing exhaustion
Add a Move to group action for the selected hosts, reusing existing group moves and saving the selection once. Omit the hidden synthetic General destination.
Related to #3194; marquee selection and dragging a selection are outside this change.
65 focused checks, lint, build and real grid/list mouse/keyboard/cancel/reload/single-host drag checks pass. Current-head remote review and both CI jobs pass.
Preserve the original local shell name and icon when a saved host is edited and a terminal is copied.
Related to #3615 and #3616.
Validated with 70 focused checks, lint, production build and actual terminal copy flows. Current-head remote review and CI pass.
Related to #3615 (stays open: hiding/restoring running sessions is separate work).
Merged by judgment after ~49 Codex findings; remaining P2 (Copy Tab name/icon snapshot when host shell is edited after launch) is a cosmetic edge case for a follow-up.
Report a closed SFTP channel instead of leaving a download waiting for a source-file open callback that cannot arrive. Keep existing upload and reconnect policies.
Related to #3406. The original connection-loss cause and reporter environment remain unconfirmed; keep the issue open.
Validation: 42 download checks and 7 lifecycle/cancellation checks; real 1 GiB normal download, pause/continue, and interrupted retry with full checksums; exact-head Codex review clean; current test and four platform package jobs successful.
Keep the initial live-shell probe short and omit unused Bash history-cleanup input after a confirmed zsh reply. Preserve the existing bounded task wrapper, Bash cleanup, history and cancellation behavior.
Related to #3575. This repairs a confirmed controlled failure; the reporter-specific startup contrast remains open for confirmation and release delivery.
Validated through real configured zsh and Bash terminals, quoted/multiline and 3,000-character commands, the complete local test suite, exact-head Codex review and current automated checks.
Match host reuse to the actual Telnet login, ET hop ports and serial/local session settings. Preserve already-scoped reconnecting tabs when other sessions remain live, and reject Telnet proxy settings consistently before reusing a connection.
Follow-up to #3614. Related to #3613; no claim of a released fix or full PR3616 inherited-default coverage.
Validated with focused checks, real Electron/TCP Telnet connections, build/lint, exact-head Codex review and current automated test/package checks.
Handle unsupported copyFile operations on writable GVFS/FUSE destinations using the existing exclusive bounded copy path. New files accept the mount's creation permissions; replacing an existing file still requires equivalent protection, and failures retain recoverable data.
Only explicitly optional permission/timestamp updates may tolerate permission refusal. Content writes, close errors, required protection, and rollback preservation remain strict. Current-head review and required checks were verified before merging.
Related to #3611. Keep the report open pending delivery and actual environment confirmation.
Allow ssh2 to continue to another authentication method when a system SSH agent is unavailable, for both target and jump-host connections. Keep the authentication deadline and fatal-error handling intact.
Validated with real SSH servers and Unix agent sockets through normal terminal startup, including forwarding, successful password fallback, real authentication failures, and timeouts. 118 focused checks and lint passed. Current-head review and required CI were checked before merging.
Related to #3640. Keep the report open for the reporter's environment and delivery validation.
Report transport closure without inventing a successful remote exit status, while preserving any actual exit code or signal already received. Reuse the existing exit classification to keep normal exits from being treated as reconnectable disconnects.
Related to #3631. This changes the diagnostic message only; the frequent-disconnection report remains open.
Validation: 458 focused checks, lint, production build, real Electron/local SSH disconnect workflow, current-head Codex review, and repository CI passed.
- Last-known-good serving keeps a decorated current id only when the base row
advertises that level (same rule as the renderer's modelPresetsContainId).
- ANTHROPIC_BASE_URL joins the main-process and renderer catalog cache keys so
a different Claude endpoint never serves another endpoint's persisted list.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- probe-output-arrived and probe-reply-parsed report the cumulative raw
byte count, consistent with the startup timeout record.
- A poll/stop that the worker answers with 'Background job not found' while
the route still exists now emits job-stop-miss (worker-job-missing). The
route itself is left alone; this PR stays diagnostics-only.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
The first decoded chunk while probing may be only the echoed probe command,
so probe-reply-arrived overstated what was seen. Rename it (and the timeout
stat) to probe-output-arrived; probe-reply-parsed remains the proof of reply.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- appendRetainedStoredModelPreset appends the exact suffixed id when a
base-only row exists that does not cover the suffix.
- createSdkRuntimeModelCache carries a degraded refresh's warning onto the
retained cached catalog so the banner and Retry stay visible.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- deleteWorkerRoute only emits when it actually removes the matching route,
so a concurrent cancel plus terminal cleanup yields one record.
- cmd-write.bytes uses Buffer.byteLength instead of UTF-16 length.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
hasExpectedPromptSuffix ran for every PTY chunk and normalised the whole
retained output (up to 256K chars of ANSI/CR stripping) just to test whether
it ends with the prompt. During an output flood with CRLF line endings that
kept the terminal worker busy for seconds at a time, delaying cancel
requests, timers and keepalive handling (#3443). Look at a bounded tail
window instead; the result is unchanged for any prompt that fits in it.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Report delivered-byte rz progress while parked on SSH backpressure, and race each progress poll against drain so upload resumes immediately when the channel drains.
Fixes#3663
- an empty catalog now yields no runtime presets even when the user has custom
composer models, so the built-in fallback list stays visible
- add GROK_CONFIG_DIR to the renderer cache env hints (the sync test caught
the drift after the main-process key was added)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- count probe bytes from the raw chunk before decoding, so an incomplete UTF-8
prefix and multi-byte text are reported in bytes
- log job-stop-miss from the main-process poll/stop handlers too: once the
worker route is gone the dispatcher falls through to them
- replace the circled numeral in the new diagnostics module
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- add GROK_CONFIG_DIR to the model catalog cache env keys so two grok login
profiles sharing HOME and the binary do not share a catalog
- replace the em dash added in the current-only refresh comment
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- generate the Copilot headless wrapper in the managed temp directory, create it
exclusively and only reuse an existing file whose content matches; a tampered
file is not handed to the SDK
- use the headless wrapper on Windows only; POSIX passes the resolved CLI straight
to the SDK so forceStop() reaches the real process
- keep the renderer model-cache env hints in sync with the main-process keys
(CODEBUDDY_* and XAI_API_KEY) and guard it with a test
- forward source/fetchedAt from list-models so a last-known-good catalog is not
labelled live
- do not retain a saved Claude alias on top of an empty catalog, which would turn
a one-row list into the authoritative one and hide the built-in presets
- ASCII-only comments in the two new modules
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- keep an already-qualified {provider, model} default in getOpenCodeDefaultModelIds
instead of prefixing the provider twice
- resolve npm's copilot.ps1 wrapper to the JS entry on Windows like .cmd/.bat
- when serving a last-known-good catalog, remap a persisted current id that has
no row to the first visible row for every backend (null still means default),
so the picker and the send preflight agree
- reconcile the catalog warning banner from the result of every refresh, so a
panel that joined another panel's in-flight load also shows it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
- keep jobDiag.stats.probeBytes in sync so the startup-timeout event reports
the bytes of a partial probe reply
- route every worker route-map deletion through one helper that emits
route-removed (poll/stop completion, stale check, chat cancel, terminal close)
- label a poll/stop miss as route-missing, scope-mismatch or
worker-unavailable instead of always route-missing
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SX3d2XMHxBEpU7VqpvXbdm
Recreate cursor-line markers/decorations when xterm disposes them externally, and treat empty decoration sets as intact so fully colored rows do not churn markers every frame.
Fixes#3656
Clamps the window layout minimum to the current display work area so small screens (e.g. 1366×768 at 125%) can fit and shrink. Restores terminal keyboard focus after OS resize/maximize, without stealing from active editable fields.
Fixes#3647
Merged by judgment: four Codex rounds already addressed the real bugs and multi-monitor sync; the latest P2 (protect every non-editable focusable control on resize) is edge-case thrash.
Classify SSH exit-signal as signaled so auto-reconnect does not fight admin idle kicks. Bare transport closed and error still reconnect.
Related to #3617.
* feat(terminal): disable auto-close-on-exit by default
Remote graceful channel closes (VPN half-close, server-side idle kick) can be reported as a clean exit with code 0, so with auto-close enabled the tab gets destroyed exactly when the user needs it kept - bypassing the SSH auto-reconnect loop, which intentionally skips exited events.
Keep tabs on exit by default; users who want close-on-exit can enable it in Settings > Terminal > Behavior. Attach-mode close behavior is unchanged.
* fix(terminal): keep session tabs when the auto-close setting is unavailable
The resolver's own default and the TerminalLayer call-site fallback still
treated a missing terminal setting as auto-close enabled. Remote graceful
channel closes (a jump host reaping an idle session) are reported as a
clean exit, so before the settings store had loaded, those closes silently
deleted the session tab and its scrollback. This completes the opt-in
default introduced above: only an explicit autoCloseOnExit true closes tabs.
* fix(terminal): migrate the materialized auto-close-on-exit default once
Builds before the default flip shipped autoCloseOnExit: true as the
default and materialized it into netcatty_term_settings_v1 on any
settings write, so most upgraders carry a value they never chose and
the new default-off never reaches them: bastion and idle-harvesting
servers reporting clean exits keep silently deleting their tabs.
Reset that materialized true exactly once behind a marker key before
the settings blob is first ingested, then persist the marker so every
later stored value is treated as the user's own choice. The cost is
documented: a user who deliberately enabled auto-close before this
release must re-enable it once.
* fix(terminal): keep synced legacy auto-close from coming back
A device that already wrote the one-shot migration marker was accepting
a pre-migration cloud or backup payload with autoCloseOnExit true.
Unversioned true is rewritten to false. Payloads that record the
migration still sync an explicit opt-in, including across a three-way
merge that would otherwise glue the local marker onto a legacy true.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: xu <tbqyomnnnbltzxy2t@aliyun.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(terminal): enable SSH auto-reconnect by default
Unexpected transport drops (VPN / Wi-Fi blips) are recoverable events, not
shell exits: the tab stays and the 5s in-place reconnect loop keeps the same
session id, so AI sessions and long-running work continue once the link is
back. With the previous default (off), a dropped link left the tab parked on
'Press Enter to reconnect', which reads as 'the session closed'.
Clean shell exits still close the tab; explicit opt-out in Settings is
preserved. mosh/et sessions self-heal and are unaffected by this gate.
* fix(terminal): skip auto-reconnect after idle timeouts
SSH auto-reconnect stays on by default for transport drops. A timeout
exit (TMOUT, server idle logout, or connect timeout) leaves the tab
disconnected instead of logging back in every 5 seconds.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: yuyang2230 <yuyang2230@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(ai): raise default AI session idle timeout from 30m to 24h
Sessions opened via host_open are auto-closed after 30 minutes without
terminal/file activity. In production use over flaky links this silently
destroyed in-flight agent work: the AI reopens a host, the link drops, the
session sits disconnected (counts as idle), and the reaper closes it
mid-workflow with no error surfaced anywhere. 24h is the normalize clamp
maximum; users can still choose a shorter window in Settings → AI, and the
idle manager still skips sessions with active executions.
* fix(ai): preserve 24-hour session default during renderer sync
* style(ai): keep session idle manager comment ASCII-only
Replace the unicode arrow in the Settings pointer comment with ASCII per the repo AGENTS.md convention (Maintain ASCII-only unless required by existing file content).
* fix(ai): default AI session idle timeout to 8 hours
480 minutes still covers a workday of flaky-link agent work without
sitting on the 24h clamp. Stored values are left untouched.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: yuyang2230 <yuyang2230@users.noreply.github.com>
Co-authored-by: 陈奇 <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): shed oversized tool output before evicting visible chat history
Under localStorage quota pressure the AI sessions serializer degraded
replay ciphertext fine-grained but never shed tool result payloads, so
AI-driven terminal chats (whose persisted bulk is terminal output)
forced the ladder to evict whole oldest sessions and finally collapse
persistence to the newest chat alone. After a restart the history list
looked empty.
Extend the per-message degradation pass to cap oversized tool result
content at a bounded head+tail with a truncation marker, mirroring the
existing ciphertext-shedding tradeoff: every visible conversation now
survives storage pressure at the cost of full tool output on affected
messages.
* fix(ai): keep tool-result truncation strictly smaller than the original
Codex review P2: for tool results only slightly over the cap, the
truncation marker (with JSON-escaped newlines) could make the degraded
copy serialize at the same size or larger than the original, breaking
the ladder's assumption that the degraded representation is the
smallest and sending quota retries farther off.
Drop the marker newlines and shrink the retained head+tail until the
whole copy fits the 8 KiB cap, so a truncated result is always
strictly shorter than its source.
---------
Co-authored-by: xu <tbqyomnnnbltzxy2t@aliyun.com>
The `Freeze trusted helper` step in the `sandbox_smoke` job used a plain
multi-line YAML scalar:
run: cp scripts/ai-automation.cjs "$RUNNER_TEMP/ai-automation.cjs"
chmod 0444 "$RUNNER_TEMP/ai-automation.cjs"
YAML folds the continuation line into the same scalar, so the step ran:
cp scripts/ai-automation.cjs "$RUNNER_TEMP/ai-automation.cjs" chmod 0444 "$RUNNER_TEMP/ai-automation.cjs"
`cp` then sees four operands and treats the last one as the destination
directory, which does not exist:
cp: target '/home/runner/work/_temp/ai-automation.cjs': No such file or directory
The job fails in 7s at step 3, and the remaining 12 steps are skipped.
Reuse the existing `&freeze_ai_helper` anchor, which is what the other eight
`Freeze trusted helper` steps in this workflow already do. It also picks up
`set -euo pipefail` and `test -f scripts/ai-automation.cjs`, so a missing
helper now fails with a clear message instead of a confusing `cp` error.
Observed on a fork mirror running commit b2ed83ae: runs 36847532363 and
36991962473 both failed at this step.
Unit tests stub execFileSync and only found ssh via /usr/bin/ssh, so the
PATH wrapper was skipped on images without that path. Prefer an absolute
findExecutable result, then command -v, then the well-known locations.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Stats latency already pings the authenticated session. Lock that in with
a unit test that fails if getServerStats dials the session host, which
is what produced kex_exchange_identification every ~30s (#3630).
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Add read-only [Netcatty JobDiag] timeline emission covering the four
checkpoints requested in #3443: probe write/reply/parse (2), command
write + start marker (3), startup timeout with byte stats, interrupt
delivery with flow-control snapshots, and the MCP background-job route
lifecycle (4). Every payload is keyed by sessionId + jobId (jobId was
previously absent from all logs) and carries monotonic at/sinceStartMs
timestamps.
interrupt-callback-invoked reports finishedAtCallback so the recovery
ordering finding from the #3443 investigation is directly observable;
interrupt-callback-skipped fires when recovery is skipped (no callback
wired or callback threw). Checkpoint 1 (flow-control samples) remains
covered by the existing [Netcatty Ctrl+C] / [Netcatty Terminal Perf]
emitters; session snapshots here reuse the same helpers.
Enabled via NETCATTY_JOB_DIAG=1 or the existing NETCATTY_TERMINAL_DEBUG=1.
Zero behavior change: instrumentation calls only, jobId/sessionId/
diagnosticsSession are optional startPtyJob options defaulting to null.
- ACP initialize _meta.modelState reflects the negotiated agent session and
has been observed to advertise only a subset of the account's models
(e.g. grok-4.6/4.5 without the 4.7 family), while `grok models` lists the
full account set; the old short-circuit (non-empty ACP catalog wins) made
the live picker silently drop models one source knows about
- run both probes together and merge by id: ACP entries win for shared ids
because they carry live vendor _meta (reasoning efforts, description);
fields an ACP entry lacks are filled from the CLI row
- tests: mergeGrokCatalogModels unit cases (subset union, dedupe, field
fill, malformed sides) and registry-level union/no-dupe coverage
opencode was the only SDK agent whose picker rendered nothing until its
live catalog pull resolved: the #3584 suppression replaced presets with
just the user's custom ids while runtimeModelLoading was set. Align it
with every other backend — presets stay selectable during loading, the
live catalog replaces them on arrival, and #3585's retry/warmer
mechanics are untouched. Adds a regression guard against reintroducing
the opencode gating.
Ground truth from grok 1.0.50 (`grok --no-auto-update models`): the default
model is listed with a "*" bullet but every other entry uses "-", so the
previous ^\*+-only regex silently dropped 3 of 4 live models and the
"live" picker shrank below the build-time presets. Accept any bullet
marker (*/-/*) so separator dashes still cannot match. Adds the captured
raw-output fixture plus an ACP initialize fixture (grok-4.7 / 4.7-build-fast /
4.6 / 4.5) asserting the registry ACP path keeps every availableModels entry.
Implements the unified model catalog UX on top of the last-known-good
persistence layer (#3624):
- R1 source provenance end-to-end: list-models now returns
source: 'live' | 'last-known-good' plus fetchedAt (ISO) on live
successes and last-known-good serve-outs; the pre-flight cache replays
the original provenance on hits. The renderer cache persists
source/fetchedAt alongside the catalog, and the build-time preset
fallback derives 'presets'.
- R2 loading state for all backends: drop the opencode-only gate on
modelCatalogLoading so every SDK backend shows the composer loading
state while its live fetch is in flight (opencode behavior unchanged).
- R3 unified status chip: ComposerModelPicker renders a muted footer chip
for every backend - live / cached with relative last-success time /
built-in default list - replaced by the spinner row while loading.
Keys added to en/es/zh-CN/zh-TW (ru keeps its pre-existing ai.opencode
gap).
- R4 copilot headless wrapper: generated per-resolve wrapper (baked with
the platform exe the npm loader would spawn) is handed to
RuntimeConnection.forStdio, spawning with windowsHide:true so no
console window flashes in Electron. Falls back to the loader when the
exe cannot be resolved (window may flash - documented degradation).
- modelCatalogLastKnown: a current-only refresh to an id absent from the
persisted list now synthesizes a minimal row (label = the id) instead of
leaving the persisted current id dangling, so a later live failure serves
a list whose current row the picker and the send preflight agree on
- mimoDriver: sdkStreamHandlers hands an absolute catalog deadline down
(outer budget minus 1s headroom); mimo serve readiness and the providers()
query draw from that one pool instead of re-arming independent 25s caps,
so the pair can no longer outrun the 30s outer model-list budget
- sdkStreamHandlers: add XAI_API_KEY to SDK_MODEL_CACHE_ENV_KEYS so two
grok profiles sharing one binary but not credentials never share a
catalog (MODEL_CACHE_ENV_HINTS does not exist in this tree)
- last-known-good store: a current-only catalog (zero visible models,
valid currentModelId) now refreshes the persisted current id via
refreshLastKnownCatalogCurrentId instead of being skipped, so the
user's selection survives a later live failure without clobbering
the persisted models list
- SDK_MODEL_CACHE_ENV_KEYS: add CODEBUDDY_API_KEY, CODEBUDDY_AUTH_TOKEN
and CODEBUDDY_INTERNET_ENVIRONMENT so two codebuddy profiles sharing a
binary but not credentials no longer share one catalog cache key
- isCopilotAutoOnlyFallback: detect the copilot auto row among the
renderer-appended rows (composer custom ids, retained selections)
instead of requiring exact single-entry shape, keeping saved explicit
selections valid when the picker list mixes extra rows
appendRetainedStoredModelPreset reduced a saved selection to its base id,
so a suffixed saved copilot model (e.g. claude-sonnet-4.5/high) produced
a row that no longer matched the stored id: the picker fell back to auto
while the send preflight passed the saved id. Append the exact stored
spelling when no build-time preset defines the base id.
getOpenCodeDefaultModelIds checked the bare value.model string before the
provider-aware shapes, so {provider: 'xiaomi', model: 'chosen'} returned
the unqualified 'chosen' and mimo's visibility check could never match
its fully qualified model ids. Mirror the singular resolver's branch
order.
Match argument snippets conservatively and remove stale snippet rows on input edits before the debounced refresh.
Related to #3622; the email-entry/editor concern remains unverified and the issue stays open.
Preserve pending local notes and conflicting external content across failed writes and successful retries.
Retain explicit clear/import replacement ownership until the current generation persists.
A stored selection preserved through the claude-alias or copilot
auto-only degraded fallback had no preset row, so the picker could show
no selected row while the send preflight passed the hidden value; append
a preset row (reusing the build-time preset when it defines the id).
Last-known-good responses were cached without their warning, so another
panel or a remount within the renderer TTL presented degraded models as
live and hid the Retry action. The warning now travels with the cached
catalog and re-syncs the banner on cached hits.
getOpenCodeDefaultModelId returns only the first default entry, so a
catalog declaring the hidden auto channel before the xiaomi provider made
the filtered catalog fall back to the first model instead of the CLI's
configured Xiaomi default. Enumerate every advertised default and select
the first one that survives filtering.
The auto-only COPILOT_MODEL_PRESETS fallback is nonempty, so a live
listModels failure invalidated a saved explicit Copilot model selection:
shouldUseStoredAgentModel only preserved unknown stored ids when the
preset list was empty, and picker plus send path silently switched to
auto. A saved id keeps resolving while the presets are exactly the
auto-only fallback.
writeLastKnownCatalogEntry sliced models to the 200-entry cap without
checking whether the current model survived the slice, so a huge live
catalog could persist a currentModelId with no picker row and the
last-known-good serve would let the picker and the send preflight
disagree again. Retain the current model in the capped set.
Adopts the PR author's commits for the copilot shutdown race, the mimo
filtered-channel default, and the claude alias fallback presets. Keeps the
complementary pieces: last-known-good mimo read-back normalization and
renderer-side preservation of stored claude aliases against live full-id
catalogs.
Preset lists before the 2026-10 claude catalog refresh persisted
sonnet/haiku alias ids. A live catalog of full ids made
shouldAdoptSdkCurrentModel treat such a stored selection as stale and
silently overwrite it, even though the claude CLI still resolves the
alias. Stored aliases now keep resolving (picker and send path) and are
never replaced by the catalog's current model; other backends and
non-alias ids are unchanged.
stopCopilotClient's 3s grace timer stayed alive whenever stop() won the
race, leaking one handle per turn/catalog load. Clear it in a finally. A
stop() rejection also no longer counts as settled, so the forceStop()
fallback still runs when the graceful handshake fails.
A live default on the filtered mimo auto channel surfaced a currentModelId
the picker could not render or check while sends still went through the
channel. Replace it with the vendor's documented default when the remaining
models offer it, else the first remaining model, else null; ids outside the
filtered channel pass through untouched. Apply the same normalization when
serving a persisted last-known-good catalog captured before filtering.
Cloud sync uploaded the whole encrypted vault as a single payload. Two
problems follow from that, both of which we hit in production:
1. A one-sided deletion propagated to every device. Restoring an older
local backup is indistinguishable from "the user deleted these 25
hosts", so the next sync happily overwrote the cloud replica and
every other machine with the older, smaller vault.
2. AI chat sessions were not part of the sync payload at all, so they
never travelled between machines and never appeared in any backup.
This change adds an opt-in "keep all" merge and makes sessions syncable.
## Non-destructive merge
`mergeSyncPayloads` gains `{ preserveDeletions }`. When set, an entity
missing on one side is carried forward from the side that still has it;
only entities absent from base *and* both sides are removed. It is
exposed as the new `smartMergePreserve` strategy ("Smart merge (never
delete)") and threaded through every merge call site:
- `providerSyncMethods.syncToProviderImpl`
- `syncAllStorageMethods`
- `useAutoSync`
Default behaviour (`smartMerge`) is unchanged, so existing users see no
behaviour change until they opt in.
## AI sessions in the payload
`SyncPayload.aiSessions` is new and opt-in via the
"Sync AI chat history" switch in Settings → Cloud Sync. Sessions are
unioned by id, and messages are merged **append-only**: a message is
identified by id and the merged transcript is the union of all three
sides ordered by timestamp. Two devices that appended different turns to
the same conversation therefore keep both turns, instead of one device
silently dropping the other's work.
Applying a downloaded payload unions with the local transcript rather
than replacing it, so an older cloud snapshot can never delete turns
that only exist locally.
Inline attachment blobs (`base64Data`) are stripped before upload —
image/PDF payloads would multiply the encrypted vault size and may carry
data the user never intended to leave the device. Attachment metadata
(filename, media type, line count) is kept for context.
## Tests
- `domain/syncMerge.preserveDeletions.test.ts` — one-sided deletions
(local and remote), both-sides deletion, additions, notes/snippets/
port-forwarding/string entities, idempotence, summary reporting.
- `domain/syncMerge.aiSessions.test.ts` — session union, append-only
message merge, timestamp ordering, metadata LWW, strategy-aware
deletion, attachment stripping, idempotence.
Verified locally: 1939/1939 tests pass across the domain, convergentSync,
i18n, cloudSync and application sync suites; eslint clean on all touched
files.
Review feedback on PR #3624:
- resolveLastKnownCatalogPath now reads userData through
electronModule.app.getPath, matching the real bootstrap where
electronModule is the require('electron') namespace (previously the
persistence layer was a silent no-op in production)
- the last-known store now caps entries at LAST_KNOWN_MAX_ENTRIES (32,
mirroring the in-memory TTL cache) and evicts the least recently
updated entries, so churned fingerprinted cache keys cannot grow the
store file without bound
- replace em dashes with ASCII punctuation per repo convention
- test stub now mirrors the real electronModule shape
Revert d2b1d5fa2's default preference of the native copilot.exe: the
1.0.59-era native CLI bootstraps %LOCALAPPDATA%\copilot\pkg\ on first
start, starts slowly, and leaves a conhost child alive after app-side
timeout/abort (console window stays open; CLI server exited unexpectedly
with code 0). Loader-first (npm-loader.js) is the default again; the
native binary is opt-in via NETCATTY_COPILOT_PREFER_NATIVE=1.
Also:
- Add COPILOT_MODEL_PRESETS (auto) so a live listModels failure no
longer leaves the Copilot picker empty.
- Sync MIMO_MODEL_PRESETS to the 6 verified live-catalog models
(v2.6/v2.5 pro-ultraspeed/pro/flash); auto channel stays filtered
driver-side.
- Reap the Copilot CLI child on abort/stop: stopCopilotClient() races
graceful client.stop() with a 3s grace and falls back to the SDK's
forceStop() (SIGKILL) so the spawned process does not linger.
npm-loader.js re-spawns the native binary via spawnSync WITHOUT
windowsHide, flashing a console window when the Copilot SDK runs inside
Electron's GUI main process on Windows. When the platform-native binary
(@github/copilot-<platform>-<arch>/copilot.exe) exists next to the
resolved package, hand it to RuntimeConnection.forStdio directly — the
SDK spawns it itself with windowsHide:true. Fall back to npm-loader.js
when the native package is absent (e.g. optional deps omitted).
The catalog path boots a dedicated `mimo serve` and then queries providers();
the inner awaitMimoSetup race (and the serve readiness timeout) was 10s and
lost on the coldest boots even though the outer mimo budget is now 30s. Give
the catalog path a 25s inner budget (MIMO_CATALOG_SETUP_TIMEOUT_MS, passed as
both the spawn readiness timeout and the providers race). runMimoTurn keeps
the 10s default.
Persist each successful live catalog ({userData}/model-catalog-last-known.json,
keyed by the same backend+binPath+runtime+envHash string as the in-memory TTL
cache) and serve it when a later live fetch fails, before degrading to curated
presets. Copilot has no presets at all, so this is its first real fallback.
Empty catalogs are never persisted; corrupt files and IO errors degrade
gracefully; per-entry models are capped at 200. The response keeps the
warning contract and adds source/fetchedAt markers for the UI.
The curated CLAUDE_MODEL_PRESETS fallback still advertised the 4.6
generation while the live CLI catalog serves Opus/Sonnet/Haiku 5.5 and
Fable 5.1. Alias ids (`default`/`sonnet`/`haiku`) are unchanged so the
CLI still resolves them; only display names move: sonnet/haiku now map
to claude-sonnet-5-5 / claude-haiku-5-5 in the Claude CLI alias table
(verified against CLI 2.1.296), and `default` has no fixed alias target,
so it keeps the CLI's own neutral "Default (recommended)" label.
RuntimeConnection.forStdio({ path }) makes the Copilot SDK spawn the
CLI through node WITHOUT shell:true. Windows npm installs expose
copilot as a .cmd shim, and Node >= 18.20 throws spawn EINVAL for
.cmd/.bat in milliseconds, so listCopilotModels()/runCopilotTurn()
could never start the runtime via the PATH-resolved shim.
Add resolveCopilotExecutableForSdk() to shellUtils (same per-CLI
resolver pattern as claude/codex/codebuddy): resolve the shim to the
package's real npm-loader.js entry via the npm layout shortcut, then
by parsing the shim, and pass .exe/.js entries through unchanged.
Apply it at both RuntimeConnection.forStdio call sites; unresolvable
shims keep the original path so behavior is unchanged.
Harness: PATH-resolved copilot.cmd -> npm-loader.js, catalog [auto]
returned in 3.0-3.6s (was: EINVAL in 1-2ms, empty catalog).
listMimoModels() spawned `mimo debug config` only to seed the server's
skills config, which the provider catalog never reads. That probe alone
costs 6-8s on a cold CLI, pushing the whole chain (probe + serve boot +
config.providers()) past the shared 10s model-list timeout, so the mimo
catalog always degraded to an empty list with a UI warning.
- drop the readMimoSkillPaths() call from listMimoModels(); the turn
path (runMimoTurn) still reads skill paths unchanged
- add a per-backend catalog timeout override map; mimo gets 30s because
it cold-boots a dedicated `mimo serve` process per catalog pull
(unlike OpenCode's pooled server), while every other backend keeps
the existing 10s (opencode: 10s + server start) budget
Harness: no debug-config spawn, catalog 7 models in 7.7-10.5s (was:
timeout error at 10s, empty catalog every time).
Add a detached multi-tab editor window with acknowledged content ownership, focused Monaco shortcuts, and shared close/save confirmation. Preserve source contents until handoff acknowledgement and block edits or incoming transfers during ownership changes.
Continuation of #3439. Original implementation by pplulee.
Co-authored-by: pplulee <hi@pplulee.me>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): bound asynchronous Windows PATH refresh
Replace blocking per-terminal registry queries with parallel async reads,
a one-second deadline, and a shared five-second snapshot. Preserve machine
before user PATH ordering and place guessed CLI directories last. Retain
per-hive successful values when registry reads fail, and honor explicit
PATH overrides without querying the registry.
* fix(terminal): cancel pending local starts through boot epochs
Register the existing pending boot controller before awaiting PATH refresh.
Honor tab close and reconnect cancellation before spawning, and clear only
the matching controller so stale completion cannot erase a newer boot.
Regression tests reproduce both the closed-tab PTY spawn and stale-close
ownership failures against the prior revision.
* fix(terminal): abort pending starts before worker close queue
Propagate an owned close to the existing pending boot controller before
waiting for its serialized start. Keep transport cleanup ordered and keep
stale boot-epoch closes from cancelling replacements.
Exercise both fire-and-forget and awaited close messages through the real
worker dispatcher and local-session bridge with a controlled PATH wait.
* fix(terminal): cancel superseded worker starts before queuing
Signal the existing boot-abort controller when a replacement arrives,
reject stale boot epochs, and recheck queued epochs before startup.
Only the latest reconnect may spawn after a pending PATH refresh.
Cover repeated reconnect requests and delayed old starts/closes through
the real worker dispatcher and local-session bridge.
Use xterm's native synchronized output state for viewport preservation, remove the redundant mode tracker, and exercise the production callback path in regression tests.
Fixes#3595
Co-authored-by: si1enteee <si1enteee@users.noreply.github.com>
* feat(vault): add host connection test to the host editor
Add a 'Test' button next to Save in the host editor that opens a dialog reusing the terminal's connection progress/auth/host-key UI, and runs a headless connection probe:
- Main process: netcatty:test-connection reuses startSSHSession in a testMode that authenticates then tears down without opening a shell, reports via netcatty:test:result, and supports cancellation (netcatty:test-connection:cancel).
- Renderer: useHostConnectionTest hook + HostConnectionTestDialog component; single-credential auth (no agent/default-key fallback) with short test timeouts (8s TCP / 15s auth).
- Reuses terminal chain progress, host-key verification, and auth prompts; success keeps the same dialog with retry/close.
* fix(ssh): cancel test connections during chain/proxy dial
Register the headless test-connection transport before connectThroughChain / createProxySocket runs, and update its pendingConn / chainConnections as hops and proxy sockets are established. This lets cancelTestConnection abort a slow/unreachable bastion or proxy dial instead of leaving it running until timeout.
* fix(ssh): address host connection test review feedback
- Route jump-host keyboard-interactive prompts through the external scope in test mode
- Always offer keyboard-interactive for requiresMfa hosts in test mode, including key/certificate-only auth
- Derive the renderer test deadline from the probe options and cancel the attempt on timeout
- Run the macOS Local Network preflight before connection tests
- Add regression tests for jump-host MFA, MFA auth order, test deadline, and macOS preflight
Author: yangheng
Model: claude-opus-5
---------
Co-authored-by: yangheng <492238647@qq.com>
* feat(ai): scope external MCP approvals to connection or host
* test(ai): keep MCP fixture in Netcatty temp directory
* fix(ai): refine external MCP approval UI and grant safety
* fix(ai): wrap long approval targets and support Node 22
* fix(ai): keep automatic scripts in observer mode
* fix(sftp): release preflight markers after transfer
* fix(sftp): retain preflight cap through conflict resolution
* Keep SFTP preflight cap across panel remount
* Update worker terminal metadata expectations
* Persist SFTP preflight skip state across restart
* Limit persistent host approvals to saved hosts
* Scope host-open approvals to the selected saved host
* Exclude serial hosts from persistent approval scopes
* fix(terminal): commit live IME transcription once
xterm sends a rewritten dictation hypothesis as the interim, the full
final string, and the tail. Keep the pending textarea diff from firing
again when composition starts, and send only the part that is not
already on the PTY.
* fix(terminal): retype rewritten IME tails as separate backspaces
A shared suffix has to be deleted and typed again because the cursor
stays at the end of the line. One-character hypotheses are real
prefixes, and each backspace is its own input event so Ctrl-H hosts
still erase.
* fix(terminal): rewrite a revised IME hypothesis in place
A composition that replaces the snapshot, instead of extending it,
sends the cancelled textarea edit. Deletions are counted in graphemes
so a surrogate pair is one backspace.
* fix(terminal): send a new IME word that shares a finished prefix
A composition that starts at or after the snapshot is a new word.
Sharing the first character with text already on the PTY is not
enough to treat it as the same hypothesis.
* fix(terminal): remember a rewritten IME hypothesis in full
A 229 rewrite stored only the inserted tail, so a later
compositionend with no composition marker sent the revised
line again. Remember the whole revised textarea instead.
* fix(terminal): preserve IME rewrite state across delayed commits
Observer mode stays on AI-started scripts that write to the terminal. Compose bar, connect, and panel runs are explicit user actions and no longer read that setting.
* fix(#3559): automated AI fix
* fix: address Codex review on PR #3560
* fix: address Codex review on PR #3560
* fix: address Codex review on PR #3560
* fix: address Codex review on PR #3560
* fix: keep listing size out of the dual-pane transfer plan
A stat-less source can show the pane listing in the overwrite dialog, but that size must not be written back as totalBytes or a short listing truncates the file again. Files past the preflight cap stay on an unknown plan and are not statted a second time.
* fix: stop preflight after a timeout and honor cancel
A half-open browse session no longer walks the whole selection at 10 seconds per file. Cancel during the overwrite size stat is checked before an apply-to-all replace deletes the existing target.
* fix: do not stat remote files again after a failed preflight
A stat that returns no usable size, times out, or never runs is not repeated for the whole selection. The transfer plan stays unknown so the bridge measures the live size.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(ui): apply the chosen app icon to the Windows tray
Settings already swap the taskbar icon. On Windows, the notification
area now follows that choice. Original keeps the multi-size tray ico;
other variants are scaled to the small-icon size Windows asks for.
* fix(ui): crop the app-icon margin before sizing the Windows tray
The variant PNGs keep a transparent inset, so scaling them straight into
the notification slot made every non-original style smaller than the
full-bleed tray ico. Crop to the opaque artwork first.
* test(sftp): update the side-panel reuse source lock
The single-channel change replaced the connected-only sourceSessionId
ternary with reuseTerminalTransport. The source lock still expected the
old expression, so lint-and-test fails on main.
When /proc/<pid>/cwd is unreadable, accept an lsof name ending in (verb: message) only if that path is an existing directory. This drops the AL2023 record "/proc/<pid>/cwd (readlink: Permission denied)" instead of using it as the SFTP follow or upload target. Real directories with parenthesized names stay usable.
Fixes#3493.
Add a host and group toggle for bastions that allow one session channel per TCP connection. After the handshake, BHostSSH, TERM-SSHD, and CLOUDBILITY take that path automatically. File transfer uses a separate SFTP login, and the interactive terminal is not given another exec, SFTP, copy/split, or port-forward channel.
The manual toggle remains for bastions that still advertise OpenSSH.
fontLigatures already defaulted to on, but the terminal never loaded
the ligatures addon, so Fira Code and Maple Mono kept drawing != as two
cells. Activate the addon after open and before WebGL, and rebuild the
WebGL atlas when the setting changes so glyphs are not rasterized
without the calt feature.
The addon bundles lru-cache's node:diagnostics_channel import. Alias
that builtin to a no-op channel in the Vite renderer build and in the
Electron live-test bundles.
Closes#3543
* feat(ai): add MiMo Code (mimo) agent support scaffolding
Registry/wiring only: discovery entry, managed-agent metadata, model presets, icon, i18n and Settings UI for Xiaomi's MiMo Code CLI (an OpenCode fork). The SDK driver is intentionally not wired yet - it depends on the pending @mimo-ai/sdk parity probe, so DRIVER_REGISTRY keeps a TODO and run-turn/list-models reject the mimo backend until then.
* feat(ai): add MiMo Code (mimo) agent support
Adds Xiaomi's MiMo Code CLI as a managed agent backend, mirroring the existing
OpenCode integration (discovery, managed-agent metadata, model presets, icon,
i18n, Settings UI) and adding the SDK driver that was left as a TODO in the
scaffolding commit.
Driver notes:
- New electron/bridges/aiBridge/sdk/mimoDriver.cjs reuses the pure translation
helpers exported by opencodeDriver.cjs (config/prompt building, event
translation, model mapping) because MiMo Code is an OpenCode fork and speaks
the same wire protocol.
- It does NOT use @mimo-ai/sdk's createOpencode(). On 0.1.15 that helper spawns
a binary named "opencode", waits for an "opencode server listening" banner,
and passes config via OPENCODE_CONFIG_CONTENT; the mimo CLI prints
"mimocode server listening on <url>", reads MIMOCODE_CONFIG_CONTENT, and ships
as "mimo"/"mimo.exe". So createOpencode() can only ever time out. The driver
runs "mimo serve" itself, parses the readiness URL, and attaches the SDK's
createOpencodeClient({ baseUrl }).
- Binary resolution honours binPath, MIMOCODE_BIN and MIMOCODE_BIN_PATH, then
falls back to PATH via resolveCliFromPath (the npm shim is mimo.cmd on
Windows, which Node cannot spawn directly).
- @mimo-ai/sdk ^0.1.15 added as a dependency.
Verified on Windows against mimo 0.1.15 + @mimo-ai/sdk 0.1.15:
- "mimo serve --port" boots and reports its URL; driver parses it.
- SDK symbols used all exist: createOpencodeClient, client.config.providers(),
client.global.event() (SSE stream), client.session.create/abort/promptAsync.
- Live provider catalog reads 2 providers (xiaomi, mimo); MIMO_MODEL_PRESETS
updated to the real lower-case provider-prefixed ids and its TODO removed.
- Server process is killed on close (taskkill tree kill), no orphan processes.
Not yet verified at runtime:
- A full end-to-end turn (no prompt was sent, so no tokens were spent): text /
reasoning / tool-part streaming and abort handling are untested against a
live model.
- MIMOCODE_CONFIG_CONTENT accepting the OpenCode config schema (permission,
mcp, model, share, autoupdate) end-to-end; only that the env var exists in
the binary has been confirmed.
- Whether the loopback server needs MIMOCODE_SERVER_PASSWORD when the API
client attaches.
- The placeholder public/ai/agents/mimo.svg still needs an official asset.
- Multi-turn resume (resumeSessionId) and MCP injection against the live CLI.
* feat(ai): inject MIMOCODE_BIN for discovered MiMo Code agents
Mirror the OpenCode OPENCODE_BIN handling so a discovered or re-detected
mimo binary path reaches the driver, and cover it with the same assertion
shape as the opencode managed-agent env test.
* fix(ai): use MiMo branding in the mimo driver
* test(ai): cover the mimo driver
Add a unit test file for the MiMo Code driver, matching the existing
opencode driver test in shape, runner (node:test via node --test) and
`.test.cjs` extension. It is the last driver in the registry without
one.
Coverage:
- spawn/launch: `mimo serve --hostname/--port` argv, binary resolution
(explicit binPath / MIMOCODE_BIN / MIMOCODE_BIN_PATH), pass-through
env and MIMOCODE_CONFIG_CONTENT; `child_process.spawn` is stubbed, so
no real `mimo` process is ever started.
- readiness: parsing of `mimocode server listening on <url>` from
stdout (including split chunks) and stderr, the OpenCode spelling,
and the MIMO_SERVE_TIMEOUT_MS timeout path (asserts the rejection and
that the child is killed).
- abort/cleanup: stopMimoProcess kill/idempotency/swallow, the Windows
taskkill tree kill, server.close() teardown, and abort mid-turn with
a throwing close().
- branding: OpenCode -> MiMo Code for the busy/idle/status, failed and
tool-failure fallback strings, with tool output and non-brand
technical errors left verbatim.
- happy path: session create -> streamed event translation -> done,
plus listMimoModels catalog mapping and the empty/aborted paths.
* fix(ai): filter MiMo events by session and stop the server behind the npm shim
Address review on #3500:
- The global SSE stream carries every session on the server, and
runMimoTurn translated all of them, so another session's text could leak
into the reply and its session.idle could end the turn early. Skip events
whose session id is not ours, reusing the OpenCode driver's
getOpenCodeSessionIdFromEvent (now exported). Covered by a test with
interleaved sessions.
- On macOS/Linux the official @mimo-ai/cli `mimo` is a Node shim that runs
the native binary through spawnSync, so killing the shim left the server
listening. `mimo serve` is now spawned detached on POSIX and
stopMimoProcess signals its whole process group (SIGTERM, then SIGKILL
after a grace period). Windows keeps taskkill /T.
Verified against the real @mimo-ai/cli@0.1.15 npm shim: on Linux (WSL
Ubuntu 22.04) the listener is the native .mimocode child; before this change
it was still listening 3s after close(), now the port is released within 1s
and no mimo process remains. On Windows the .cmd shim path also releases the
port within 1s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: harden MiMo CLI integration before merge
* fix: wait for MiMo events and handle setup cancellation
* fix: finish MiMo stream handling and scope skill access
* fix: preflight MiMo skill paths before one server startup
* fix: scope MiMo skill reads to trusted roots
* fix: allow MiMo skills from ancestor project roots
* fix: honor MiMo observer and confirm command permissions
* fix: clear MiMo approval when its service exits
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* feat(vault): let Skills + CLI operate notes with the same approval as MCP
Expose vault note list, read, write, and markdown import on the tool CLI so agents can save generated documents only after the user approves.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): keep explicit empty note fields in the CLI
A supplied empty string was treated as an omitted flag, so note clears and empty markdown imports never reached the note service.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): allow an explicit empty note body on CLI create
notes create --content "" was rejected as a missing flag, so it did not match vault_notes_create.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): stop echoing imported note bodies and recognize leading CLI flags
A batch import resent every document over IPC, and a leading --json hid the notes command from artifact inference.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): make note CLI import safe and fail writes cleanly
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
A local terminal inherited the PATH snapshot the Electron main process
captured at launch. A GUI-launched or auto-started Netcatty keeps that
snapshot for its entire lifetime, so a CLI installed or upgraded afterwards
is missing inside Netcatty while resolving fine in an external
cmd/PowerShell. The upgrade case is the worst: installers that rewrite a
versioned user-PATH entry leave the captured entry pointing at a directory
the upgrade deleted, so the command disappears entirely.
Reuse the registry re-read the AI harness already performs for its shell
env. resolveWindowsLivePath() merges the HKCU/HKLM PATH (with %VAR%
expanded) and the well-known npm/pnpm/Yarn bin dirs ahead of the captured
PATH, and buildLocalSessionEnv() applies it to local sessions on Windows
only, degrading to today's behaviour off Windows or when the registry
cannot be read.
An explicit PATH in the session payload is never widened, and PATH/Path
duplicates are collapsed so the effective value stays deterministic.
* feat(terminal): Add configurable terminal bar cursor width
Adds a configurable bar-cursor width for the terminal so thin vertical cursors are easier to find, particularly when vi/vim switches to insert mode.
* Add terminal bar cursor width setting
This adds the terminal bar cursor width control to the settings UI with an aria-label for accessibility and a matching test. It also adds localized labels/descriptions and search catalog entries so the setting is discoverable in both English and Chinese.
* Fix terminal cursor width override bug
Prevent width-only cursor updates from clearing DEC/vi cursor overrides by tracking the last applied shape/blink snapshot. The runtime now resets the snapshot on teardown, skips full cursor reapplication for width-only changes, and preserves remote bar/underline cursor styles. Added regression tests covering boot-time and later width updates.
* Fix terminal cursor preference lifecycle
Preserve DEC/vi cursor overrides during the first width-only terminal update after runtime creation. The runtime now records the user cursor preference snapshot when a term is published and applies width-only updates without clearing active cursor style or blink overrides. This prevents regressions when terminal settings change before the panel has fully booted.
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* terminal: allow Shift+Enter text inside ConPTY Win32 input mode
Win32 input mode (PR #3247) delivers Shift+Enter to ConPTY as a lossless
INPUT_RECORD, which CLIs reading console records natively (Codex, Grok) can
use. Node/Bun CLIs read input through libuv, which drops modifier state when
translating INPUT_RECORD to VT bytes, so Claude Code and CodeBuddy still see a
bare CR and submit instead of inserting a newline (anthropics/claude-code#92771,
reproducing on 2.1.278).
While the mode is active, Netcatty also skips its Shift+Enter text fallback and
Kitty encoding, so shiftEnterNewlineText had no effect on local Windows
sessions at all.
Add shiftEnterForceText (default false): when enabled, Shift+Enter sends the
configured text even inside Win32 input mode, and consumes the keydown so no
native record is emitted as well. Add `\e` (ESC) to the escape decoder so the
text can carry the legacy Alt+Enter encoding, which is the only sequence that
survives the ConPTY input path.
Defaults keep the #2974 behaviour untouched for Codex/Grok.
* docs: record libuv input findings and the Win32 input mode opt-out
* fix(terminal): don't swallow Shift+Enter when there is no text to send
The Shift+Enter send-text interception resolved its text after already
calling preventDefault/stopPropagation, and returned false outside the
`if (shiftEnterText)` guard. When resolveShiftEnterText() yields an empty
string (e.g. a persisted empty setting) the keydown was therefore consumed
with nothing written, and the handler never fell through to the Win32
bookkeeping or Kitty paths below, so no Enter reached the session either.
The result was indistinguishable from a dead feature.
Resolve the text first and only enter the interception block when it is
non-empty; empty text now falls through exactly as it did before the
`shiftEnterForceText` setting existed, so a normal Enter is still produced.
The opt-out semantics for the non-empty case are unchanged.
Also tighten the source-shape test so it verifies the real intent: the
interception (preventDefault through return false) must live inside the
empty-text guard, which fails against the previous code.
* fix(terminal): scope Shift+Enter force-text to Win32 mode, honor it per broadcast target
Address review on #3464:
- The force-text opt-out could override a negotiated Kitty encoding that
preserves Shift+Enter in non-Win32 sessions. The keydown decision now
lives in shouldClaimShiftEnterForText: under Win32 input mode it follows
shiftEnterForceText, otherwise only the existing collapse fallback
applies. Covered by a regression test for a non-Win32 session whose Kitty
encoding already preserves the chord.
- Broadcast Win32 targets returned the native record without consulting
their own shiftEnterForceText. They now send their configured text for
Shift+Enter when opted in, whether the source forwarded a key chord or a
native Win32 record, and drop the paired release so ConPTY never sees a
key-up without its key-down. Targets without the opt-out keep the native
press/release pair. Tests cover source and target settings that differ.
ShiftEnterEvent now accepts KittyKeyboardEvent's optional type/modifier
fields so broadcast events can be checked with the same helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The flaky Windows run timed out in where.exe inside startMoshSession,
before any wait started. findExecutable then fell back to the in-box
OpenSSH, which never prints the fixture's MOSH CONNECT line, so no ready
event could arrive no matter how long the test waited.
Pass the existing findExecutable option so the test always uses its
compiled fake ssh.exe without shelling out, and keep the 15s wait: locally
the whole handoff finishes in about 7s.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix: prevent cloud sync version inflation when idle
Three bugs combine to cause an infinite upload loop where the sync
version increments every ~2.5 minutes even with no user interaction:
1. Smart merge unconditionally uploads (useAutoSync.ts):
`if (mergeResult.payload)` is always true since payload is an object.
Now checks `cloudSyncPayloadsEqual(portableMerge, remotePayload)`
before uploading — if merged result matches remote, skip the upload
and mark data as synced.
2. Download-remote re-uploads to same single provider (useAutoSync.ts):
When only one cloud provider is connected, downloading the remote
payload and immediately re-uploading it to the same provider bumps
the version unnecessarily. Now checks `readyProviders.length > 1`
before round-tripping.
3. applySyncableSettings bumps versions unconditionally (syncPayload.ts):
customAccent, terminalFontSize, and customKeyBindings handlers always
bump their storage version counters even when incoming values are
identical to existing values. This fires LOCAL_STORAGE_ADAPTER_CHANGED_EVENT
and triggers a debounced syncNow(). Now guarded with change detection:
- customAccent: compare parsed color values
- terminalFontSize: compare fontSize numbers
- customKeyBindings: compare JSON-serialized bindings
Observed impact: lastSyncVersion reached 7796+ with LevelDB compaction
every ~155 seconds matching getRuntimeRemoteCheckIntervalMs(5).
* fix(sync): avoid repeated versioned setting writes
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Requiring mcpServerBridge from node --test inherited the production
netcatty-tool-cli/discovery.json path, so cleanup() removed the running
app's bridge pointer. Resolve the discovery path on demand and isolate the
affected tests via NETCATTY_TOOL_CLI_DISCOVERY_FILE.
Rebased onto #3502, which now owns the self-heal (live-host reuse, status
and content validation); this change only adds on-demand path resolution to
those helpers and drops the separate 30s missing-file sweep.
The subprocess regression test redirects HOME/USERPROFILE as well as
APPDATA/XDG_CONFIG_HOME, since macOS derives the default path from
os.homedir(), and refuses to plant its canary anywhere outside the
throwaway root, so it can never write the real app path on any OS.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(#3496): automated AI fix
* fix: address Codex review on PR #3499
* fix: address Codex review on PR #3499
* fix: keep live model catalogs scoped and surface discovery failures
* fix: isolate Claude catalog profiles and support xhigh effort
* fix: handle missing external model catalog state
* fix: isolate Claude model catalogs by credentials
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(terminal): keep broadcast going on vim ex command-line
Broadcast pauses on fail-closed colon prompts so passwords are not
copied to peers. Vim command mode's last line is a bare `:`, which
matched that heuristic. Exempt lines that start with `:` so `:w` and
friends stay in sync; `Password:` is unchanged.
* fix(terminal): only treat a bare colon as vim command-mode
Codex: exempting every `:`-prefixed line would broadcast into custom
prompts such as `sudo -p ':'`. The hang is the first `:` key; later
`:w` no longer matches the trailing-colon heuristic.
* fix(terminal): exempt vim colon only on the alternate screen
A bare `:` on the primary screen can be `sudo -p ':'`. Vim command mode
uses DEC 1049, so require alternateScreen before treating `:` as ex.
* test(terminal): allow alternateScreen in prompt-security options
* fix(mcp): drop mustache braces from tool descriptions
Hosts that interpolate {{name}} in tools/list text treat the literal
{{variables}} snippet wording as a missing template variable and abort
the turn. Use plain language instead and regenerate the committed specs.
* fix(mcp): describe snippet placeholders without mustache tokens
Keep agents informed that snippets use two curly braces on each side,
without emitting a literal {{name}} token that template hosts interpolate.
* fix(#3455): automated AI fix
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix(terminal): let explicit SFTP auto-open beat the saved layout
A JumpServer SFTP deep link or host.autoOpenSftpPanel must still open
file transfer when the saved default layout omitted SFTP.
* fix(terminal): skip saved layout only when SFTP auto-open is available
Telnet/plugin hosts can carry autoOpenSidePanel=sftp even though the
protocol cannot serve SFTP. Those sessions should still get the saved
default with the SFTP pane pruned.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Interactive bash prints PS2 on the probe's backslash continuations, so
strict startsWith checks miss `_Q`/`_P:` and silent PTY execs hang until
the command timeout. Strip common prompt prefixes in the parser; keep
the existing short-line probe for OpenWrt.
* Add local shell side panel auto-open settings with i18n support
- Add settings to control side panel behavior for local shell sessions
- Support auto-opening side panel when local shell is launched
- Allow users to choose which pane (Files, SFTP, Scripts, etc.) opens by default
- Add complete i18n translations for en, zh-CN, zh-TW, es, ru locales
* fix: wire local shell side panel settings
* fix: restore undefined check for local shell protocol detection
Local shell sessions may have protocol=undefined, not just 'local'.
Restore the original condition to auto-open sidebar for both cases.
* fix: enable SFTP sidebar for local shell sessions
Local shell now supports SFTP (for local file browsing).
Update sftpAvailable to include local shell so the auto-open
sidebar setting works correctly for PowerShell/bash sessions.
* chore: bump version to 1.1.83-fork.1
Distinguish fork from upstream (1.1.83) and previous 0.0.0 dev version.
* feat: show SFTP button in local shell toolbar
Local shell sessions now display the 'Open SFTP' button in the toolbar,
allowing users to browse local files via SFTP sidebar, consistent with
the auto-open behavior added earlier.
* feat: 本地Shell侧边栏支持追随终端目录功能
- 追随终端目录现在支持本地 POSIX shell(bash/zsh/fish)
- 排除 PowerShell 和 cmd(因为它们的 cd 命令和路径格式不同)
- 串口连接仍然不支持(它们根本没有 SFTP)
- 定位路径到终端功能已支持本地 Shell(无需修改)
* feat: support PowerShell and CMD in session restore
- Accept Windows absolute paths (C:\...) in isRestoreCwdPathEligible
- Add shell-specific cd commands:
* PowerShell: Set-Location -LiteralPath with '' quote escaping
* CMD: cd /d with "" quote escaping
* bash/POSIX: cd -- with '\'' quote escaping
- Remove PowerShell/CMD restrictions in cwd injection eligibility
- Fix line terminator in SFTP locate path (use \r\n for Windows PTY)
* fix: bound local shell panel preferences and synchronize settings
---------
Co-authored-by: HHXXYY123 <HHXXYY123@outlook.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* Default WSL launches to Linux $HOME
When launching WSL on Windows, ensure the distro starts in the Linux home if no explicit working directory was provided. Adds isWslExecutable and getWslLaunchArgs which append `--cd ~` for wsl.exe calls that lack an explicit cwd or --cd option. startLocalSession now uses getWslLaunchArgs when building shell args. Exports getWslLaunchArgs and adds a unit test verifying the behavior. This prevents WSL from failing early when the parent Windows cwd isn't mounted or accessible to the chosen distro.
* Insert --cd before WSL command/--exec
Ensure WSL launch uses Linux home by inserting --cd "~" before the first bare command or --exec/-e boundary so the option is consumed by WSL rather than passed to the invoked shell/command. Early-return when not a WSL executable, when an explicit cwd is supplied, or when --cd is already present. Also account for WSL options that take a separate value (--distribution/-d, --user/-u). Adds a unit test verifying --cd is placed before --exec.
* fix(terminal): preserve WSL command argument boundaries
* fix(terminal): retain legacy WSL distribution selectors
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Introduce a new TerminalTabDoubleClickBehavior setting (duplicate | copy | disabled) with default 'duplicate'. Add model field, normalization, default, and sync key (tabDoubleClickBehavior). UI: TerminalBehaviorSettings adds a Select; AppView and TopTabs pass the setting through; TopTabItems implements createTopTabSessionDoubleClickHandler to perform duplicate/copy with a safe fallback. Add i18n strings, settings search entry, and update tests to cover behavior and serialization. Improves user control over double-click actions on session tabs.
* fix: bound AI command input for OpenWrt BusyBox
* fix: address Codex review on PR #3342
* fix: preserve custom prompts and split command history
* fix: preserve zsh history suppression for split AI input
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* fix(ai): keep AI command execution connected through bastion hosts (#3146)
Approving an AI assistant command after jumping to a target through a
menu-driven bastion (BHostSSH, QiZhi TERM-SSHD) disconnected the session.
Two separate failures were involved:
1. The AI probe/wrapper was written to the interactive PTY as one
multi-character channel write. Strict bastions treat one SSH channel
write as a single keystroke and silently drop multi-character writes, so
the wrapper never ran; the inactivity timeout then sent Ctrl+C, which the
bastion read as "exit". Type the probe and wrapper one Unicode code point
per write to match human keystrokes.
2. ensureSessionShellKindForExec opened a second SSH channel (exec) to probe
the login shell. Bastions bind the transport to the first interactive
session and tear it down when a second channel opens. Skip the
exec-channel probe for bastion banners and let the PTY live shell probe
determine the wrapper instead.
* fix(ai): preserve paced delivery and scope bastion keystrokes
* fix(ai): pace bastion input for Windows shells too
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix: unify host system detection and simplify host details
* fix: retain detected systems for temporary terminal hosts
* fix: detect remote systems after Mosh becomes ready
* fix: show the effective device mode in host system details
* fix(terminal): align history preview with rendered cell dimensions
* fix(terminal): fit wide history rows without changing copied text
* fix(terminal): select every history preview row
* fix(port-forwarding): reconnect auto-start rules after inactive status
* fix(port-forwarding): honor current auto-start setting during reconnect
* fix(port-forwarding): ignore retries from replaced connections
* fix(port-forwarding): preserve exhausted retries after close
* fix(port-forwarding): schedule one retry per failed connection
* fix(port-forwarding): retain pending retry status after repeated errors
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Allow global broadcast hotkeys for visible orphan sessions and centralize target resolution with coverage for workspace, global, and direct-target modes.
Keep stored boolean updates pure and propagate global broadcast state through terminal panes so orphan sessions can toggle and receive broadcasts correctly.
Select default command-safety rules from the live session shell so legal
PowerShell subexpressions are allowed and PowerShell-native dangerous
commands are blocked. Untouched pre-shell-aware lists gain the new
defaults on read.
Closes#3256
The original bug is: lock + display sleep moves a secondary-display window
to the primary, and unlock never puts it back. Review follow-ups had grown
a general display-identity machine far beyond that.
Restore only while the session is locked/asleep, or shortly after unlock.
Keep user moves authoritative. Drop Win+Shift destination tracking and
multi-unknown-display correlation.
Hosts were hardcoded to os 'linux' at every creation and update entry
point, so Windows hosts were always reported as linux by the AI
environment (get_environment) and nothing could correct them.
- buildVaultHostFromDraft parses draft.os with a strict allowlist
(linux/windows/macos, common aliases included) and defaults to linux
- applyVaultHostUpdate accepts os with the same validation
- the AI host.update field list and the MCP vault host tool schemas
expose os so agents can set and correct it
Empty pane arrays are not a first-visit signal; they also happen after
the user closes every tab. Route every Open SFTP request through the
planner, and do not forceNewTab on an empty side.
The empty-left shortcut was racing first-visit auto-connect, but it also
fired after the user closed every left tab. Open SFTP then skipped the
planner and could replace the right pane. Only skip the planner when
both sides are still empty.
A matching hostId in error or disconnected state is not a live session.
Treat only connected/connecting panes as reusable so Open SFTP reconnects
in place instead of selecting a dead tab.
Skipping realpath on plain directories also dropped them from the ancestor
set, so a symlink back to a non-root ancestor duplicated that subtree.
Compose the resolved parent path with the child name instead, and still
realpath directory links.
Address the SFTP UX list in #3240: wider bookmark management with
confirm/reorder/rename, a compact file-list density, a host-list shortcut
into the existing local/remote dual pane, faster local folder discovery,
and higher small-file upload concurrency.
Tree view already supports double-click to enter directories, so that
item is left as-is.
Keychain identities can change after the host copied username. Use the
same resolveHostAuth username as sudo autofill so a stale host.username
does not skip or force elevation.
Keychain identities clear host.password. Reuse the same host-auth
resolution as sudo autofill so those saved identity passwords can
elevate a terminal drop into /root.
After sudo -i, terminal drag into /root uses the host's saved password for
SFTP sudo. If no usable password is saved, the drop fails with guidance to
enable host Sudo elevation or return to the user directory.
Automatic SFTP reconnect still prefers the originating session, then falls
through to a fresh open so a dead channel after sudo -i can re-elevate.
Do not treat a lagging split-pane focus as the user leaving the drop
origin, and do not bind a Mosh/ET drop to the still-focused SSH session.
First-open follow treats a missing SSH id as bound to the live focused
terminal so a probe from A cannot apply after focus moves to B.
A follow sync that started probing session A must not navigate after
focus moves to session B. Capture the expected session before the
await and reuse the same guard already used for go-to-terminal-cwd.
Keep confirmed active-shell destinations, originating-session bind, sudo
fidelity, and the longer ZMODEM start wait. Remove the dormant queue,
routeSessionId identity, and source-regex contracts that only locked
that extra routing in.
Add Duplicate Session as an explicit fresh-login alternative to Copy Tab. Keep authenticated transport reuse for Copy Tab and double-clicks, preserve fresh intent across session restoration, and avoid sending a previous target's directory command to a fresh bastion login.
Verified with focused and full tests, lint/build, independent local reviews, and real Electron menu operations against a local SSH server.
Fixes#3221.
Release consumed second-instance argument buffers after deriving connection requests from an independent ordered copy. This closes the late password-retention finding on #3220 without changing connection or Explorer path routing.
Verified with regression tests, the full local suite, lint/build, two independent local reviews, and a real two-process Electron SSH connection that checks credential delivery and removal of both transport copies.
Related to #3218 and #3220.
Restore full height through the AppLockGate background wrapper, which otherwise lets the tray content collapse to about half of its window height. Preserve the existing lock-screen interaction restrictions.
Add real Electron regression coverage for repeated opening, session lists, lock/unlock, short windows, and Escape. Run it on macOS and Windows, including 100% and 200% device scale on Windows, and trigger it for the relevant renderer changes.
Fixes#3181
A wrong master password can decrypt to a NUL mid-buffer and leave a short
printable UTF-8 prefix. Treat only a trailing NUL pad as a C string so
vectors like wrong25 no longer save garbage host passwords.
AES-CFB has no authentication, so a mistyped master password can decrypt
to printable Latin-1 junk that was previously saved as a host password.
Require valid UTF-8 plaintext, fail closed if any ciphertext does not
decrypt, and keep leading or trailing whitespace in the master password.
Sanitize lastConnectedAt in convergent equality, change summaries, and
v1-to-v2 conversion so legacy telemetry cannot block migration or leak
into CRDT state.
Bound the post-commit remote chmod with the same best-effort timeout
used for destination mtime stamps. A stalled chmod after promotion
now warns and lets completion/cleanup continue instead of pinning
the transfer, SFTP lease, and admission slot.
Drop the extra pre-transaction mode stat so cancel can settle and
existing call-count tests keep their first stat as the planner/final
size check. Restore replace mode from the transactional upload plan
snapshot instead, and hang the in-place cancel regression on the
post-OPEN size verification.
Replace uploads recreate the remote file via fastPut / pipelined WRITE
with umask defaults, which dropped executable and other mode bits.
Capture the existing remote mode before the transfer and chmod it back
on the published final path after success.
Fixes#2954
Vendor bastion/network-device CLIs disconnect when live-preview injects
Ctrl-U or backspaces while typing cd/cat. Keep the popup, but treat
network-device sessions as preview-off until the user accepts.
Fixes#1193
Claude Code Ajv does not load that dialect, so every triage died before
the model ran: no issue reply and no diagnosis. Strip $schema/$id before
--json-schema.
Route and several other jobs required $RUNNER_TEMP/ai-automation.cjs after
checking the helper out to the workspace, so GitHub never reached classify.
#3181 dispatch failed immediately with MODULE_NOT_FOUND.
Unindented Brave helper heredocs made GitHub reject ai-automation.yml.
The workflow never registered issues/opened, so new issues only got the
format check and template triage label.
Arm a one-shot window/document capture listener on non-primary
pointerdown so releasing over the portalled context menu still
restores draggable, and share the helper across both surfaces.
Identity recovery cleared the temp-root cache but kept the in-memory
signing key from the old inode. Drop that promise so the next load
picks up the replacement key file instead of signing with a stale key.
Point spawnSync children at isolated TEMP/TMP/TMPDIR so Windows cannot
rm the real Netcatty temp root. Keep the old inode allocated via rename
so the replacement test cannot pass against recycled inodes.
A resolved cursor-agent binary is a user install even when status JSON
is missing, unrecognized, or the status command throws. Login still
requires parsed authenticated status output.
Right-clicking a script package in the terminal scripts tree now shows
Rename and Delete. Vault package and snippet cards use asChild triggers
and ignore non-primary drags so Windows context menus are not swallowed.
Netcatty ships @cursor/sdk, so probing it from the Electron main process
always succeeded and Settings showed Cursor installed even with no
Cursor Agent CLI on the machine. Treat installed as CLI presence or
proven CLI login; keep sdkInstalled for API-key mode.
Swap issue triage, implement, follow-up, and Codex-fix jobs from Cursor
CLI to Claude Code against Ollama Cloud (glm-5.3-flash:cloud). Isolated
research now uses Brave Search. Automation files and repo variables use
an ai- prefix; existing cursor-* HTML markers and cursor/issue-*
branches still parse so in-flight work is not reprocessed.
SFTP downloads fail when the cached Netcatty temp root still exists but
its inode changed (Windows temp cleaner, worker mkdir race). Treat that
identity mismatch like ENOENT: clear the cache, re-run full safety
checks, and rebind instead of aborting the transfer.
Keep the retry markdown override only while the parent prop still contains
the stale body that originally failed MDX import. Once NotesManager's
source-draft debounce catches up, the editor goes back to normal prop
synchronization instead of keeping an old retry seed around.
Co-Authored-By: Claude <noreply@anthropic.com>
Codex review follow-ups for the unrenderable-markdown fallback:
- Retry now seeds the MDX import from the editor's latest accepted source
draft. NotesManager debounces source drafts (300 ms), so retrying right
after fixing the markdown could remount the rich editor with the stale
pre-fix `value` prop and drop straight back into the fallback.
- The read-only preview fallback no longer mutates the note: NoteSourceEditor
skips its custom Tab insertion, undo/redo handling and imperative
insertAction when readOnly is set (the DOM readOnly attribute alone did
not block those paths).
Co-Authored-By: Claude <noreply@anthropic.com>
The Vault notes rich editor parses note content as MDX. Some valid
CommonMark is not valid MDX — most commonly an unbalanced angle tag such
as `mysql -h <host> -u <user>` written in prose. When mdxeditor fails to
import the markdown it clears the lexical document and only reports the
failure through its onError callback, which netcatty never wired up. The
note then rendered as an empty editor with just the placeholder, while
the content was still intact in the vault (export/copy still worked).
This is exactly the note-view half of #3167: the AI wrote a skill
document into Vault → Notes, the note showed no content, and exporting
it produced a complete markdown file.
The editor now listens to MDXEditor's onError and falls back to the raw
markdown source view with a short notice, so the content is always
visible (and editable) instead of silently blank. Recovery is
deterministic: switching note or editor mode retries the rich import,
and the notice offers an explicit retry.
Co-Authored-By: Claude <noreply@anthropic.com>
Strict bastion prompts (QAX/奇安信) treat one SSH channel write as a single keystroke and silently drop every multi-character chunk, so an IME commit of two or more hanzi and a Chinese paste both reach the bastion's search prompt as nothing at all. Single characters work, which is why typing them one at a time is the only path that works today.
Split plain multi-character text at the send boundary instead of adding a setting: the write side of handleTerminalInputData now iterates one chunk per Unicode code point, and all bookkeeping (command buffer, script recorder, autocomplete, sudo autofill, broadcast, scroll) still sees the original payload exactly once. IME commits split whenever the committed text has more than one code point; raw (non-bracketed) onData splits up to 32 characters and longer pastes keep the single write. Anything containing an escape sequence - bracketed paste wrappers, CSI-u composition, forwarded Kitty key sequences, cursor reports - is never split, and broadcast peers still receive the original string once.
Each chunk carries the same writeToSession options as the original single write, so the sensitive marker and automation flags are kept per character. Bridge writes stay one channel write per IPC call, which reproduces what Xshell sends on the same bastion (#3077).
Co-Authored-By: Claude <noreply@anthropic.com>
When the punctuation keyup was dropped and a previously held key was released
next, the recovery flushed the deferred key (recording a Kitty press) but then
released only the unrelated key, leaving the deferred press forwarded until
focus loss.
Replace the sentinel-rewrite predicate with resolveDeferredKeyupRelease, which
enumerates all three cases: sentinel releases take over the deferred key's
release identity, the deferred key's own release pairs the press directly, and
an unrelated release keeps its identity while the deferred press gets a
synthesized release.
Clearing a history object reused by a new session kept the previous
session's cell column, so a leading tab in the fresh output padded to the
wrong tab stop.
Co-Authored-By: Claude <noreply@anthropic.com>
Follow-ups on the alternate-screen preview transcript:
- Cap the pending escape tail on its introducer, so an oversized control
string split across chunks is not re-parsed as transcript text.
- Write spans in bounded pieces, committing a full line at the character cap,
instead of clipping a span and dropping its remainder.
- Track the cursor in terminal cells (not UTF-16 length) so tab stops land
correctly after wide glyphs.
- Apply erase-in-line (CSI 0K / 2K) to the open line, so a shorter progress
redraw leaves no stale suffix text behind.
Co-Authored-By: Claude <noreply@anthropic.com>
A modified keydown (Ctrl+C, Alt+...) is a command, not the continuation of the
lost punctuation keystroke. Leaving the deferral armed across it meant the next
ordinary keydown flushed the pending character in front of the user's input,
right after the interrupt or shortcut ran.
Discard the stale deferral when the next keydown carries Ctrl/Alt/Meta; an
active composition still resolves it via insertText.
Follow-ups on the alternate-screen preview transcript:
- C1 control-string introducers (DCS/SOS/OSC/PM/APC) were filtered as stray
control bytes, leaving their payload as transcript text; consume them
through the BEL / ESC-\ / C1 ST terminator like their ESC-based forms.
- Tabs were kept as-is, so a row's cell width under-counted against how the
preview pre element renders tab stops and long tabbed lines were clipped
instead of wrapping. Expand each tab to the next 8-column stop while
writing, and route tab padding through the same open-line budget as text
spans.
Co-Authored-By: Claude <noreply@anthropic.com>
The keydown recovery for a swallowed punctuation keyup flushes the deferred
key, which records a Kitty press locally and for broadcast peers. Since that
branch only runs when the release will never arrive, the press stayed
forwarded and the TUI saw the key held until focus loss or session reset.
Extract the forwarded-press release pairing into releaseForwardedKittyPress and
call it from the recovery with a release synthesized from the deferred physical
key, so the press/release pair completes in the same keystroke.
Two robustness gaps in the alternate-screen preview transcript:
- A cursor-addressed redraw (cursor-home CSI stripped, no LF) appended every
frame to the open line, which the character cap only checked on commit, so
it grew without limit and every append copied it whole. Commit the open
line once it reaches the budget and clip each span to the remaining room.
- Escape sequences were assumed to be two bytes, so intermediate bytes leaked
their final byte into the transcript (ESC ( B from ncurses/reset left a
stray B). Consume intermediates (0x20-0x2f) through the final byte
(0x30-0x7e) per ECMA-48.
Co-Authored-By: Claude <noreply@anthropic.com>
A real keyup for another held key could land while the punctuation deferral
was still armed (the deferred key's own release was swallowed). That release
was rewritten to the deferred physical key, so in Kitty report-event modes the
held key's forwarded press never got its release and the TUI saw it stuck down
until focus loss or session reset.
Restrict the rewrite to IME sentinel releases (Process/229/Unidentified/
Dead/Compose, mirroring the non-text DOM keys the Kitty encoder already
refuses to send as text). Any real release still ends the stale deferral, so
the #3103 recovery is unchanged.
Plain wheel in screen/vim/codex still forwards arrow keys (xterm's
alternate-scroll), and shift+wheel previewed xterm's normal buffer, which
holds no scrollback once an app owns the alternate buffer - so the preview
clamped to a single screen and looked dead (#2516).
When the alternate buffer is active and the normal buffer has no rows above
the viewport, the preview now renders the session's captured output instead:
a bounded plain-text transcript kept next to the connection log capture,
after the programmatic command rewriter (masked commands stay masked) and
before the replay sanitizer, which drops alternate-screen output on purpose.
Rows are wrapped to the terminal columns with wide-character awareness and
reuse the existing wrap-flag selection joining.
Mouse-reporting apps are untouched: their wheel events still go to the app.
Co-Authored-By: Claude <noreply@anthropic.com>
The CJK IME punctuation deferral (#2833) armed on a punctuation keydown and
only resolved on an insertText remap, an exact-key keyup, or a session reset.
Windows IMEs report Process/keyCode 229 as the keyup of a key they consumed,
or drop the keyup, so the deferred key stayed armed: the deferral then blocked
every keypress, and a second "/" search in vi (or any later keystroke routed
through keypress) never reached the PTY (#3103).
The deferral now ends on any real key release (composing releases excluded, so
an active composition still resolves via insertText) and a stale deferral is
flushed when the next unmodified, non-composing keydown for a different key
arrives. The keypress block is scoped to the deferred key instead of every
keypress, so a stale deferral can no longer swallow unrelated input. A mangled
release pairs its kitty release from the deferred physical key instead of the
Process/229 event.
'sftp.fileName' read 檔名稱 and its placeholder 輸入檔名稱, which are both
missing a character — the term is 檔案名稱. Every other locale spells it
out (File name / 文件名称 / Имя файла / Nombre del archivo), and the
sibling folder keys directly above already use 資料夾名稱.
Visible in the SFTP "New file" dialog.
Two changes based on Codex bot review feedback on PR #3156:
1. Preserve ZWNJ (U+200C) and ZWJ (U+200D) in terminal input.
These carry meaning in Persian orthography and emoji sequences;
PTYs can process the raw bytes, so stripping them could corrupt
filenames, args, and passwords that legitimately contain them.
2. Sanitize composition text before Kitty encoding.
When Kitty report-all + associated-text mode is negotiated,
encodeKittyCompositionText converts U+200B into an escape sequence
(e.g. ESC[0;;8203u) BEFORE reaching the handleTerminalInputData
guard. Sanitize the raw text in both composition paths
(commitImeTextInput remap path and onData handler) so zero-width
artifacts are stripped before encoding, not after.
Update pattern-matching tests to reflect the new variable names
(sanitizedText / sanitizedData) in the Kitty composition paths.
CJK IMEs (notably Microsoft Pinyin / Sogou on Windows) occasionally emit
zero-width Unicode characters (U+200B ZWSP, U+FEFF BOM, etc.) when switching
composition modes. xterm.js sends these to the PTY via onData, and with the
15-graphemes Unicode version they render at width 0 — so the command line
looks normal but contains hidden characters that cause the executed command
to fail.
Add sanitizeTerminalInput() and call it at the top of
handleTerminalInputData so every input path (keyboard, IME composition,
paste, Kitty keyboard, Shift+Enter) strips zero-width formatting chars
before they reach the PTY. If the input was zero-width-only, return early
to skip all downstream processing (command buffer, broadcast, sudo autofill).
The folder conflict copy was clarified but the transfer-center accessibility test still expected the previous wording. Assert the current destructive warning while preserving the aria-describedby coverage.
The identity-file safety check correctly rejects the old placeholder strings used by four auth retry tests. Generate a real test key and return a realistic parsed private key so the fixtures exercise the intended retry behavior without weakening validation.
The repository ships AGENTS.md, but every README linked to lowercase
agents.md. GitHub serves blob paths case-sensitively, so the architecture
guide link 404s on all four pages.
Fixed in README.md, README.zh-CN.md and README.ja-JP.md as well, since the
same broken link exists there.
Add README.zh-TW.md for Traditional Chinese (Taiwan) readers and link it
from the language switcher in every README.
The localized download badges were also broken. README.zh-CN.md and
README.ja-JP.md put raw non-ASCII text straight into the shields.io query
string (label=下载最新版, label=最新版をダウンロード). shields.io rejects
those with HTTP 400, so both rendered as a broken image. Percent-encoding
the label fixes it; all four badges now return HTTP 200.
- add README.zh-TW.md, written for Taiwan usage rather than converted from
Simplified Chinese (伺服器, 用戶端, 分頁, 工作階段, 檔案總管, 程式碼簽章,
相依套件, 叢集, 權杖, 序列埠)
- percent-encode the badge label in README.zh-CN.md and README.ja-JP.md
- add 繁體中文 to the language switcher in all four READMEs
- allowlist README.zh-TW.md in .gitignore, which ignores root files by
default
Validate ssh2 parser results for actual private material and stop raw key fallback after resolution failures. Add generated-key regression coverage for public-only and malformed identity inputs while preserving encrypted-key handling.
Dragging a host card in grid view left it stuck at opacity-45 until the
user started another drag on it.
The grid preview reorder runs from onDragOverCapture, so the dragged card
can move into a different virtual row while the drag is still active. In
VirtualizedHostCollection each row is a separate React parent, so crossing
a row boundary unmounts and remounts the card. The browser still dispatches
dragend at the original node, but a detached node no longer bubbles the
event up to the container's onDragEndCapture, so draggingHostId was never
cleared. A short drag that stayed inside one row kept the node mounted,
which is why dragging again appeared to fix it.
- bind a native dragend listener on the source node at dragstart; it is
dispatched at that node and still fires after React unmounts it
- always reset drag state in onDropCapture: after a grid preview reorder
the card under the cursor is the dragged card itself, so
draggedHostId === targetHostId fell through every branch without
resetting
- apply the same fix in useVaultItemReorder, which backs the keychain,
port forwarding, proxy and known-hosts lists, and in SnippetsManager
zh-TW was missing 78 keys. I18nProvider resolves a missing zh-TW key
through the base "zh" language, and that lookup matches zh-CN first in
MESSAGES_BY_LOCALE, so Traditional Chinese users saw Simplified Chinese
for the whole App Lock surface rather than an English fallback.
- add the 78 missing keys: appLock.*, settings.appLock.*, topTabs.lockApp,
dialog.createWorkspace.title, field.selectHosts, placeholder.searchHosts
- fix Simplified leftovers: 云端 -> 雲端, 筆記本里 -> 筆記本裡, 賬密 -> 帳密
- switch mainland wording to Taiwan usage: 分組 -> 群組, 標籤頁 -> 分頁,
身份 -> 身分, 新建 -> 新增, 地址 -> 位址, 重置 -> 重設, 快捷鍵 -> 快速鍵,
生成 -> 產生, 審批/批准 -> 核准, 波特率 -> 鮑率, 停止位 -> 停止位元,
拖拽 -> 拖曳, 響應 -> 回應, 字號 -> 字型大小, 重啟 -> 重新啟動
zh-TW now covers every key present in en and zh-CN.
Avoid rewriting thinking/provider maps when values are unchanged so
mounted AI panels do not re-render. Put permission title and short
description on one line and drop the Cursor CLI parenthetical.
Stop advertising Gemini 3.7 Flash minimal, GPT-5.x-chat reasoning, and
Claude 4.7+ extended thinking. Map GPT-5 Off to minimal, send adaptive
effort on Claude 4.6+, and keep composer Enter on an exact or custom id.
Node's http.request falls back to Transfer-Encoding: chunked when no
Content-Length header is present. Both AI request paths pass the AI SDK's
headers through unchanged, and those headers carry no content-length (in the
browser fetch computes it), so every AI request body went out chunked.
The body is a fully-buffered string at that point, so its length is known.
Send an explicit Content-Length instead, measured with Buffer.byteLength so
multi-byte system prompts are not truncated.
Chunked request bodies are the less interoperable choice: on the plain-HTTP
path an HTTP proxy has to de-frame and re-serialize them, and some fail on
larger bodies. Xray's HTTP inbound drops the connection once the chunked body
exceeds roughly 8KB. Because Catty's system prompt plus tool definitions is
about 48KB, chat against an http:// provider behind a system HTTP proxy failed
with an opaque "socket hang up" while the connection test kept passing, since
that test issues GET /v1/models with no body and never exercises this path.
Adds three regression tests covering the streaming path, byte-accurate length
for multi-byte bodies, and the non-streaming path.
Closes#3083
Codex P2: switching to a catalog model dropped context_length, so
Catty budgeted 128k instead of the advertised window. Keep the
metadata on picker models and write it to modelContextWindows.
Codex P2: models whose menu includes Off still showed Off for users
with no stored preference, while the request sent no override. Leave
the chip unselected until the user picks a level.
Codex P2: missing agentThinkingMap was treated as Off, so GPT-5.1+
got reasoningEffort=none and Gemini 2.5 Flash got thinkingBudget=0
without a user choice. Store Off as an explicit value and leave an
absent preference unset so the provider default is used.
Codex P2: an empty agentThinkingMap was normalized to Off and then to
minimal, so every Gemini 3 Flash turn overrode the provider default.
Only send thinkingLevel when the user actually picked a level.
Codex P2: gpt-5-chat-latest matched the gpt-5 heuristic and received
reasoningEffort, which the OpenAI chat-only variants reject. Exclude
gpt-5-chat* from the thinking chip and request options.
Codex P2: agentThinkingMap was persisted locally but omitted from
collect/apply sync, so backups and other devices restored provider
and model without the chosen reasoning level.
Codex P2: hiding the custom-model row still left Enter falling back
to the typed query, which send-time validation then discarded. Only
use the query as a custom ID when Catty offers that action.
Codex P2: opening the composer on Gemini Pro persisted Low for users
who had never chosen a thinking level. Only remap an explicit stale
value; leave an empty preference unset so the request uses the
provider default.
Codex P2: pinning or recents in one parked ChatInput only updated that
instance. Subscribe every mounted composer so sibling terminals see
the same recent/pinned list without remounting.
Codex P2: gemini-3.1-pro-preview supports Medium, but Catty rewrote it
to High. Original Gemini 3 Pro still only has Low/High, so omit Medium
instead of silently converting it.
Codex P2: refreshed CLI catalogs still showed saved IDs that the
catalog no longer contains, then send-time validation swapped them.
Only Catty may keep a custom ID that is not in the live list.
Codex P2: a built-in provider with no stored baseURL never listed
live models even though chat still used the preset host. Fall back to
PROVIDER_PRESETS.defaultBaseURL before probing /models.
Codex P2: external-agent pickers advertised a custom ID that send-time
validation ignored. Offer Use custom model only for Catty providers.
Codex P2: the thinking budget was added to both compaction max output
and reservedTokens, so it was subtracted twice. Keep it only on
compaction max output.
Codex P2: medium/high thinking adds 10k/20k to the SDK max_tokens,
but pre-turn/step compaction still reserved only the 4,096 default.
Fold the selected thinking budget into output reserve and compaction
max output without changing the streamed maxTokens field.
Codex P2: Claude 3 Haiku still offered Low/Medium/High and would send
an enabled thinking budget. Gate the chip and the provider option on
Claude 3.7 / 4+ model ids. Also memoize Catty thinking levels.
Codex P2: switching away from Gemini 3 Flash left `minimal` selected
on models that do not advertise it. Persist a valid level for the new
model.
Codex P2: Gemini 3 Pro and 2.5 Pro cannot disable thinking, so Off
was a lie. Those menus only offer low/medium/high.
Codex P2: catalog cache ignored API key rotation, so a replaced
credential could keep the previous account's models. Fingerprint the
stored secret in the cache key.
Codex P2: Gemini 3 Flash cannot turn thinking off; Off was sending
thinkingLevel=minimal. Surface Minimal on the chip instead of Off.
Codex P1: o3/o4-mini/gpt-5 reject reasoning_effort=none. Only GPT-5.1+
get that Off mapping; older reasoning models omit the field.
Codex P2: Cursor ModelListItem.parameters.effort.values is the catalog
capability list. Use it when present instead of fabricating fallbacks
or dropping the thinking chip on unknown models.
Codex P2: OpenAI-style models such as gpt-4o rejected reasoning_effort
when the chip always offered Off/Low/Medium/High. Gate the chip and
the provider option on known reasoning model ids, and send explicit
none when Off is selected on a reasoning model.
Remove the provider divider and search-field outline in the sidebar
model picker. Size Save/Test/Cancel to the same h-8 box without
changing the global Button styles.
The Ollama preset always rewrote OpenAI-compat keys to the literal
"ollama" dummy. That is correct for local servers with no key, but it
replaced the IPC placeholder used for Cloud, so chat sent
Authorization: Bearer ollama and 401ed. Only fill the dummy when no key
is configured, and normalize a bare ollama.com origin to /v1.
Omit Gemini 3 Pro Off instead of sending unsupported minimal, clamp
medium to high, and merge advertised Cursor efforts with the fallback
table so CLI-listed gpt-5 still gets a thinking chip.
Do not rewrite bare CodeBuddy model ids to /medium, fail closed when
Google has no model id, and send Gemini 2.5 thinkingBudget instead of
Gemini 3 thinkingLevel.
Map Cursor effort variants and CodeBuddy SDK effort onto the shared
sidebar thinking chip, and send Gemini thinkingConfig for Catty
models that look like they support it.
Give Catty a two-column provider/model menu in the composer, add search
plus recent/pinned models, and move thinking effort onto its own chip
so Claude, Codex, Grok, and Catty share the same compact controls.
Closes#2137Closes#2637Closes#1884
Closes#3078
OpenAI-compatible providers always used Chat Completions. Some
relays cache poorly on that path. Add a per-provider Responses
opt-in so Catty can call /v1/responses while keeping Chat
Completions as the default.
Surface the remote file owner in the SFTP list and tree, using the
existing toggleable column menu. Parse the username from SFTP longname
or ls output, then fall back to uid when a name is unavailable.
Closes#3073
Malformed work factors must fail with the limits error even when
crypto.argon2Sync is missing, so CI on Node 22 still rejects crafted
headers instead of reporting an unsupported runtime.
Check encryption on the prepared key so SFTP/jump hops do not prompt
again after a PPK is rewritten to OpenSSH. Build Argon2-limit tests
from stub headers so they do not need crypto.argon2Sync. Keep the
empty HMAC key for unencrypted PPK v3, matching PuTTYgen.
The overlay window already uses native hasShadow. CSS shadow-lg on the
transparent rounded card paints a second halo, which macOS then
composites as an extra outline under the panel.
Reject non-positive or unbounded memory/passes/parallelism (and
malformed salt) before argon2Sync so a crafted PPK cannot stall the
Electron main process.
Closes#3072
Close-to-tray and app-hide always requested a background lock whenever
app lock was enabled. "Never lock automatically" only disabled the idle
timer, so clicking X on Windows still asked for the password on reopen.
Honor timeoutMinutes === 0 for background locks. Startup and manual
locks still apply. Setting copy now says Never skips both.
Codex P2: a keyboard or accessibility activation can fire with valid
event bounds while the pointer sits on another monitor. Keep those
bounds for display and placement; only reject stale tray.getBounds()
when it disagrees with the cursor.
Closes#3069
App Lock lives on the System tab but was never added to the
settings search catalog, so queries such as "应用锁" and
"App Lock" returned no matches. Index the section and wire a
jump anchor so search can open it.
ssh2 only parses PPK v2 RSA/DSA, so an encrypted PuTTYgen ed25519
key failed even with the correct passphrase. Convert PPK v2/v3
(Ed25519, ECDSA, RSA) to OpenSSH PEM before handing the key to ssh2.
Windows right-click ignored the event bounds Electron already provides
and reused tray.getBounds() / win.getBounds(), which often report y=0
or a blown-up size. Always set 360x520 and anchor above a bottom
taskbar (or below a top one), falling back to the cursor when bounds
are stale.
Closes#3064
Windows shows the system title-bar menu on right-click in drag regions.
The lock overlay is a drag region so the window stays movable, which also
exposed Minimize/Maximize/Close. Suppress BrowserWindow system-context-menu
while locked so the lock screen cannot quit the app.
Closes#3065
The lock screen is a full-window overlay, so it covers the title-bar
drag region and is the only chrome on a locked first paint. Mark the
backdrop as app-drag and keep unlock controls app-no-drag.
Closes#3058
Reconcile the app-lock feature with main's refactored renderer
architecture (publishers/hosts/AppShell) and main-process changes:
- index.tsx/App.tsx: AppLockGate keeps ownership of useSettingsState and
useAppLockState; new AppLockRuntimePublisher publishes the runtime via
appRuntimeBridge; SettingsPublisher now binds the gate-owned settings.
- AppSideEffects: defer deep links, tray port-forward toggles, global
hotkeys and window close commands while locked; flush on unlock.
- main.cjs: keep fresh-session re-lock before main's reusable-window
fast path; before-quit guard delegates to handleBeforeQuit with main's
async plugin-shutdown commitQuit and pending-update cancellation.
- globalShortcutBridge: keep locked-state tray redaction on top of
main's deferred tray panel show logic.
- preload: adopt main's lastPayload replay for terminal popup config
(superset of the gate-delay replay fix).
- vite config: keep dev CSP plugin, adopt main's warn-only stale
optimize-dep handling; electron-builder: keep Windows Hello helper
extraResource with main's CLI-controlled arch targets.
Co-authored-by: Cursor <cursoragent@cursor.com>
The HTTP stream layer used a hard 120s total deadline, so long
reasoning that was still emitting tokens got cut off. Re-arm a
2-minute idle timer on incoming bytes and keep a 30-minute hard cap
for runaway streams.
Closes#3051
MCP allows clients to omit tools/call arguments or send {}. Empty Zod
object schemas rejected the omitted case, so external agents could not
call get_environment and other no-arg tools.
Closes#3049
closeSessionWorkspaceLayoutState pruned the workspace tree but left
focusedSessionId pointing at the closed session. In a focus-mode workspace no
pane then qualified as visible, so the continuously rendered hidden panes all
painted on top of each other (reported in #3046 follow-up). Reassign focus to
a remaining session and drop closed ids from focusSessionOrder, matching the
detach path.
Co-authored-by: Cursor <cursoragent@cursor.com>
A pane that was once full-size kept those pixels in lastVisiblePaneSizeRef
through split view; after a viewport resize, switching back to focus pinned
the hidden pane (and its PTY) at the stale size. Split-visible panes now
invalidate the cache so hidden full-size layouts re-measure the current area.
Co-authored-by: Cursor <cursoragent@cursor.com>
Switching a workspace from split view to focus view left each newly hidden
pane pinned at the split fragment size cached in lastVisiblePaneSizeRef, so
the background PTY stayed narrow and the \r progress spam from #3046 could
still occur. Only full-size layouts feed the hidden-pane pinned size now.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(terminal): pass Esc+. through for readline yank-last-arg
Autocomplete was swallowing Escape, and macOS Option+. typed ≥ instead of Meta-., so the standard shell shortcut never reached bash/zsh.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): allow Shift when Esc+_ yanks the last argument
Underscore is Shift+Minus, so rejecting all shiftKey follow-ups dropped M-_ after autocomplete dismissed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): keep Esc+_ armed across the Shift keydown
Typing underscore emits Shift before Minus. Clearing the Meta prefix on that modifier event dropped M-_ after autocomplete dismiss.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): do not treat Esc+. as typed input
Yank-last-arg rewrites the shell line. Walking the ESC+. bytes appended a literal period to the command buffer and left autocomplete's typed buffer marked reliable.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(ssh): accept PuTTY-style CLI args from bastion hosts
Reuse the existing ssh:// and telnet:// deep-link path so PAM products that launch putty.exe -ssh user@host -P port -pw password can point at Netcatty instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ssh): consume operands of ignored PuTTY flags
Unknown value-taking options such as -m and -D were skipped while their next token was treated as a host or port, which could connect the supplied password to the wrong endpoint.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ssh): reject unsupported PuTTY protocols and unknown flags
-raw/-rlogin/-serial must not fall through to SSH with the supplied password, and valueless switches such as -batch must not consume the following host. Unknown dash flags are now rejected instead of guessed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ssh): reject missing or empty PuTTY option operands
An empty or trailing -pw was turned into ssh://user:@host, which the deep-link path treats as no password and can then authenticate with a saved vault credential.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ssh): reject unsupported -pwfile, -i, and -hostkey launches
Those options request credentials or a host-key pin this pipeline cannot honor, so connecting with -pw or a vault match would silently change the authentication the caller asked for.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ssh): keep .app/.exe hosts and reject -loghost
Suffix filtering now only covers the Electron script argument, so destinations like prod.app are not dropped. -loghost is rejected because this pipeline cannot honor the logical host-key identity.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ssh): keep packaged argv[1] hosts like prod.js
Suffix filtering now applies only after an Electron binary, so a packaged launch can still use a first-position hostname that looks like a script.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Focus-mode panes are always viewed full-size, but hidden non-focused panes
were laid out at their split rects when hibernate is off (the default). The
continuously rendered xterm and the remote PTY shrank to a 1/N-width
fragment, so \r-refresh progress output (rsync --info=progress2) soft-wrapped
and left one scrollback line per refresh, flooding the terminal when the
host was focused again (#3046). Focus mode now never uses split geometry.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(sftp): add right-click extract for archives
Let SFTP list and tree views unpack zip/tar/gz archives in place via remote SSH exec (or local tar/unzip), so users do not need to switch to another file manager.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): keep extract from clobbering files or dying on unzip output
Write gzip/bzip2/xz results to a staging path and replace the destination only after a zero exit. Drain unzip progress instead of treating routine stdout as a hard failure.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): quote extract cleanup trap and use unique staging files
Remote gzip/bzip2/xz nested a quoted path inside trap, so names with spaces failed before decode. Staging now exclusively creates a unique sibling, and local .gz uses Node zlib.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): refuse single-file extract onto an existing directory
Remote mv would otherwise drop the staging file inside that directory and still report success.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): restore the previous file if Windows extract replace fails
Rename the destination aside before publishing the staging file, instead of deleting it first.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Decrypt master-password and legacy SessionP credentials during MobaXterm
import so migrating hosts do not require re-entering passwords.
Co-authored-by: Cursor <cursoragent@cursor.com>
The backtracking regex cannot enumerate every control that homes the
cursor (DECOM, DECCOLM, ...). Snapshot the skipped start row's text before
a newline-prefixed write; if it changed, something climbed back onto that
row and it is restored and repainted with the rest of the range. This
closes the whole class instead of chasing individual sequences.
Co-authored-by: Cursor <cursoragent@cursor.com>
Setting a scroll region (`CSI Ps;Ps r`) homes the cursor, so a
newline-prefixed chunk using DECSTBM can land back on the highlighted
starting row like CUU/VPA. Keep the pre-write restore and repaint the
start row for such chunks.
Co-authored-by: Cursor <cursoragent@cursor.com>
`CSI Ps d` positions the cursor on an absolute row and can jump back onto
the highlighted starting row just like CUU/CUP, so newline-prefixed chunks
using VPA must also keep the pre-write restore and repaint the start row.
Co-authored-by: Cursor <cursoragent@cursor.com>
A chunk like `\r\n\x1b[1A\rOK\x1b[1B` (multi-line progress redraws) starts
with a line advance but returns to the previous row and partially
overwrites a highlighted keyword. The line-advance exemption skipped the
pre-write restore while the post-write repaint also skipped that row,
leaving the untouched cells with the injected color.
Keep the restore and include the start row in the repaint whenever the
chunk contains sequences that can move the cursor back up (CUU/CPL/CUP,
restore cursor, RI, DECRC).
Co-authored-by: Cursor <cursoragent@cursor.com>
Codex review: putting the turn prompt on a cmd.exe line hits the
8191-char limit, mishandles quotes/%VAR%, and leaves the Node child
alive after Stop. Resolve cursor-agent.cmd to versions/*/node.exe plus
index.js and spawn that argv with shell:false.
Closes review on #3039
A write chunk that starts with a line advance (bash 5.1+ fuses the echoed
newline with the bracketed-paste `\x1b[?2004l\r`, command output, and the
next prompt) restored the previous prompt row's highlight before writing,
while the post-write repaint deliberately skips that row. The keyword
highlight on the command line vanished until a background catch-up ran.
Only treat a chunk as an in-place rewrite of the cursor row when it does
not begin with a line advance: once the cursor leaves the row, later CR/EL
sequences can only touch rows the post-write repaint already covers.
Fixes#3000
Co-authored-by: Cursor <cursoragent@cursor.com>
Address the second Codex pass on #3037. A non-ST ESC now ends an
in-flight OSC 9/777/99 the same way xterm does, so hibernated
replay keeps the following CSI/text. OSC 99 fragments are also
concatenated raw so kitty can split on whitespace.
Settings → AI → Cursor Check ran `cursor-agent status` via execFileSync
on the path from `where`, which is a .cmd installer shim. Node cannot
spawn .cmd files directly (EINVAL), so the probe reported logged-out
even when the terminal showed authenticated.
Run the status probe, turns, and model listing through the existing
Windows shim spawn helper, without unwrapping cursor-agent.cmd to
node.exe (that would drop index.js). Also accept status JSON wrapped
in cmd noise.
Closes#3003
User-invoked Select All / Select Word now pulse the gesture so they
still copy. touchcancel releases the same way as touchend so an
interrupted touch cannot leave SearchAddon able to overwrite the
clipboard.
Address Codex review on #3037. Disabled mode no longer marks tab
activity. Hidden hibernated sessions now scan incoming chunks for
OSC 9/777/99 so Codex can still raise a toast before the tab wakes.
A post-mouseup contextmenu was re-arming the gesture with no later
pointer-up, so SearchAddon could keep copying. Pulse contextmenu and
observe it in document capture so tmux/vim select-word still counts.
Copy-on-select listened to every xterm selection change. SearchAddon
selects the active match on find and again 200ms after resize/write, so
searching for a term then selecting other text left the search term on
the clipboard (issue #3007).
Only write the clipboard after a user pointer gesture on the terminal.
Closes#3019
Codex and other agents emit OSC 9 when a turn completes or approval
is needed, but Netcatty ignored those sequences. Register OSC 9, 777
notify, and kitty OSC 99 handlers, map them to Electron notifications,
and reuse tray focus so a click jumps back to the emitting session.
Ignore ConEmu OSC 9;n progress sequences so they do not become toasts.
Closes#3023
A long line that wraps exactly on a space loses that space on copy
(hbase shell becomes hbaseshell). xterm.js 6 may serve a no-endColumn
translateToString(true) from its string cache via String.trimEnd(),
which cannot tell a real last-column space from empty-cell padding.
Prompt detection and similar callers populate that cache first, so
both the normalizer and raw getSelection drop the space.
Always pass an exclusive end column so the copy path uses
getTrimmedLength() instead of trimEnd().
#3026
An empty Ctrl+C copy binding kept the overlay up and still sent SIGINT.
The same happened for an unbound Ctrl/Cmd+A and for context-menu paste.
Keep the preview only when copy/select-all actually consume the chord,
and hide it before ordinary paste.
#3026
Dragging on the overlay can blur xterm, so native copy never hit the
container listener and the raw wrapped rows went to the clipboard.
Listen on document while the preview is open. Also keep a selected
hard newline at a row boundary instead of dropping it.
#3026
Select All built an element-boundary range, so wrap joining never ran
and copied extra newlines. Context-menu paste selection also left the
overlay up, unlike the keyboard path. Select the text node, accept
element-boundary offsets, and dismiss the preview before pasting.
#3026
The overlay had to keep pointer events so history could be copied, but
then every later click stayed on the preview. A click without a drag
now closes it and restores terminal focus. Copy also joins overlay
rows that xterm marked as soft-wrapped, so a long command does not
pick up extra newlines.
#3026
Urgent interrupt still asked only xterm for a selection. With history
preview text selected, Ctrl+C therefore sent SIGINT into vim before the
copy path ran. Use the same copyable-selection flag for interrupt,
shortcut passthrough, and Kitty broadcast.
#3026
Shift+scroll already overlays the normal-buffer history on the
alternate screen, but the overlay ignored pointer events and any
mousedown or copy chord dismissed it. Selection now stays on the
preview so the usual copy shortcuts, context menu, and copy-on-select
can take that text without leaving vi.
#2923
Copy Tab pins the source transport across preflight. If that shell
closes before the queued channel opens, pendingShellReconnectRisk is
already recorded and TERM-SSHD marks the conn non-reusable for shells.
Decide liveness and allowShellReuse at channel-open time so a pinned
captured source cannot accept an immediately exiting session.
#2923
Idle park is not the only last-shell state. An SFTP or forward lease
keeps the transport live and records pendingShellReconnectRisk; those
reconnects now run the same liveness settle. Known-bad banners also
refuse later shell reuse on that socket so start() dials fresh instead
of borrowing the dead session again.
Keep the liveness timer referenced so node --test cannot exit while
the settle is still pending.
#2923
QiZhi TERM-SSHD accepts a new session channel on a parked connection
and immediately exits 0, so reconnect after close flashes the tab
shut. 1.1.80 skipped PID discovery on that path but still reused the
parked transport.
Known-bad banners now end the transport instead of parking. Idle-park
reconnects on unknown banners wait for the new shell to stay open; an
immediate exit discards the transport, denylists the endpoint, and
falls back to a fresh dial. Copy Tab and live multiplex are unchanged.
When the keyboard shortcut scheme is off, snippet recording still
treated default Mac and PC bindings as occupied. Conflict checks now
use only bindings that can actually fire, and the error names the
conflicting action.
Closes#3025
Older AxonHub 0.9 and some intranet OpenAI-compat proxies emit
`data:{json}` instead of `data: {json}`. The stream parser required
the space, so chat chunks were dropped while the model-list probe
still succeeded.
Parse `data:` per the EventSource spec (optional single space) and
cover both the line and end-of-stream flush paths.
Closes#3020
- Add complete es message dictionaries (core, vault, terminal, ai,
systemManager, scripts) covering all en keys with matching placeholders
- Register 'es' in MESSAGES_BY_LOCALE and SUPPORTED_UI_LOCALES
- Extend locale test coverage to include es
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.
Put requestDisable/requestReset on the settings mutation queue so concurrent
timeout changes cannot re-enable App Lock after a successful disable. Capture
the runtime lock version before system auth and reject the prompt if the lock
epoch advanced (idle→background re-lock) while the dialog was open.
Password-change saves go through the settings mutation queue. System unlock
re-checks locked state after the OS prompt so a re-lock during the dialog
cannot unlock from a stale auth success.
Queue app-lock save mutations and use unique temp paths so concurrent
writes cannot ENOENT or clobber. Update the system-unlock toggle test to
match the already-enabled-while-unavailable disable path.
Write app-lock settings via temp file + rename to avoid truncating the
verifier on crash. Gate application-menu Close and window:close IPC while
locked so popups are not destroyed behind the overlay.
isVisible is false for minimized BrowserWindows, which made Dock reopen
re-lock while a minimized main/session window was still open. Treat
isMinimized() as a live session, while still ignoring hidden prewarm
windows.
Keep forceRenderChildren so popup config IPC still registers while locked,
but do not mark the popup ready (or mount Terminal/startup commands) until
allowTerminalStart is true after unlock.
The overlay helper import was accidentally inserted inside open named
import lists, which broke TypeScript parsing. Move it to a top-level
import statement in SnippetsManager and SFTP keyboard shortcuts.
Optimistic unlock used current.version+1, which can outrank a concurrent
main-process re-lock and make refreshRuntimeState ignore the authoritative
locked state. Keep the observed version so real main updates always win.
Raise the lock screen to max z-index and hide the context-menu portal while
locked. Centralize isAppLockOverlayActive and gate ChatInput slash picker,
SFTP shortcuts, theme modal, and shortkey/hotkey recorders so earlier
window-capture listeners cannot steal unlock keystrokes.
Settings prewarm stays as a hidden BrowserWindow and was blocking
ensureAppLockForFreshSession after the main window closed. Only visible
windows count as a live session for that emptiness check.
Earlier-registered capture handlers (shortkey/hotkey recorders, snippet
save) run before the overlay's stopImmediatePropagation. Skip those
handlers when [data-app-lock-overlay] is present so password keystrokes
cannot mutate settings behind the lock screen.
Fresh-session re-lock now considers settings/tray/popup windows via
getAppLockReopenWindows so an open Settings UI is not interrupted.
System unlock toggle stays clickable when already enabled even if the
authenticator is currently unavailable, so users can turn it off.
While locked, use window capture with stopImmediatePropagation so
shortkey/hotkey recorders never see lock-screen keys, and stop
document bubble so window-level shortcuts (e.g. Snippets save) cannot
run even when focus is on body.
inert does not block keydown bubbling to window listeners. While locked,
stop propagation for keys targeted at the overlay so panel shortcuts
(e.g. Snippets Ctrl/Cmd+S) cannot act behind the lock screen.
Mounted shortcut hooks (e.g. SFTP) suppress when a dialog has
data-state=open. Without that marker, keyboard actions could still run
behind the app lock overlay when focus is not on the password field.
System Manager terminal popups receive config immediately after loadURL.
AppLockGate used to withhold children until async lock state resolved,
so onTerminalPopupConfig could miss the one-shot IPC. Force-render the
popup route and keep the last preload config for late remounts.
Detached #/session-window windows register with registerAsMainWindow:false
and were omitted from app-lock runtime/settings broadcasts, so they stayed
interactive after a lock. Include getAppContentWindows() (deduped) in the
broadcast fan-out and cover session windows in the harness.
On darwin, closing every window leaves the process alive while app-lock
runtime stays unlocked. Re-apply startup lock when the last window closes
and before creating a fresh main window with no app-content windows.
Mark mounted app children inert/aria-hidden/pointer-events-none while locked,
trap Tab inside the lock dialog, and suppress global hotkeys. Queue tray
port-forward toggles in main (and renderer) until the runtime unlocks.
Dirty-quit focus restore already verified.
When quit is cancelled because a renderer reports unsaved editors,
show and focus those windows first. A tray-hidden or unfocused main
window otherwise only posts a toast the user never sees, so quit
appears to fail silently.
Defer first App mount (and rendererReady) for any lock reason so
background/idle locks cannot deliver ssh:// connects behind the overlay.
Wait indefinitely for renderer readiness and re-queue failed deliveries so
startup unlock after the old 15s timeout no longer drops deep links. Queue
renderer-side deep links while already-mounted sessions re-lock.
Netcatty **v1.1.84** introduces custom host monitoring dashboards and an integrated host browser, expands AI conversation and model controls, and improves cloud sync, SSH authentication, and everyday terminal workflows.
### 📊 Custom host monitoring dashboards
- Create named **monitoring panels** in the Vault, choose their hosts, and switch between chart and list views. Panel configuration syncs across devices (#3703).
- Inspect CPU, memory, disk, network, and latency readings, with GPU details where available. Choose visible columns, resize them, adjust refresh controls, and open a terminal directly from a monitored host (#3704, #3705, #3713).
- Virtualized host collections keep large monitoring views responsive (#3713).
### 🌐 Browse host services inside Netcatty
- Open the **Browser** tool alongside SFTP and Notes in a terminal side panel. SSH browsing uses the selected host's network path, including services on that host's `localhost` (#3701).
- Work with browser tabs, bookmarks, navigation controls, and downloads without leaving the terminal workspace. Terminal links can open internally or in your system browser, according to your preference (#3701, #3712).
- Browser contexts stay with their terminal, and idle panes avoid unnecessary layout polling (#3701, #3723).
### 🤖 More control over AI conversations
- **Search chat history**, fork a conversation from a completed response, or undo the last turn by branching at a safe boundary while preserving the original conversation (#3601, #3602, #3604).
- Use the new **MiMo Code** agent integration. SDK model pickers gain clearer loading, source and retry states, better live model discovery, and support for manual model IDs (#3500, #3535, #3624, #3642).
- Dock the AI assistant at the bottom, scope external MCP approvals to a connection or host, and let Skills + CLI work with Vault Notes (#3482, #3569, #3512).
- Long-running AI sessions now default to an **8-hour idle timeout**. Catty prompt-cache handling and cached-token usage reporting are improved (#3618, #3714).
### ☁️ Cloud sync and Vault organization
- Choose a **non-destructive merge** strategy that retains entries missing from only one side, and include AI conversation history in cloud sync (#3633).
- Clear local backup history with confirmation, with clearer disconnect and retry behavior and a refreshed convergent-sync settings panel (#3708, #3728).
- Import FinalShell hosts, add Markdown notes to host groups, move selected hosts into a group, search workspace hosts by tag, and test a connection from the host editor (#3331, #3372, #3675, #3337, #2984).
### 🖥️ Terminal and editing workflows
- Save and automatically apply a **default terminal side-panel layout**, and open files in a detached editor window with multiple tabs (#3456, #3606).
- Confirm multiline pastes, recall compose-bar history, save the current terminal screen, and configure tab double-click behavior and cursor width (#3400, #3365, #3364, #3401, #3495).
- Highlight Unix paths with a switchable rule and prepare `tar` or `zip` commands from archive links. Command history gains a clear-all action (#3672, #3682, #3632).
- Toggle window transparency while preserving the chosen opacity (#2987).
### 🔐 SSH, SFTP, and responsiveness
- **FIDO2 OpenSSH security keys** return with touch/PIN prompts, retry handling, and an explicit saved-password fallback when available. Compatible hardware and OpenSSH support are required (#2823, #3709).
- Enable a single-channel SSH option for bastion hosts. SSH auto-reconnect is enabled by default, while automatic terminal closure on exit is disabled by default (#3458, #3617, #3619).
- SFTP gains a **Cancel all** transfer action and local-shell file browsing, with fixes for independent upload cancellation, interrupted downloads, and transfer setup errors (#3574, #3379, #3638, #3702).
- Large SFTP panes, expanded transfer lists, AI history searches, detached editors, and host searches receive responsiveness improvements. Release packages omit duplicate renderer and unused native files (#3716, #3717, #3719, #3721, #3722, #3724, #3726).
@@ -94,7 +94,7 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
|---------|-------------------|--------|
| Catty (sidebar) tools | `npm run generate:capability-tools` → `infrastructure/ai/harness/generated/cattyToolSpecs.json` | Sidebar agent tool set. CI verifies JSON drift. |
| Global agent tools | same script → `globalAgentToolSpecs.json` | Prepared for future global agent runtime; shared RPC tools only today. |
| MCP stdio | `electron/capabilities/codegen/mcpToolRegistry.cjs` → `electron/mcp/netcatty-mcp-server.cjs` | Registry-driven; external agents. Harness tools are **not** on MCP. |
| MCP stdio | `electron/capabilities/codegen/mcpToolRegistry.cjs` → `electron/mcp/netcatty-mcp-server.cjs` | Registry-driven; external agents. Only `harness.terminal.read_context` is also on MCP. |
@@ -104,12 +104,15 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
**Handles:**`ToolOutputStore` persists across turns per chat session; cleared on chat session delete. Large `sftp.read` results spill to `tool_output_read`.
**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.
**Harness domain (`catalog/harness.cjs`):** Catty executes these locally in `capabilityTools.executeLocalCattyCapability`. The read-only `harness.terminal.read_context` also exposes `netcatty/readContext` to MCP, with scope validation before and after the existing renderer bridge call. It reuses the sidebar reader through the screen snapshot registry (80 rows by default, maximum 300); no script or command runs. Other harness tools remain Catty-only. `harness.web.search` is omitted when web search is not configured.
## Plugin host runtime (internal preview)
## Plugin host runtime
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
The plugin host under `electron/plugins/`exposes browser-only packages in normal
Settings. `NETCATTY_PLUGIN_DEV=1`admits advanced development packages, without
granting their permissions. Admission and default placement share
`domain/pluginRuntimePolicy.cjs`; permission explanations shared with native
prompts live in `domain/pluginPermissionDescriptions.cjs`. Public wire and package
types still come only from `packages/plugin-contract/schema/`; do not add a
Watch Catty Agent orchestrate a Docker Swarm cluster across two servers in one conversation. It handles the init, token exchange, and node joining — you just tell it what you want.
@@ -84,6 +93,7 @@ Watch Catty Agent orchestrate a Docker Swarm cluster across two servers in one c
- [Split Terminals](#split-terminals)
- [Supported Distros](#supported-distros)
- [Getting Started](#getting-started)
- [Open a local terminal from the command line](#open-a-local-terminal-from-the-command-line)
- [Build & Package](#build--package)
- [Tech Stack](#tech-stack)
- [Contributing](#contributing)
@@ -127,7 +137,9 @@ 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
- **Save current screen** — right-click a terminal to save the visible final text, without replaying keystrokes or overwritten output; scroll first to save an earlier screen.
- **Inline images** — render Kitty graphics, SIXEL and iTerm inline images from remote programs
- **Archive shortcuts** — on Linux sessions, Ctrl/Cmd+click a printed `.tar`, `.tar.gz`/`.tgz`, `.tar.bz2`/`.tbz2`, `.tar.xz` or `.zip` name to fill an extraction command with the archive's absolute path into the prompt (kept next to any typed text); press Enter to run it. Names relative to the current directory need directory tracking (OSC 7, see the toolbar's Configure directory tracking); absolute paths always work. Windows, WSL, and macOS sessions are excluded.
### 📁 SFTP + Built-in Editor
- **File workflows** — drag & drop uploads/downloads
@@ -147,9 +159,11 @@ If you regularly work with a fleet of servers, Netcatty is built for speed and f
The main window is designed for long-running SSH workflows: quick access to sessions, navigation, and core tools in one place.
Inspect CPU, memory, disk, network, and latency readings, with GPU details where available. Choose visible columns, resize them, select refresh intervals down to 1 second, and open a terminal directly from a monitored host.
Disk I/O shows aggregate read/write throughput for Linux hardware-backed block devices, without counting partitions or stacked virtual devices twice. Rates use two consecutive samples; `--` means unavailable or warming up, not zero activity. Disk I/O is currently unavailable on macOS; existing macOS monitoring is unchanged. The terminal system overview uses the same shared measurements.
@@ -221,6 +251,48 @@ application and integration are complete.
> **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.
`--open-terminal-path <dir>` opens a local terminal in that folder. `--open-terminal-shell <id-or-path>` selects the shell: a discovered id such as `pwsh`, `cmd`, or `wsl-ubuntu`, or a path to a shell executable. Either flag is enough. A shell alone opens a local tab in the default directory. A path alone uses the configured local shell. If Netcatty is already running, the tab opens in that window.
Windows needs an extra `--` so Electron forwards the flags:
With [Scoop](https://scoop.sh/) installed, you can install Netcatty from the community-maintained [lemon bucket](https://github.com/hoilc/scoop-lemon/blob/master/bucket/netcatty.json), which downloads the installer from Netcatty's official GitHub Releases:
This is a third-party package, not an official Netcatty bucket, and updates may lag behind GitHub Releases. It currently supports x64 only. Settings remain in `%APPDATA%\netcatty` across updates and normal uninstalls; `scoop uninstall netcatty --purge` also deletes those settings. Use Scoop to manage updates for this installation.
### Nix / NixOS
Netcatty provides a flake that wraps the official Linux AppImage release for Nix and NixOS users:
@@ -296,7 +368,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
See [agents.md](agents.md) for architecture overview and coding conventions.
See [AGENTS.md](AGENTS.md) for architecture overview and coding conventions.
// No Electron UI, sockets, real transfers, or preferences. Only the network
// pause completion is held. Actual renderer hook, window close callback, and
// dirty-query helper run together with the actual 5000ms production deadline.
for(constslowPauseof[true,false])test(`${slowPause?'slow pause refuses close at the production deadline':'control: fast pause preserves Cancel'}`,{timeout: 8000},asynct=>{
test(`app Quit with an unfinished transfer: ${accept?"confirm quits":"declining reports \"transfers\" before resuming only transferring and queued tasks"}`,async(t)=>{
'ai.providers.style.help':'Selects which API format requests use. Override when a third-party endpoint speaks a different dialect than its provider type suggests.',
'ai.providers.openaiApi':'OpenAI API format',
'ai.providers.openaiApi.chat':'Chat Completions',
'ai.providers.openaiApi.responses':'Responses',
'ai.providers.openaiApi.help':'Chat Completions works with most OpenAI-compatible endpoints. Responses can raise cache hit rates on relays that support /v1/responses.',
'ai.providers.icon.errorType':'Please choose an image file.',
'ai.providers.headers':'Custom HTTP Headers',
'ai.providers.headers.help':'Optional headers for this provider. Values are hidden and encrypted when saved.',
'ai.providers.headers.name':'Header name',
'ai.providers.headers.value':'Header value',
'ai.providers.headers.add':'Add header',
'ai.providers.headers.remove':'Remove header',
'ai.providers.headers.invalid':'Use valid, unique HTTP headers. Content-Length and Transfer-Encoding are managed automatically.',
'ai.providers.headers.loadError':'Unable to unlock saved headers. Cancel and try again after unlocking your system keychain.',
'ai.providers.headers.saveError':'Unable to save securely. Check that your system keychain is available and try again.',
'ai.providers.apiKey':'API Key',
'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.providers.baseUrl.ollamaHelp':'Local Ollama: http://localhost:11434/v1 (no API key). Ollama Cloud: https://ollama.com/v1 plus your cloud API key.',
'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.codebuddy.persistSession.hint':"Keep CodeBuddy's own session transcript on disk so CodeBuddy can resume it later. Turn off to disable CodeBuddy session storage; Netcatty may still save chat history and external session metadata. File checkpointing is skipped. Requires CodeBuddy CLI 2.125.1 or newer.",
'ai.opencode.v2.startupTimeout':'OpenCode 2 did not become ready in time. Confirm `opencode serve` works in a terminal, then retry.',
'ai.opencode.v2.exited':'OpenCode 2 stopped before the chat could start.',
'ai.opencode.v2.unauthorized':'OpenCode 2 refused the connection. Retry the chat.',
'ai.opencode.v2.unavailable':'OpenCode 2 could not be reached. Check that it is installed and try again.',
'ai.opencode.v2.sessionFailed':'OpenCode 2 could not finish this turn.',
'ai.opencode.v2.empty':'OpenCode 2 returned an empty response. Run `opencode` in a terminal to configure authentication and models.',
'ai.opencode.v2.contextSkipped':'OpenCode 2 could not apply the Netcatty conversation context.',
'ai.opencode.v2.mcpSkipped':'OpenCode 2 could not attach an MCP tool. The chat will continue without it.',
// AI MiMo Code (Xiaomi MiMo Code CLI — OpenCode fork)
'ai.mimo.title':'MiMo Code',
'ai.mimo.description':"Xiaomi's MiMo Code CLI, an OpenCode fork. Install it and configure providers in its own config, then select it as an external coding agent.",
'ai.mimo.detecting':'Detecting...',
'ai.mimo.detected':'Detected',
'ai.mimo.notFound':'Not found',
'ai.mimo.path':'Path:',
'ai.mimo.notFoundHint':'Could not find mimo in PATH. Install it or specify the executable path below.',
'ai.toolAccess.description':'Choose how external agents access Netcatty sessions. MCP exposes the built-in server, while Skills + CLI points agents to the local Netcatty skill and CLI commands.',
'ai.toolAccess.mode.mcp':'MCP',
'ai.toolAccess.mode.skills':'Skills + CLI',
'ai.toolAccess.mcpPrompt.title':'Prompt for your AI client',
'ai.toolAccess.mcpPrompt.description':'Paste this prompt into your AI client (Codex, Claude Code, …) and it will register Netcatty MCP for you.',
'ai.toolAccess.mcpPrompt.enableHint':'Turn on External MCP below to include the launcher path in this prompt.',
'ai.toolAccess.skills.file':'Skill file',
'ai.toolAccess.skills.description':'In Skills + CLI mode, agents are pointed to this local skill file automatically. The Netcatty CLI launcher path is provided to the agent in each session.',
// External MCP (productized catalog MCP for Codex / Claude Code / Cursor)
'ai.externalMcp.title':'External MCP',
'ai.externalMcp.allowCommandOnHost':'Always',
'ai.externalMcp.allowCommandOnHostHint':'Always allow this action on this server.',
'ai.externalMcp.allowAll':'Allow all actions',
'ai.externalMcp.allowSession':'This connection',
'ai.externalMcp.allowSessionHint':'Allow all actions from this client until it disconnects.',
'ai.externalMcp.allowHost':'This server',
'ai.externalMcp.allowHostHint':'Always allow actions on this server.',
'ai.externalMcp.description':'Expose Netcatty as an MCP server for external clients such as Codex, Claude Code, Cursor, and Grok. Uses the same catalog tools as in-app agents (terminal, SFTP, Vault, port forwarding). Keep Netcatty running while clients are connected.',
'ai.externalMcp.sessionsExposed':'Sessions in scope: {count}',
'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.approvalAlert':'Approval alerts',
'ai.externalMcp.approvalAlert.description':'When an external MCP client asks for confirmation, show a system notification. While Netcatty is unfocused, the taskbar or Dock asks for attention. The notification uses the system sound.',
'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.security.description':'Listens on 127.0.0.1 with a rotating token, reuses AI Permission Mode for writes, and removes discovery when disabled. No OAuth — this is a local desktop bridge.',
"ai.privacy.title":"AI privacy",
"ai.privacy.description":"Filters known sensitive text patterns in Netcatty-managed context and tool results for Catty, integrated agents and external MCP clients. Off by default. It cannot detect every secret. Binary tool responses are blocked while enabled.",
"ai.privacy.boundary":"Only Netcatty-managed data and tools are covered. Third-party agents' native tools, direct file access and other MCP servers remain outside this boundary.",
"ai.privacy.enable":"Enable text redaction",
"ai.privacy.privateIps":"Redact private IP addresses",
"ai.privacy.privateIps.description":"Also hide private and local IP addresses in returned text.",
"ai.privacy.cidrs":"Additional IP ranges (CIDR)",
"ai.privacy.cidrs.description":"One IPv4 or IPv6 CIDR per line. Leave empty to use only built-in rules.",
"ai.privacy.patterns.description":"One RE2-compatible pattern per line, without / delimiters or flags. Unsupported or unsafe patterns are rejected.",
"ai.privacy.connectionScope":"Placeholders stay stable within one managed conversation or external MCP connection. Original values are restored only through Netcatty-managed tool calls in that same scope.",
"ai.privacy.reconnect":"Apply changes, then start a new managed conversation and reconnect external MCP clients. Existing conversations and connections must not reuse placeholders.",
"ai.privacy.apply":"Apply",
"ai.privacy.applying":"Applying...",
"ai.privacy.applied":"AI privacy settings applied. Start a new managed conversation and reconnect external MCP clients.",
"ai.privacy.failed":"AI privacy settings were not applied.",
'ai.externalMcp.http.title':'Streamable HTTP',
'ai.externalMcp.http.description':'Serve the same External MCP tools over Streamable HTTP for clients that cannot launch the stdio CLI, such as Codex inside WSL. Off by default.',
'ai.externalMcp.http.enable':'Enable HTTP',
'ai.externalMcp.http.port':'Port',
'ai.externalMcp.http.portHint':'Fixed port, default 47391. Client URLs stay the same across restarts. Allowed range: 1024–65535.',
'ai.externalMcp.http.allowCustomBind':'Listen on another address',
'ai.externalMcp.http.allowCustomBind.description':'By default the endpoint binds to 127.0.0.1. Turn this on for WSL2 NAT and set 0.0.0.0 or the vEthernet IP.',
'ai.externalMcp.http.clientHost.description':'Used in copied configs when the listener is 0.0.0.0 or ::. Enter the IP clients can reach, such as the Windows host IP.',
'ai.externalMcp.http.clientHostRequired':'Enter the IP clients should connect to. 0.0.0.0 is not a reachable address.',
'ai.externalMcp.http.invalidClientHost':'Client address must be a reachable IP, not 0.0.0.0 or ::.',
'ai.externalMcp.http.warning':'Listening beyond 127.0.0.1 exposes External MCP to other machines that can reach this address. Keep the bearer token private and do not publish this port.',
'ai.externalMcp.http.url':'URL',
'ai.externalMcp.http.token':'Bearer token',
'ai.externalMcp.http.regenerate':'Regenerate',
'ai.externalMcp.http.regenerateHint':'Issues a new bearer token. HTTP clients must use the new token. The stdio discovery file is updated; clients that are already connected over stdio stay connected.',
'ai.externalMcp.http.wslHint':'With WSL mirrored networking, localhost works. Otherwise listen on 0.0.0.0 or the vEthernet IP and set Client address to the Windows host IP.',
'ai.externalMcp.permissionMode.hint':'Same setting as Settings → AI → Safety. Auto runs Netcatty write tools without Netcatty approval prompts; Confirm asks each time. External clients (Codex / Claude / Grok) may still show their own tool approval UI.',
'ai.externalMcp.addToClaude':'Add to Claude Code',
'ai.externalMcp.addToGrok':'Add to Grok',
'ai.externalMcp.codexAdded':'Codex MCP entry added. Restart Codex or open a new Codex session.',
'ai.externalMcp.claudeAdded':'Claude Code MCP entry added. Restart Claude Code or open a new Claude Code session.',
'ai.externalMcp.grokAdded':'Grok MCP entry added. Restart Grok or open a new Grok session.',
'ai.externalMcp.codexAdded':'Codex MCP entry and Netcatty skill added. Restart Codex or open a new Codex session.',
'ai.externalMcp.claudeAdded':'Claude Code MCP entry and Netcatty skill added. Restart Claude Code or open a new Claude Code session.',
'ai.externalMcp.grokAdded':'Grok MCP entry and Netcatty skill added. Restart Grok or open a new Grok session.',
'ai.externalMcp.installCodex':'Install Codex separately, then click Refresh.',
'ai.externalMcp.installClaude':'Install Claude Code separately, then click Refresh.',
'ai.externalMcp.installGrok':'Install the Grok CLI separately, then click Refresh.',
'ai.externalMcp.conflict.description':'A netcatty-external entry already exists and points elsewhere. Remove or edit it manually.',
'ai.externalMcp.enableForLauncher':'Enable External MCP to get a usable launcher path.',
'ai.externalMcp.cursor.title':'Cursor / other clients',
'ai.externalMcp.cursor.description':'Merge this into your MCP config (for example ~/.cursor/mcp.json). Do not replace the whole file if you already have other servers.',
'ai.externalMcp.cursor.title':'Cursor',
'ai.externalMcp.cursor.description':'Merge this into your Cursor MCP configuration without replacing existing servers.',
'ai.externalMcp.other.description':'Copy the setup prompt into another terminal agent, or merge the MCP snippet into its client configuration.',
'ai.chat.undoLastTurnNotice':"Original chat kept. Commands and file changes are not undone. Return to the original chat for saved full outputs and existing background tasks.",
'ai.chat.searchSessionsHint':'Searches titles, prioritizing newer messages and entries. For long messages and tool outputs, only a limited portion at the beginning is searched. This is not full-text search.',
'ai.chat.searchingSessions':'Searching...',
'ai.chat.noMatchingSessions':'No matches in titles or recent retained content',
'ai.chat.noSessions':'No previous sessions',
'ai.chat.forkFromHere':'Fork from here',
'ai.chat.forkCreated':'Branched into a new chat — the original stays in history.',
'ai.chat.retryHint':'You can retry by sending your message again.',
'ai.chat.approvalTimeout':'Tool approval timed out after 5 minutes. You can retry by sending your message again.',
'ai.safety.commandTimeout.description':'Maximum seconds a command can run before being terminated through Netcatty execution.',
'ai.safety.commandTimeout.unit':'sec',
'ai.safety.responseIdleTimeout':'Built-in AI Response Wait',
'ai.safety.responseIdleTimeout.description':'Cancel a built-in AI request after this many seconds without a new response. This setting does not control total response time or command execution.',
'ai.safety.responseIdleTimeout.unit':'sec',
'ai.safety.maxIterations':'Max Iterations',
'ai.safety.maxIterations.description':'Maximum number of AI tool-use loops to prevent runaway execution. External agents may have their own internal iteration limits that take precedence.',
'credentials.protectionUnavailable.message':'Saved passwords and keys cannot be auto-decrypted on this device. Re-enter credentials before connecting.',
'settings.system.credentials.unavailableHint':'Credentials encrypted on another user profile or machine cannot be decrypted here. Re-enter and save credentials on this device.',
'settings.system.credentials.portabilityHint':'Cloud Sync is portable because it uses your master key encryption. Local safeStorage encryption is device/user scoped.',
// Settings > System > App Lock
'settings.appLock.title':'App Lock',
'settings.appLock.description':'Require a local lock password when Netcatty opens, after inactivity, or when you lock it manually.',
'settings.appLock.setupTitle':'Set a lock password',
'settings.appLock.setupDescription':'Create the password below. App Lock turns on immediately after it is saved.',
'settings.appLock.manageTitle':'App Lock is ready',
'settings.appLock.enabledStatus':'Enabled. Netcatty locks on startup, after inactivity, and when you lock it manually.',
'settings.appLock.disabledStatus':'Password saved, but App Lock is currently off.',
'settings.appLock.disableTitle':'Turn off App Lock',
'settings.appLock.disableDescription':'Enter the current lock password to stop locking Netcatty. The saved password will be removed; turning App Lock on again requires creating a new one.',
'settings.system.networkProxy.description':'HTTP(S) proxy for cloud sync and AI providers. This is separate from SSH ProxyJump / ProxyCommand profiles.',
'settings.globalHotkey.toggleWindowDesc':'Press a key combination to set a global shortcut for showing/hiding the window.',
'settings.globalHotkey.notSet':'Not set',
'settings.globalHotkey.reset':'Reset to default',
'settings.globalHotkey.closeToTray':'Close to System Tray',
'settings.globalHotkey.closeToTrayDesc':'When enabled, closing the window will minimize to the system tray instead of quitting.',
'settings.globalHotkey.closeToTray':'Keep Running When Window Closes',
'settings.globalHotkey.closeToTrayDesc':'When enabled, closing the window hides it while the app keeps running. Tray icon visibility is set separately.',
'settings.globalHotkey.showTrayIcon':'Show System Tray Icon',
'settings.globalHotkey.showTrayIconDesc':'When disabled, the tray icon is hidden but the app keeps running in the background.',
'settings.globalHotkey.showTrayIconHiddenHint':'Tray icon hidden. Launch the app again or use the global toggle hotkey to bring the window back, then re-enable this setting to restore the icon.',
'settings.globalHotkey.enabled':'Enable Global Hotkey',
'settings.globalHotkey.enabledDesc':'Register system-wide keyboard shortcuts. When disabled, all global hotkeys are unregistered.',
'settings.globalHotkey.hint':'Global hotkey works system-wide to quickly show or hide the window (Quake-style terminal).',
'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.',
'settings.vault.notesFont':'Code font',
'settings.vault.notesFontDesc':'Font family used for code blocks and inline code in notes',
'settings.vault.notesFontSize':'Note font size',
'settings.vault.notesFontSizeDesc':'Font size used for note body text',
'settings.vault.notesCodeFontSize':'Code font size',
'settings.vault.notesCodeFontSizeDesc':'Font size used for code blocks and inline code in notes',
'Add custom CSS to personalize the app appearance. Changes apply immediately.',
'Add custom CSS to personalize the app appearance. Changes apply immediately. Selected top tabs (`.top-tab[data-active="true"]`) read `--top-tab-active-bg`, `--top-tab-active-fg`, and `--top-tab-active-border`. Leave them unset to keep the current look.',
'settings.appearance.customCss.help.body':`Major UI regions expose a \`data-section\` attribute you can target. Changes apply as soon as you edit the CSS.
@@ -345,6 +466,22 @@ Because many styles come from Tailwind utilities, you often need \`!important\`
-\`top-tabs-host-tree-toggle\`
-\`top-tabs-quick-switcher-toggle\`
Selectedtoptabsusetheclass\`top-tab\` and \`data-active="true"\`. Set these variables to recolor the selected tab. Leave them unset to keep the current look. They apply without \`!important\`.
-\`--top-tab-active-bg\` — selected fill (defaults to the current tab background)
-\`--top-tab-active-fg\` — selected label color (defaults to the current tab text)
-\`--top-tab-active-border\` — 1px inset outline (defaults to transparent, so there is no border)
TheVaultstabstaysflatanddoesnotusethefill.
\`\`\`css
.top-tab[data-active="true"]{
--top-tab-active-bg:#2563eb;
--top-tab-active-fg:#ffffff;
--top-tab-active-border:#1d4ed8;
}
\`\`\`
###Vault
-\`vault-view\`
@@ -438,15 +575,17 @@ Highlight the focused split pane:
\`\`\`
`,
'settings.appearance.customCss.placeholder':
'/* Examples — use !important to beat Tailwind utility specificity */\n\n/* Hide the host-list toggle in the top tab bar */\n[data-section="top-tabs-host-tree-toggle"] {\n width: 0 !important;\n opacity: 0 !important;\n pointer-events: none !important;\n}\n\n/* Hide the plus button that opens the quick switcher */\n[data-section="top-tabs-quick-switcher-toggle"] {\n display: none !important;\n}\n\n/* Border around the SFTP / side panel (does not linger after closing) */\n[data-section="terminal-side-panel"] {\n border: 2px solid #00c851 !important;\n border-radius: 6px !important;\n}\n\n/* Change the whole side panel background, not only the top tabs */\n[data-section="terminal-side-panel"],\n[data-section="terminal-side-panel-tabs"],\n[data-section="terminal-side-panel-content"],\n[data-section="terminal-sftp-panel"],\n[data-section="terminal-sftp-pane"],\n[data-section="terminal-sftp-list"],\n[data-section="terminal-sftp-tree"],\n[data-section="terminal-sftp-transfer-queue"] {\n background-color: #1c384a !important;\n}\n\n/* Style selected SFTP file rows */\n[data-section="terminal-sftp-list-row"][data-selected="true"] {\n background-color: #00c851 !important;\n color: #001b10 !important;\n}\n\n/* Thicker split dividers */\n[data-section="terminal-split-resizer-bar"] {\n background-color: hsl(var(--primary)) !important;\n transform: scale(2) !important;\n}\n\n/* Highlight the focused split pane */\n[data-section="terminal-split-pane"][data-focused="true"] {\n outline: 2px solid hsl(var(--primary)) !important;\n outline-offset: -2px;\n}\n\n/* Or use Settings → Terminal → Workspace Focus Indicator → Border on focused pane */',
'/* Examples — use !important to beat Tailwind utility specificity */\n\n/* Selected top tab. Leave these unset to keep the current look. */\n.top-tab[data-active="true"] {\n --top-tab-active-bg: #2563eb;\n --top-tab-active-fg: #ffffff;\n --top-tab-active-border: #1d4ed8;\n}\n\n/* Hide the host-list toggle in the top tab bar */\n[data-section="top-tabs-host-tree-toggle"] {\n width: 0 !important;\n opacity: 0 !important;\n pointer-events: none !important;\n}\n\n/* Hide the plus button that opens the quick switcher */\n[data-section="top-tabs-quick-switcher-toggle"] {\n display: none !important;\n}\n\n/* Border around the SFTP / side panel (does not linger after closing) */\n[data-section="terminal-side-panel"] {\n border: 2px solid #00c851 !important;\n border-radius: 6px !important;\n}\n\n/* Change the whole side panel background, not only the top tabs */\n[data-section="terminal-side-panel"],\n[data-section="terminal-side-panel-tabs"],\n[data-section="terminal-side-panel-content"],\n[data-section="terminal-sftp-panel"],\n[data-section="terminal-sftp-pane"],\n[data-section="terminal-sftp-list"],\n[data-section="terminal-sftp-tree"],\n[data-section="terminal-sftp-transfer-queue"] {\n background-color: #1c384a !important;\n}\n\n/* Style selected SFTP file rows */\n[data-section="terminal-sftp-list-row"][data-selected="true"] {\n background-color: #00c851 !important;\n color: #001b10 !important;\n}\n\n/* Thicker split dividers */\n[data-section="terminal-split-resizer-bar"] {\n background-color: hsl(var(--primary)) !important;\n transform: scale(2) !important;\n}\n\n/* Highlight the focused split pane */\n[data-section="terminal-split-pane"][data-focused="true"] {\n outline: 2px solid hsl(var(--primary)) !important;\n outline-offset: -2px;\n}\n\n/* Or use Settings → Terminal → Workspace Focus Indicator → Border on focused pane */',
'settings.appearance.language':'Language',
'settings.appearance.language.desc':'Choose the UI language',
'settings.appearance.uiFont':'Interface Font',
'settings.appearance.uiFont.desc':'Choose the font for the application interface',
'settings.appearance.windowOpacity.desc':'Adjust the transparency of the entire application window. Lower values also fade terminal text. Some Linux desktop environments may not support this.',
'settings.appearance.windowBackgroundBlur.desc':'Blur the desktop behind the window. At 100% opacity the effect is hidden, but your choice is preserved.',
'settings.appearance.appIcon':'App Icon',
'settings.appearance.appIcon.desc':'Choose a dock/taskbar icon style to make Netcatty easier to spot when switching apps. Applies at runtime; the installed app bundle icon is unchanged.',
'settings.appearance.appIcon.desc':'Choose a dock/taskbar icon style to make Netcatty easier to spot when switching apps. On Windows, the notification area icon uses the same style. Applies at runtime; the installed app bundle icon is unchanged.',
'settings.terminal.behavior.rightClick.desc':'Action when right-clicking in terminal',
'settings.terminal.behavior.rightClick.longPressMenu':'Right-click hold opens menu',
'settings.terminal.behavior.rightClick.longPressMenu.desc':'When right-click pastes or selects a word, hold for half a second to open the menu. Does not override terminal apps that capture the mouse.',
'settings.terminal.behavior.rightClick.fullscreenMenu':'Show menu over fullscreen apps',
'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.behavior.confirmMultilinePaste':'Ask before pasting multiple lines',
'Show a confirmation dialog before pasting multiple lines, with the line/character count, an editable preview, and Send / Send line by line / Cancel actions. Protects switch and firewall CLIs (Cisco IOS, Huawei VRP, H3C Comware) that do not support bracketed paste.',
'Use \\n for newline, \\t for tab, and \\\\ for backslash.',
'Use \\n for newline, \\t for tab, \\e for Escape, and \\\\ for backslash. Defaults to \\e\\r (Escape + Enter, the legacy Alt+Enter encoding), which most TUI CLIs treat as a newline.',
'settings.terminal.behavior.shiftEnterForceText':'Send text in Win32 input mode',
'Also send the text above when a local Windows ConPTY session negotiates Win32 input mode. Needed for Node/Bun CLIs (Claude Code, CodeBuddy) that cannot read modifier keys from console input records.',
'Show a system notification when a program emits OSC 9, OSC 777 notify, or kitty OSC 99. Used by Codex and other agents for turn-complete and approval alerts.',
'settings.terminal.connection.verifyHostKeys.desc':'Ask before connecting to a new or changed SSH host key. Turn this off only for trusted private networks.',
'settings.terminal.connection.temporaryLegacyAlgorithms':'Allow legacy algorithms for temporary connections',
'settings.terminal.connection.temporaryLegacyAlgorithms.desc':'SSH connections with no saved host, including PuTTY-style launches and quick connect, offer the same deprecated algorithms as the per-host option. Saved hosts keep their own setting. Off by default; these algorithms have known weaknesses.',
'settings.terminal.connection.temporaryNetworkDeviceMode':'Network device mode for temporary connections',
'settings.terminal.connection.temporaryNetworkDeviceMode.desc':'SSH connections with no saved host, including external launches and quick connect, start in network device mode. Saved hosts and groups keep their own device type. Off by default.',
'settings.terminal.connection.sshAutoReconnectEnabled.desc':'When turned on, established SSH sessions that drop unexpectedly try to reconnect every 5 seconds until the tab is closed or the connection succeeds.',
'settings.terminal.connection.defaultProxy.desc':'Saved proxy used when a host has no proxy of its own and does not inherit one from a group. None connects directly.',
'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.',
@@ -704,8 +892,8 @@ Highlight the focused split pane:
'settings.terminal.systemManager.processRefreshInterval.desc':'How often to refresh the process list in the system manager side panel.',
'settings.shortcuts.binding.sftp-open':'Open File / Enter Directory',
'settings.shortcuts.binding.sftp-go-parent':'Go to Parent Directory',
'settings.shortcuts.binding.sftp-navigate-to':'Navigate to Selected Directory',
// Context menus / common actions
'action.newHost':'New Host',
'action.newSubfolder':'New Subfolder',
@@ -935,6 +1168,10 @@ Highlight the focused split pane:
'vault.groups.deleteDialogTitle':'Delete Group',
'vault.groups.deleteDialog.desc':'This will permanently delete the group and move all hosts to the root level.',
'vault.groups.deleteDialog.managedDesc':'This is a managed SSH config group. Deleting it will also delete all hosts and unlink from the source file.',
'vault.groups.deleteDialog.mixedDesc':'This group contains hosts imported from a linked SSH config file. Every host under its managed subgroup will be deleted, even hosts not managed by that file. All other hosts will move to the root level unless you also delete them below.',
'vault.groups.deleteDialog.managedWarning':'Deleting this group will also clear its entries from the linked SSH config file. Entries that came from that file may be lost for other apps that use it. Back up the file before continuing.',
'vault.groups.deleteDialog.sourcesChanged':'The managed files affected by this deletion have changed. Review the files and confirm again.',
"settings.plugins.manager.preview":"Imported packages stay disabled until you review and enable them.",
"settings.plugins.manager.empty":"Import a local plugin package to get started.",
"settings.plugins.manager.enabled":"Enabled",
"settings.plugins.manager.disabled":"Disabled",
"settings.plugins.manager.enable":"Enable",
"settings.plugins.manager.disable":"Disable",
"settings.plugins.manager.remove":"Uninstall",
"settings.plugins.manager.removeNote":"Uninstall removes package code and local KV data. User-owned settings, secrets and sync sidecars are retained.",
"plugins.interaction.sharing":"Text you share becomes available to this plugin and its granted capabilities, including network access. Reading does not approve a new network destination.",
"plugins.interaction.previewNote":"The clipboard is read only after you choose Preview. You can then inspect the text and separately confirm sharing it.",
"plugins.interaction.failed":"This request expired or could not be completed.",
"plugins.command.failed":"The plugin command could not be completed. Check its required selection and permissions.",
"plugins.interaction.terminalDisclosure":"Only the current selection from this terminal is shared, up to 4096 UTF-8 bytes. Terminal text can contain passwords, tokens, and other secrets. Inspect the exact preview before sharing. No redaction guarantee is made.",
"plugins.interaction.draftNote":"Editable draft for review only. This dialog sends no terminal input or Enter. Copy text yourself and use the normal terminal UI if you choose to send it. Your edits stay in this dialog.",
"plugins.interaction.exactText":"Exact text with escaped controls",
"plugins.interaction.closeDraft":"Close draft",
"settings.plugins.permissions.current":"Matches the current activation",
"settings.plugins.permissions.inactive":"Inactive for the current activation",
"settings.plugins.permissions.note":"Declared capabilities default to Allow. Ask requests a scoped decision; Deny blocks access immediately. Target selection and draft editing still use the host UI.",
"settings.plugins.permissions.session":"Until the session closes",
"settings.plugins.permissions.audit":"Recent security events (up to 100)",
"settings.plugins.permissions.revoke":"Disable and revoke all grants",
"settings.plugins.permissions.revokeNote":"This disables the plugin, cancels its work, and removes saved grants. Your Allow / Ask / Deny settings are retained.",
"settings.plugins.permissions.unavailable":"Plugin permission management is unavailable",
"settings.plugins.manager.permissionNote":"Declared supported capabilities default to Allow. Change each capability to Ask or Deny in the permission controls. Host validation and resource scope always apply.",
"settings.plugins.themes.description":"Preview changes only this window. Apply saves this device’s selection. Personal accent, fonts and terminal overrides stay in control.",
"settings.plugins.themes.empty":"No enabled plugin themes for this mode.",
"settings.plugins.themes.choose":"Choose a theme",
"settings.plugins.themes.unavailable":"Saved theme unavailable; using the host default",
"settings.plugins.themes.invalid":"Some theme palettes were rejected or unavailable.",
"settings.plugins.themes.failed":"Could not apply theme.",
"settings.plugins.manager.dataTheme":"Theme pack: validated colors, no executable runtime.",
"settings.plugins.manager.browser":"Browser runtime: privileged access goes through host permission brokers.",
"settings.plugins.manager.advanced":"Advanced runtime: full Node filesystem and network authority. Broker permissions cannot restrict this access. Development preview only.",
"settings.plugins.manager.data":"Local KV stays on this device. Only non-secret settings explicitly marked for sync participate in encrypted cloud sidecars. Packages and permission grants do not sync.",
"settings.plugins.manager.lastError":"Last recorded error",
"settings.plugins.sources.offline":"Source unavailable. If a previously validated catalog exists for this source, it remains available for browsing only. Installation is unavailable until refresh succeeds.",
"settings.plugins.sources.note":"This catalog is provided by the selected third party, not curated by Netcatty. Review the source domain and permissions before installing. Updates are manual.",
"settings.plugins.sources.sourceOnly":"Source-only entry from this provider. No downloadable package is available; review the author’s build and import instructions.",
"settings.plugins.market.offline":"Registry unavailable. Showing the last valid catalog or bundled examples. Installation is unavailable until refresh succeeds.",
"settings.plugins.market.note":"Curated open-source plugins. Review is not a security certification. Updates are manual.",
"settings.plugins.market.back":"All plugins",
"settings.plugins.market.install":"Install this version (disabled)",
"settings.plugins.market.installNote":"This replaces and stops the installed version. Review its permissions in Installed plugins before enabling.",
"settings.plugins.market.sourceOnly":"First-party source example. Build with the local CLI and import the .ncpkg; no downloadable release has been published.",
"settings.plugins.market.readmeNote":"Pinned to the commit above. HTML is inert and remote images are not loaded. Written by the plugin author.",
"settings.plugins.manager.noVault":"System key storage is unavailable. Secret and credential operations are blocked; browsing and non-secret plugins still work.",
"settings.plugins.manager.sensitive":"Sensitive access: this capability may expose private data. Its mode can be changed in permission settings.",
"settings.plugins.manager.dual":"Browser is the default runtime. This package also includes a Node entrypoint with full device authority; developer mode only.",
"settings.plugins.manager.advancedDeclaration":"Browser entrypoint with advanced permission declarations. Not eligible for normal browser-only mode; review every declared permission.",
"settings.plugins.market.retry":"Retry",
"settings.plugins.market.empty":"No catalog entries are available. You can still import a local browser plugin.",
"settings.plugins.market.emptyReadme":"This README is empty.",
"settings.plugins.market.search":"Search name, author or description",
"settings.plugins.market.noResults":"No matching plugins. Try another search.",
"settings.plugins.market.manifest":"Compatibility and permissions",
"settings.plugins.manager.unbounded":"No narrower resource scope is declared. Host validation and the selected permission mode still apply.",
"settings.plugins.sources.conflict":"This ID is installed from another source. Uninstall and explicitly reset its private data before switching. Existing source:",
"settings.plugins.sources.clearPrivate":"Also clear this source’s private data, grants and secrets to reinstall from another source. This cannot be undone.",
"settings.plugins.sources.risk":"Third-party sources are not maintained or endorsed by Netcatty. Review the real domain, package permissions and author before installing.",
"settings.plugins.sources.accept":"I trust this domain to supply plugin metadata and packages. This does not grant plugin permissions.",
"settings.plugins.sources.removeNote":"Installed plugins and their data remain. Updates from this source stop; another source will not replace it.",
"settings.plugins.sources.disablePlugins":"Also disable plugins installed from this source",
'plugins.hostActions.selectionNote':'Only the selected hosts\' saved labels, addresses, usernames, protocols, groups and tags are shared. Group selection includes only the hosts selected now. Later edit or connection requests follow this plugin\'s permission settings.',
'plugins.hostActions.fieldDiff':'Changes to save',
'plugins.hostActions.defaultsWarning':'Saved connection defaults apply. Changing a group can change inherited authentication, proxy settings and scripts.',
'plugins.hostActions.invalidDraft':'Enter a valid host draft.',
'plugins.hostActions.scriptsRun':'Saved startup, connect or output scripts may run with this connection.',
'plugins.hostActions.noScripts':'No saved startup, connect or output scripts are configured for this host.',
'plugins.hostActions.connectionNote':'Netcatty will open this connection using the saved settings. This does not share session content with the plugin.',
'plugins.hostActions.localExecution':'Local execution: this opens a shell on this device.',
'plugins.hostActions.managedUnsupported':'Managed SSH config changes require the Vault host editor.',
'plugins.hostActions.failed':'The request expired, was revoked, or the host changed. Select the host again.',
'plugins.hostActions.selectionLimit':'Select up to 50 current hosts.',
"monitor.diskIoHint":"Aggregate Linux hardware-backed block-device I/O, excluding partitions and stacked virtual devices. Unavailable until two valid samples; not supported on macOS.",
"monitor.actions":"Panel actions",
"monitor.unavailable":"Monitoring is unavailable. Try starting again when the connection is restored.",
"monitor.username":"Username",
"monitor.password":"Password (this run only)",
"monitor.status.limit":
"Monitoring is limited to 256 unique hosts at a time.",
"sync.entityType.monitorPanels":"monitor panels",
"monitor.title":"Monitoring",
"monitor.create":"New panel",
"monitor.createFirst":"Create your first panel",
"monitor.empty":"Your hosts, at a glance",
"monitor.emptyHint":
"Choose saved hosts from any group. Start monitoring when you are ready.",
"monitor.choosePanel":"Choose a panel",
"monitor.edit":"Edit panel",
"monitor.copy":"Duplicate panel",
"monitor.copyName":"{name} copy",
"monitor.delete":"Delete panel",
"monitor.deleteHint":
"Delete “{name}”? Its monitoring will stop. Saved hosts and other panels are kept.",
"monitor.start":"Start monitoring",
"monitor.stop":"Stop",
"monitor.stopAll":"Stop all",
"monitor.list":"List",
"monitor.charts":"Charts",
"monitor.search":"Search hosts, groups, tags or notes",
'systemManager.errors.clusterUnreachable':"Can't reach the cluster for context {{context}}",
'systemManager.errors.clusterUnreachableGeneric':"Can't reach the cluster",
'systemManager.errors.loadOverview':'Failed to load system overview',
'systemManager.errors.loadGpu':'Failed to load GPU / NPU stats',
'systemManager.errors.loadPorts':'Failed to load listening ports',
'systemManager.errors.loadServices':'Failed to load systemd services',
'systemManager.errors.sshChannelUnavailable':'The server refused to open a new execution channel. Try again later, or reconnect this host.',
'systemManager.unsupportedRemote':'This host does not allow extra SSH exec channels, so System Manager is unavailable. Opening this panel will no longer disconnect the terminal.',
'systemManager.overview.empty':'No system overview data yet.',
'systemManager.overview.loading':'Loading system overview…',
'terminal.dragDrop.notConnected':'Cannot drop files - terminal is not connected',
'terminal.dragDrop.errorTitle':'Drop Error',
'terminal.dragDrop.errorMessage':'Failed to process dropped files',
'terminal.dragDrop.destinationUnknown':'Could not determine the current terminal folder. Enable directory tracking, or open SFTP and choose an upload folder first.',
'terminal.dragDrop.uploadCancelled':'The upload was cancelled because the terminal connection changed or could not be reused. Drop the files again after reconnecting.',
'terminal.dragDrop.needsSudoElevation':'This folder is not writable as the login user. Enable Sudo elevation in host settings, or go back to your user directory and drop again.',
'terminal.search.placeholder':'Search...',
'terminal.search.noResults':'No results',
'terminal.search.prevMatch':'Previous match (Shift+Enter)',
'terminal.auth.credentialsUnavailable':'Saved credentials cannot be decrypted on this device. Please re-enter and save them again.',
'terminal.auth.jumpCredentialsUnavailable':'A jump host has saved credentials that cannot be decrypted on this device. Open host settings and re-enter them.',
'cloudSync.provider.disconnect.confirmMessage':'This device will stop syncing with {name}. Its saved address and login credentials will be removed. Your local vault, backup history and sync encryption password stay here. If system credential storage is unavailable, plugin credentials may need manual removal.',
'cloudSync.provider.disconnect.cloudRemains':'Encrypted sync copies may still exist in your {name} storage. To remove them, delete the sync files there. Disconnecting does not clear this device for handover.',
'cloudSync.provider.disconnect.failed':'Could not finish disconnecting from {name}. Saved connection details may remain. Please try again.',
'cloudSync.provider.webdav':'WebDAV',
'cloudSync.provider.webdav.desc':'Connect to a self-hosted WebDAV endpoint',
'cloudSync.aiSessions.title':'Sync AI chat history',
'cloudSync.aiSessions.desc':'Include AI chat sessions in cloud sync. Messages merge append-only across devices; attachment contents stay on this device.',
'cloudSync.aiSessions.compatibility':'Older versions may overwrite chat history. Use an updated Netcatty on every synced device.',
'cloudSync.aiSessions.enableFailed':'Could not update chat sync. Check the cloud connection and available local storage, then try again.',
'cloudSync.strategy.smartMergePreserveDesc':'Combine changes from both sides and never propagate a one-sided deletion — an entry survives until it is removed on every device. Recommended when you restore backups or keep several machines in sync.',
'cloudSync.strategy.smartMergeDesc':'Combine changes from both sides when possible; if Netcatty cannot decide safely, ask you to choose.',
'cloudSync.strategy.preferCloud':'Cloud wins',
'cloudSync.strategy.preferCloudDesc':'When both sides changed, download the cloud version and replace local changes.',
'cloudSync.localBackups.clearBusy':'Finish restoring or recovering the vault before clearing backups.',
'cloudSync.localBackups.clear':'Clear all backups',
'cloudSync.localBackups.clearConfirmTitle':'Clear local backup history?',
'cloudSync.localBackups.clearConfirmDesc':'Permanently delete all local vault backups on this device. Your current hosts, saved credentials and cloud copies will remain. This does not prepare the device for handover. New backups will still be created during normal use.',
'cloudSync.localBackups.clearSuccess':'Local backup history cleared.',
'cloudSync.localBackups.clearFailed':'Could not clear all local backups. Please retry.',
"fido.recovery.timeout.desc":"This device operation has stopped. Check that your key is connected, then retry and follow its touch or verification prompt.",
"fido.recovery.pin-failed.title":"Security key PIN was not accepted",
"fido.recovery.pin-failed.desc":"Check the FIDO2 PIN for this device before retrying. Repeated incorrect entries can lock the device.",
"fido.recovery.failed.title":"Security key verification did not complete",
"fido.recovery.failed.desc":"Check that the matching device is connected and supports this key. Retry, or cancel to check the device and OpenSSH configuration.",
'serial.noPorts':'No serial ports detected. Connect a device and refresh.',
'serial.field.customPort':'Custom Port Path',
'serial.field.namedPipeHint':'Windows VM console: enter \\\\.\\pipe\\name. The VM must create the pipe as a server. Baud rate and framing are ignored.',
'serial.field.customPortPlaceholder':'e.g. /dev/ttys001 or COM1',
'serial.field.byteOrientedBackspaceDesc':'Send one DEL per byte so the device deletes a whole multi-byte character (for devices that delete bytes). Disable for character-aware line editors (e.g. Linux readline).',
'notes.editor.unrenderableMarkdown':'This note contains Markdown the rich editor cannot display (for example unbalanced <tag> text). Showing the raw Markdown source instead — the content is intact.',
'notes.editor.retryRichView':'Retry rich view',
'notes.preview.empty':'Nothing to preview yet.',
'notes.empty.title':'No notes yet',
'notes.empty.desc':'Create Markdown notes for runbooks, reminders, and server handoff details.',
'sftp.tabs.copyCurrentPath':'Copy and go to current path',
'sftp.conflict.title':'File Conflict',
'sftp.conflict.desc':'A file with the same name already exists at the destination',
'sftp.conflict.folderTitle':'Folder Conflict',
'sftp.conflict.folderDesc':'Choose how to continue.',
'sftp.conflict.folderFileDesc':'A file with the same name already exists at the destination. A folder cannot be merged with a file.',
'sftp.conflict.folderSymlinkDesc':'A symbolic link with the same name already exists at the destination. A folder cannot be merged with a symbolic link.',
'sftp.conflict.folderUnknownDesc':'An item with the same name exists at the destination, but its type could not be confirmed. Replace is unavailable to protect destination data.',
'settings.sftp.doubleClickBehavior.transferDesc':'Transfer the file to the other pane\'s active host',
'settings.sshDeepLink.title':'SSH and Telnet links',
'settings.sshDeepLink.enable':'Open ssh:// and telnet:// links with Netcatty',
'settings.sshDeepLink.enableDesc':'Allow Netcatty to handle ssh:// and telnet:// links from browsers and other apps.',
'settings.sshDeepLink.enableDesc':'Allow Netcatty to handle ssh:// and telnet:// links from browsers and other apps, plus PuTTY-style launches such as -ssh user@host -P 22 -pw password.',
'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.',
'hostConnectionTest.successHint':'The host accepted your credentials. You can close this window.',
'hostConnectionTest.connecting':'Connecting… timeout in {seconds}s',
'hostConnectionTest.failed':'Connection failed.',
'hostConnectionTest.retry':'Retry',
'hostConnectionTest.credentialsUnavailable':'Saved credentials cannot be decrypted on this device. Re-enter them below.',
'hostDetails.section.address':'Address',
'hostDetails.hostname.placeholder':'IP or Hostname',
'hostDetails.section.general':'General',
'hostDetails.os.title':'System identification',
'hostDetails.os.desc':'Prefer automatic detection. Override only when the detected system is incorrect. Network Device Mode is configured separately.',
'hostDetails.os.auto':'Automatic',
'hostDetails.os.unknown':'Other / unknown',
'hostDetails.os.selection':'System selection',
'hostDetails.os.current':'Current system',
'hostDetails.os.freebsd':'FreeBSD',
'hostDetails.os.linux':'Linux',
'hostDetails.os.windows':'Windows',
'hostDetails.os.macos':'macOS',
'hostDetails.section.sftp':'SFTP Settings',
'hostDetails.sftp.fileProtocol':'File Protocol',
'hostDetails.sftp.fileProtocol.desc':'Auto tries SFTP, then falls back to SCP mode when the SFTP subsystem is unavailable (e.g. some NAS). Force SCP for hosts that only support SCP.',
'hostDetails.totp.autoSubmit':'Auto-submit on known TOTP prompts',
'hostDetails.totp.autoSubmitDesc':'Submits once per connection, and only when the prompt exactly matches JumpServer, Google Authenticator PAM, or your pattern.',
'hostDetails.totp.removeMessage':'This host will no longer store this TOTP secret.',
'hostDetails.totp.error.hotp':'HOTP is not supported. Use a TOTP secret.',
'hostDetails.totp.error.migration':'otpauth-migration QR codes are not supported. Use the service binding QR code for a single account.',
'hostDetails.totp.error.invalid':'Could not read a TOTP secret from that value.',
'hostDetails.totp.error.notFound':'No TOTP QR code was found.',
'hostDetails.totp.error.permission':'Screen recording permission is required. Allow Netcatty to record the screen in System Settings, then try again.',
'hostDetails.totp.error.unavailable':'Screen capture is not available.',
'hostDetails.totp.error.pattern':'The custom prompt pattern is not a valid RE2 regular expression.',
'hostDetails.auth.credentialRequired':'Select a key or certificate before saving this login method.',
'hostDetails.identity.suggestions':'Identities',
'hostDetails.identity.missing':'Identity not found',
'ai.description':'Configura proveedores de IA, agentes y ajustes de seguridad',
'ai.providers':'Proveedores',
'ai.agents':'Agentes',
'ai.providers.empty':'No hay proveedores configurados. Agrega un proveedor para comenzar.',
'ai.providers.add':'Agregar proveedor',
'ai.providers.active':'Activo',
'ai.providers.apiKeyConfigured':'Clave de API configurada',
'ai.providers.noApiKey':'Sin clave de API',
'ai.providers.configure':'Configurar',
'ai.providers.remove':'Quitar',
'ai.providers.name':'Nombre para mostrar',
'ai.providers.name.placeholder':'p. ej. Mi proveedor',
'ai.providers.style':'Estilo de protocolo',
'ai.providers.style.anthropic':'Compatible con Anthropic',
'ai.providers.style.openai':'Compatible con OpenAI',
'ai.providers.style.google':'Compatible con Google',
'ai.providers.style.inherited':'auto',
'ai.providers.style.help':'Selecciona qué formato de API usan las peticiones. Anúlalo cuando un endpoint de terceros hable un dialecto distinto al que sugiere su tipo de proveedor.',
'ai.providers.openaiApi':'Formato de API de OpenAI',
'ai.providers.openaiApi.chat':'Chat Completions',
'ai.providers.openaiApi.responses':'Responses',
'ai.providers.openaiApi.help':'Chat Completions funciona con la mayoría de endpoints compatibles con OpenAI. Responses puede mejorar la tasa de aciertos de caché en relés que admiten /v1/responses.',
'ai.providers.baseUrl.anthropicHelp':'Compatible con Anthropic: host con o sin /v1 (por ejemplo https://gateway.example o https://gateway.example/v1). La detección y el chat usan /v1/models y /v1/messages.',
'ai.providers.baseUrl.ollamaHelp':'Ollama local: http://localhost:11434/v1 (sin API key). Ollama Cloud: https://ollama.com/v1 y tu clave de Cloud.',
'ai.providers.skipTLSVerify':'Omitir la verificación del certificado TLS (para certificados autofirmados)',
'ai.providers.contextWindow.help':'Déjalo en blanco para usar el valor de la lista de modelos cuando esté disponible; de lo contrario, Netcatty usa un valor predeterminado seguro.',
'ai.providers.contextWindow.error':'Ingresa un número entero positivo o déjalo en blanco.',
'ai.providers.headers.help':'Cabeceras opcionales para este proveedor. Los valores se ocultan y se cifran al guardarlos.',
'ai.providers.headers.name':'Nombre de la cabecera',
'ai.providers.headers.value':'Valor de la cabecera',
'ai.providers.headers.add':'Agregar cabecera',
'ai.providers.headers.remove':'Quitar cabecera',
'ai.providers.headers.invalid':'Usa cabeceras HTTP válidas y únicas. Content-Length y Transfer-Encoding se gestionan automáticamente.',
'ai.providers.headers.loadError':'No se pudieron desbloquear las cabeceras guardadas. Cancela y vuelve a intentarlo después de desbloquear el llavero del sistema.',
'ai.providers.headers.saveError':'No se pudo guardar de forma segura. Comprueba que el llavero del sistema esté disponible y vuelve a intentarlo.',
// AI Codex
'ai.codex':'Codex',
'ai.codex.title':'Codex CLI',
'ai.codex.description':'Conecta OpenAI Codex. Inicia sesión con ChatGPT aquí, o habilita una clave de API de un proveedor compatible con OpenAI y un endpoint personalizado en Configuración.',
'ai.codex.appServer.description':'Usa el protocolo persistente de Codex para aprobaciones nativas, controles de sandbox, modelos en vivo y preguntas a mitad de turno. El SDK sigue siendo el predeterminado.',
'ai.codex.appServer.checking':'Comprobando la compatibilidad con App Server…',
'ai.codex.appServer.available':'App Server está disponible para esta Codex CLI.',
'ai.codex.appServer.modelCatalogWarning':'El catálogo de modelos de Codex en vivo no está disponible. Se usa la lista de modelos integrada.',
'ai.codex.appServer.approval.allowSession':'Permitir por sesión',
'ai.codex.appServer.userInput.title':'Codex necesita tu aporte',
'ai.codex.appServer.userInput.description':'Responde estas preguntas para continuar el turno actual.',
'ai.codex.appServer.userInput.other':'Ingresa otra respuesta',
'ai.codex.appServer.userInput.autoResolve':'Codex continuará automáticamente si no se proporciona una respuesta a tiempo.',
'ai.codex.steer.placeholder':'Agrega una instrucción mientras Codex está trabajando…',
'ai.codex.steer.notSteerableReview':'Este turno de revisión de Codex no puede aceptar instrucciones adicionales. Tu borrador se conservó.',
'ai.codex.steer.notSteerableCompact':'Este turno de compactación de Codex no puede aceptar instrucciones adicionales. Tu borrador se conservó.',
'ai.codex.steer.busy':'Ya se está enviando otra instrucción a Codex.',
'ai.codex.steer.inactive':'El turno de Codex ya terminó. Tu borrador se conservó.',
'ai.codex.steer.unsupported':'Las instrucciones en curso requieren el runtime de Codex App Server.',
'ai.codex.steer.failed':'Codex no pudo aceptar la instrucción adicional. Tu borrador se conservó.',
'ai.codex.detecting':'Detectando...',
'ai.codex.notFound':'No encontrado',
'ai.codex.awaitingLogin':'Esperando inicio de sesión',
'ai.codex.connectedChatGPT':'Conectado mediante ChatGPT',
'ai.codex.connectedApiKey':'Conectado mediante clave de API',
'ai.codex.connectedCustomConfig':'Conectado mediante ~/.codex/config.toml',
'ai.codex.customConfigIncomplete':'Configuración personalizada detectada (falta la variable de entorno)',
'ai.codex.customConfigHint':'Usando el proveedor personalizado "{provider}" configurado en ~/.codex/config.toml: no se necesita inicio de sesión con ChatGPT.',
'ai.codex.customConfigMissingEnvKey':'Advertencia: {envKey} no está definida en tu entorno de shell. Expórtala (o inicia netcatty desde un shell que la tenga) para que Codex pueda autenticarse.',
'ai.codex.notConnected':'No conectado',
'ai.codex.statusUnknown':'Estado desconocido',
'ai.codex.path':'Ruta:',
'ai.codex.notFoundHint':'No se pudo encontrar codex en el PATH. Instálalo o especifica la ruta del ejecutable a continuación.',
'ai.claude.configSection':'Autenticación y configuración (opcional)',
'ai.claude.configDir':'Directorio de configuración',
'ai.claude.configDir.placeholder':'~/.claude (déjalo en blanco para usar el predeterminado)',
'ai.claude.configDir.hint':'Define CLAUDE_CONFIG_DIR: apunta a una carpeta donde hayas ejecutado el inicio de sesión `claude` (contiene settings.json + credenciales).',
'ai.claude.settings':'Archivo de configuración',
'ai.claude.settings.placeholder':'~/team-settings.json (ruta o JSON en línea {"model":"..."})',
'ai.claude.settings.hint':'Opcional. Una ruta a settings.json o un JSON en línea, pasado al SDK como `settings`. Se suma a — y es independiente de — el directorio de configuración anterior (se combina por encima, no lo reemplaza).',
'ai.claude.envVars.hint':'Una KEY=VALUE por línea, pasada al agente de Claude. Se almacena localmente en texto plano; para claves de API o credenciales, prefiere el directorio de configuración anterior (un inicio de sesión `claude`).',
'ai.claude.check':'Verificar',
'ai.claude.resetPath':'Restablecer',
// AI GitHub Copilot CLI
'ai.copilot.title':'GitHub Copilot CLI',
'ai.copilot.description':'Usa la CLI de GitHub Copilot. Una vez detectada, se puede seleccionar como agente de codificación externo.',
'ai.copilot.detecting':'Detectando...',
'ai.copilot.detected':'Detectado',
'ai.copilot.notFound':'No encontrado',
'ai.copilot.path':'Ruta:',
'ai.copilot.notFoundHint':'No se pudo encontrar copilot en el PATH. Instálalo o especifica la ruta del ejecutable a continuación.',
'ai.cursor.description':'Usa el SDK de Cursor o el inicio de sesión local de la CLI de Agent.',
'ai.cursor.detecting':'Detectando...',
'ai.cursor.detected':'Disponible',
'ai.cursor.notFound':'No disponible',
'ai.cursor.path':'Runtime:',
'ai.cursor.notFoundHint':'Ingresa una clave de API para habilitar Cursor o cambia al inicio de sesión por CLI.',
'ai.cursor.notInstalledHint':'No se detectó el SDK de Cursor ni la CLI de Agent.',
'ai.cursor.installStatus':'Runtime de Cursor',
'ai.cursor.installed':'Detectado',
'ai.cursor.notInstalled':'No detectado',
'ai.cursor.modeCli':'Inicio de sesión por CLI',
'ai.cursor.modeApiKey':'Clave de API',
'ai.cursor.modeCliHint':'Usa tu sesión local de `cursor-agent login` y la cuota Automática de la suscripción. La clave de API guardada se conserva, pero no se usa en este modo.',
'ai.cursor.modeApiKeyHint':'Usa la API de Cursor medida. El inicio de sesión por CLI se ignora mientras este modo esté activo.',
'ai.cursor.cliLoginStatus':'Inicio de sesión por CLI',
'ai.cursor.cliLoginOk':'Sesión iniciada',
'ai.cursor.cliLoginAs':'Sesión iniciada como {{email}}',
'ai.codebuddy.description':'Usa CodeBuddy Code mediante el SDK oficial de Agent (`@tencent-ai/agent-sdk`). Una vez detectado, se puede seleccionar como agente de codificación externo.',
'ai.codebuddy.detecting':'Detectando...',
'ai.codebuddy.detected':'Detectado',
'ai.codebuddy.notFound':'No encontrado',
'ai.codebuddy.path':'Ruta:',
'ai.codebuddy.notFoundHint':'No se pudo encontrar codebuddy en el PATH. Instálalo o especifica la ruta del ejecutable a continuación.',
'ai.codebuddy.envVars.hint':'Una KEY=VALUE por línea, pasada al agente de CodeBuddy. Define CODEBUDDY_API_KEY o CODEBUDDY_AUTH_TOKEN aquí para la autenticación. Se almacena localmente en texto plano.',
'ai.codebuddy.effort.hint':'Controla la profundidad de razonamiento del modelo. Usa Mínimo/Bajo para comandos simples y ahorrar tokens, y Máximo/Alto para diagnósticos complejos.',
'ai.codebuddy.maxTurns':'Máximo de turnos',
'ai.codebuddy.maxTurns.hint':'Limita el máximo de turnos de conversación por petición para evitar bucles descontrolados. Déjalo vacío para usar el predeterminado.',
'ai.codebuddy.maxBudget.hint':'Gasto máximo (USD) por petición. Se detiene automáticamente al superarse. Déjalo vacío para no tener límite.',
'ai.codebuddy.sandbox':'Modo sandbox',
'ai.codebuddy.sandbox.hint':'Ejecuta llamadas a herramientas en un sandbox, restringiendo el acceso al sistema de archivos y a la red.',
'ai.codebuddy.persistSession':'Persistencia de sesión',
'ai.codebuddy.persistSession.hint':'Guarda en disco la transcripción de la sesión propia de CodeBuddy para que CodeBuddy pueda reanudarla más tarde. Desactívalo para deshabilitar el almacenamiento de sesiones de CodeBuddy; Netcatty aún puede guardar el historial del chat y los metadatos de la sesión externa. Se omiten los puntos de control de archivos. Requiere CodeBuddy CLI 2.125.1 o superior.',
'ai.codebuddy.fileCheckpointing':'Puntos de control de archivos',
'ai.codebuddy.fileCheckpointing.hint':'Habilita puntos de control de operaciones de archivos para poder revertir las modificaciones de IA a los archivos.',
'ai.codebuddy.elicitation.title':'CodeBuddy necesita tu aporte',
'ai.codebuddy.elicitation.description':'Revisa la petición para continuar el turno actual.',
'ai.codebuddy.elicitation.select':'Selecciona una opción',
'ai.codebuddy.elicitation.yes':'Sí',
'ai.codebuddy.elicitation.no':'No',
'ai.codebuddy.elicitation.decline':'Rechazar',
'ai.codebuddy.elicitation.accept':'Continuar',
'ai.codebuddy.elicitation.validation.required':'{field} es obligatorio.',
'ai.codebuddy.elicitation.validation.invalidType':'{field} tiene un valor no válido.',
'ai.codebuddy.elicitation.validation.integer':'{field} debe ser un número entero.',
'ai.codebuddy.elicitation.validation.notInteger':'{field} debe ser un número entero.',
'ai.codebuddy.elicitation.validation.minimum':'{field} debe ser al menos {limit}.',
'ai.codebuddy.elicitation.validation.maximum':'{field} debe ser como máximo {limit}.',
'ai.codebuddy.elicitation.validation.minLength':'{field} debe contener al menos {limit} caracteres.',
'ai.codebuddy.elicitation.validation.maxLength':'{field} debe contener como máximo {limit} caracteres.',
'ai.codebuddy.elicitation.validation.minItems':'Selecciona al menos {limit} opciones para {field}.',
'ai.codebuddy.elicitation.validation.maxItems':'Selecciona como máximo {limit} opciones para {field}.',
'ai.codebuddy.elicitation.validation.format':'{field} debe coincidir con el formato {format}.',
'ai.codebuddy.elicitation.validation.option':'Selecciona una opción válida para {field}.',
// AI OpenCode
'ai.opencode.title':'OpenCode',
'ai.opencode.description':'Usa OpenCode mediante el SDK oficial. Configura proveedores y claves en OpenCode y luego selecciónalo como agente de codificación externo.',
'ai.opencode.detecting':'Detectando...',
'ai.opencode.detected':'Detectado',
'ai.opencode.notFound':'No encontrado',
'ai.opencode.path':'Ruta:',
'ai.opencode.notFoundHint':'No se pudo encontrar opencode en el PATH. Instálalo o especifica la ruta del ejecutable a continuación.',
'ai.opencode.v2.startupTimeout':'OpenCode 2 no estuvo listo a tiempo. Confirma que `opencode serve` funciona en una terminal y vuelve a intentarlo.',
'ai.opencode.v2.exited':'OpenCode 2 se detuvo antes de que el chat pudiera iniciarse.',
'ai.opencode.v2.unauthorized':'OpenCode 2 rechazó la conexión. Vuelve a intentar el chat.',
'ai.opencode.v2.unavailable':'No se pudo contactar con OpenCode 2. Comprueba que esté instalado y vuelve a intentarlo.',
'ai.opencode.v2.sessionFailed':'OpenCode 2 no pudo completar este turno.',
'ai.opencode.v2.empty':'OpenCode 2 devolvió una respuesta vacía. Ejecuta `opencode` en una terminal para configurar la autenticación y los modelos.',
'ai.opencode.v2.contextSkipped':'OpenCode 2 no pudo aplicar el contexto de la conversación de Netcatty.',
'ai.opencode.v2.mcpSkipped':'OpenCode 2 no pudo adjuntar una herramienta MCP. El chat continuará sin ella.',
// AI MiMo Code (CLI de Xiaomi MiMo Code — fork de OpenCode)
'ai.mimo.title':'MiMo Code',
'ai.mimo.description':'CLI de MiMo Code de Xiaomi, un fork de OpenCode. Instálalo y configura los proveedores en su propia configuración, luego selecciónalo como agente de codificación externo.',
'ai.mimo.detecting':'Detectando...',
'ai.mimo.detected':'Detectado',
'ai.mimo.notFound':'No encontrado',
'ai.mimo.path':'Ruta:',
'ai.mimo.notFoundHint':'No se pudo encontrar mimo en el PATH. Instálalo o especifica la ruta del ejecutable a continuación.',
// AI Grok Build (in-app managed agent — distinct from External MCP client install)
'ai.grok.title':'Grok Build',
'ai.grok.description':"La CLI de agente de codificación Grok Build de xAI. Instala la CLI de Grok, inicia sesión con `grok login` o define XAI_API_KEY, y luego selecciónala como agente externo.",
'ai.grok.detecting':'Detectando...',
'ai.grok.detected':'Detectado',
'ai.grok.notFound':'No encontrado',
'ai.grok.path':'Ruta:',
'ai.grok.notFoundHint':'No se pudo encontrar grok en el PATH. Instala la CLI de Grok Build o especifica la ruta del ejecutable a continuación.',
'Habla con Grok mediante el Protocolo de Cliente de Agente (grok agent stdio). Inyecta el MCP de Netcatty en session/new. Apágalo para usar la ruta original de CLI headless con streaming-json.',
'ai.grok.runtime.streamingJson.hint':
'Usando streaming-json headless (grok -p --output-format streaming-json). El archivo .grok/config.toml del proyecto se usa para la inyección de MCP.',
// AI Default Agent
'ai.defaultAgent':'Agente predeterminado',
'ai.defaultAgent.description':'Agente que se usará al iniciar una nueva sesión de IA',
'ai.defaultAgent.catty':'Catty (Integrado)',
'ai.toolAccess.title':'Acceso a herramientas',
'ai.toolAccess.mode':'Modo de acceso de Netcatty',
'ai.toolAccess.description':'Elige cómo acceden los agentes externos a las sesiones de Netcatty. MCP expone el servidor integrado, mientras que Habilidades + CLI apunta a los agentes hacia las habilidades locales de Netcatty y los comandos de CLI.',
'ai.toolAccess.mode.mcp':'MCP',
'ai.toolAccess.mode.skills':'Habilidades + CLI',
'ai.toolAccess.mcpPrompt.title':'Prompt para tu cliente de IA',
'ai.toolAccess.mcpPrompt.description':'Pega este prompt en tu cliente de IA (Codex, Claude Code, …) y registrará el MCP de Netcatty por ti.',
'ai.toolAccess.mcpPrompt.enableHint':'Activa MCP externo abajo para incluir la ruta del launcher en este prompt.',
'ai.toolAccess.skills.file':'Archivo de skill',
'ai.toolAccess.skills.description':'En modo Habilidades + CLI, los agentes apuntan automáticamente a este archivo de skill local. La ruta del lanzador de la CLI de Netcatty se entrega al agente en cada sesión.',
'ai.toolAccess.skills.unavailable':'Ruta del archivo de skill no disponible',
// External MCP (productized catalog MCP for Codex / Claude Code / Cursor)
'ai.externalMcp.title':'MCP externo',
'ai.externalMcp.allowCommandOnHost':'Siempre',
'ai.externalMcp.allowCommandOnHostHint':'Permitir siempre esta acción en este servidor.',
'ai.externalMcp.allowAll':'Permitir todas las acciones',
'ai.externalMcp.allowSession':'Esta conexión',
'ai.externalMcp.allowSessionHint':'Permitir todas las acciones de este cliente hasta que se desconecte.',
'ai.externalMcp.allowHost':'Este servidor',
'ai.externalMcp.allowHostHint':'Permitir siempre acciones en este servidor.',
'ai.externalMcp.description':'Expón Netcatty como un servidor MCP para clientes externos como Codex, Claude Code, Cursor y Grok. Usa las mismas herramientas del catálogo que los agentes de la aplicación (terminal, SFTP, Vault, reenvío de puertos). Mantén Netcatty en ejecución mientras los clientes estén conectados.',
'ai.externalMcp.sessionsExposed':'Sesiones en alcance: {count}',
'ai.externalMcp.mode.description':'El modo temporal se desactiva automáticamente tras el tiempo de inactividad. El modo Siempre activo restaura el MCP externo cuando se inicia Netcatty.',
'ai.externalMcp.idleTimeout':'Tiempo de inactividad',
'ai.externalMcp.idleTimeout.description':'En modo temporal, desactiva el MCP externo después de esta cantidad de minutos sin operaciones de MCP.',
'ai.externalMcp.idleTimeout.minutes':'min',
'ai.externalMcp.focusOnHostOpen':'Enfocar ventana en host_open',
'ai.externalMcp.focusOnHostOpen.description':'Cuando un cliente de MCP abre un host, trae la ventana principal al frente. Apágalo para seguir trabajando sin interrupciones.',
'ai.externalMcp.approvalAlert':'Alertas de aprobación',
'ai.externalMcp.approvalAlert.description':'Cuando un cliente MCP externo pide confirmación, muestra una notificación del sistema. Si Netcatty no tiene el foco, la barra de tareas o el Dock pide atención. La notificación usa el sonido del sistema.',
'ai.externalMcp.approvalAlert.off':'Desactivado',
'ai.externalMcp.approvalAlert.unfocused':'Si no tiene el foco',
'ai.externalMcp.approvalAlert.always':'Siempre',
'ai.externalMcp.approvalAlert.notificationTitle':'Aprobación de MCP externo',
'ai.externalMcp.silentSessions.description':'Las sesiones abiertas por la IA se mantienen fuera de tu barra de pestañas y no se restauran tras reiniciar. Puedes verlas en cualquier momento desde el panel de la bandeja.',
'ai.externalMcp.sessionIdleTimeout':'Tiempo de inactividad de la sesión abierta',
'ai.externalMcp.sessionIdleTimeout.description':'Cierra automáticamente las sesiones abiertas por una IA después de esta cantidad de minutos sin actividad de terminal o archivos.',
'ai.externalMcp.usage.title':'Cómo usarlo',
'ai.externalMcp.usage.keepRunning':'1. Activa el MCP externo y mantén Netcatty en ejecución.',
'ai.externalMcp.usage.localhost':'2. Los clientes se conectan mediante el launcher local (solo 127.0.0.1). El descubrimiento se elimina cuando desactivas el interruptor.',
'ai.externalMcp.usage.permissions':'3. Las operaciones de escritura siguen Configuración → IA → Seguridad (observador / confirmar / automático) y la lista de bloqueo de comandos.',
'ai.externalMcp.usage.capabilities':'4. Todas las herramientas del catálogo están disponibles: terminal, SFTP, Vault y reenvío de puertos. Los secretos (contraseñas / claves privadas) nunca se devuelven.',
'ai.externalMcp.help.ariaLabel':'Ayuda del MCP externo',
'ai.externalMcp.security':'Seguridad',
'ai.externalMcp.security.description':'Escucha en 127.0.0.1 con un token rotativo, reutiliza el modo de permisos de IA para las escrituras y elimina el descubrimiento al desactivarse. Sin OAuth: este es un puente local de escritorio.',
"ai.privacy.title":"Privacidad de IA",
"ai.privacy.description":"Filtra patrones de texto sensibles conocidos en el contexto y los resultados de herramientas gestionados por Netcatty para Catty, agentes integrados y clientes MCP externos. Desactivado de forma predeterminada. No detecta todos los secretos. Las respuestas binarias de herramientas se bloquean mientras está activo.",
"ai.privacy.boundary":"Solo cubre los datos y las herramientas gestionados por Netcatty. Las herramientas nativas de agentes de terceros, su acceso directo a archivos y otros servidores MCP quedan fuera de esta protección.",
"ai.privacy.enable":"Activar ocultación de texto",
"ai.privacy.privateIps":"Ocultar direcciones IP privadas",
"ai.privacy.privateIps.description":"Oculta también las direcciones IP privadas y locales en el texto devuelto.",
"ai.privacy.cidrs":"Rangos IP adicionales (CIDR)",
"ai.privacy.cidrs.description":"Un CIDR IPv4 o IPv6 por línea. Déjalo vacío para usar solo las reglas integradas.",
"ai.privacy.patterns.description":"Un patrón compatible con RE2 por línea, sin delimitadores / ni indicadores. Los patrones incompatibles o inseguros se rechazan.",
"ai.privacy.connectionScope":"Los marcadores son estables dentro de una conversación gestionada o una conexión MCP externa. Los valores originales solo se restauran mediante llamadas a herramientas gestionadas por Netcatty dentro del mismo ámbito.",
"ai.privacy.reconnect":"Aplica los cambios, inicia una nueva conversación gestionada y vuelve a conectar los clientes MCP externos. Las conversaciones y conexiones existentes no deben reutilizar marcadores.",
"ai.privacy.apply":"Aplicar",
"ai.privacy.applying":"Aplicando...",
"ai.privacy.applied":"Configuración de privacidad de IA aplicada. Inicia una nueva conversación gestionada y vuelve a conectar los clientes MCP externos.",
"ai.privacy.failed":"No se aplicó la configuración de privacidad de IA.",
'ai.externalMcp.http.title':'Streamable HTTP',
'ai.externalMcp.http.description':'Expone las mismas herramientas de MCP externo por Streamable HTTP para clientes que no pueden iniciar el CLI stdio, como Codex dentro de WSL. Desactivado de forma predeterminada.',
'ai.externalMcp.http.enable':'Activar HTTP',
'ai.externalMcp.http.port':'Puerto',
'ai.externalMcp.http.portHint':'Puerto fijo, predeterminado 47391. Las URL de los clientes se mantienen al reiniciar. Rango permitido: 1024–65535.',
'ai.externalMcp.http.allowCustomBind':'Escuchar en otra dirección',
'ai.externalMcp.http.allowCustomBind.description':'De forma predeterminada el extremo se enlaza a 127.0.0.1. Actívalo para NAT de WSL2 y usa 0.0.0.0 o la IP de vEthernet.',
'ai.externalMcp.http.listenAddress':'Dirección de escucha',
'ai.externalMcp.http.clientHost':'Dirección del cliente',
'ai.externalMcp.http.clientHost.description':'Se usa en las configuraciones copiadas cuando la escucha es 0.0.0.0 o ::. Introduce una IP alcanzable, como la IP del host de Windows.',
'ai.externalMcp.http.clientHostRequired':'Introduce la IP a la que deben conectarse los clientes. 0.0.0.0 no es una dirección alcanzable.',
'ai.externalMcp.http.invalidClientHost':'La dirección del cliente debe ser una IP alcanzable, no 0.0.0.0 ni ::.',
'ai.externalMcp.http.warning':'Escuchar fuera de 127.0.0.1 expone el MCP externo a otros equipos que puedan alcanzar esta dirección. Mantén el bearer token en privado y no publiques este puerto.',
'ai.externalMcp.http.url':'URL',
'ai.externalMcp.http.token':'Bearer token',
'ai.externalMcp.http.regenerate':'Regenerar',
'ai.externalMcp.http.regenerateHint':'Emite un bearer token nuevo. Los clientes HTTP deben usar el token nuevo. El archivo de descubrimiento stdio se actualiza; los clientes stdio ya conectados siguen conectados.',
'ai.externalMcp.http.wslHint':'Con la red reflejada de WSL, localhost funciona. Si no, escucha en 0.0.0.0 o la IP de vEthernet y pon la IP del host de Windows en Dirección del cliente.',
'ai.externalMcp.http.envLine':'Variable de entorno del token de Codex',
'ai.externalMcp.http.notListening':'Sin escucha',
'ai.externalMcp.http.listening':'Escuchando',
'ai.externalMcp.http.portInUse':'El puerto {port} ya está en uso.',
'ai.externalMcp.http.requiresExternalMcp':'Activa el MCP externo para que este extremo empiece a escuchar.',
'ai.externalMcp.http.invalidPort':'Introduce un puerto entre 1024 y 65535.',
'ai.externalMcp.http.invalidAddress':'La dirección de escucha debe ser una IP, como 0.0.0.0 o la IP de vEthernet.',
'ai.externalMcp.http.listenFailed':'HTTP no pudo escuchar.',
'ai.externalMcp.http.legacySseNote':'No se ofrece el HTTP+SSE heredado (2024-11-05). Usa esta URL de Streamable HTTP.',
'ai.externalMcp.permissionMode':'Modo de permisos actual: {mode}',
'ai.externalMcp.permissionMode.label':'Modo de permiso de escritura',
'ai.externalMcp.permissionMode.hint':'La misma configuración que Configuración → IA → Seguridad. Automático ejecuta las herramientas de escritura de Netcatty sin solicitar aprobación de Netcatty; Confirmar pregunta cada vez. Los clientes externos (Codex / Claude / Grok) aún pueden mostrar su propia interfaz de aprobación de herramientas.',
'ai.externalMcp.bridgeUnavailable':'Puente de MCP externo no disponible',
'ai.externalMcp.copy':'Copiar',
'ai.externalMcp.copied':'Copiado',
'ai.externalMcp.copyFailed':'No se pudo copiar. Intenta copiarlo manualmente.',
'ai.externalMcp.refresh':'Actualizar',
'ai.externalMcp.clientConfiguration':'Configuración del cliente',
'ai.externalMcp.clientConfiguration.description':'Elige un cliente para instalarlo con un clic, o copia fragmentos de CLI o configuración.',
'ai.externalMcp.client.codex':'Codex',
'ai.externalMcp.client.claude':'Claude Code',
'ai.externalMcp.client.grok':'Grok',
'ai.externalMcp.client.cursor':'Cursor',
'ai.externalMcp.client.other':'Otros',
'ai.externalMcp.cliCommand':'Comando CLI',
'ai.externalMcp.configSnippet':'Fragmento de configuración',
'ai.externalMcp.universalSetupPrompt':'Prompt de instalación universal',
'ai.externalMcp.addToCodex':'Agregar a Codex',
'ai.externalMcp.addToClaude':'Agregar a Claude Code',
'ai.externalMcp.addToGrok':'Agregar a Grok',
'ai.externalMcp.codexAdded':'Entrada MCP de Codex y skill de Netcatty agregados. Reinicia Codex o abre una nueva sesión de Codex.',
'ai.externalMcp.claudeAdded':'Entrada MCP de Claude Code y skill de Netcatty agregados. Reinicia Claude Code o abre una nueva sesión de Claude Code.',
'ai.externalMcp.grokAdded':'Entrada MCP de Grok y skill de Netcatty agregados. Reinicia Grok o abre una nueva sesión de Grok.',
'ai.externalMcp.installCodex':'Instala Codex por separado y luego haz clic en Actualizar.',
'ai.externalMcp.installClaude':'Instala Claude Code por separado y luego haz clic en Actualizar.',
'ai.externalMcp.installGrok':'Instala la CLI de Grok por separado y luego haz clic en Actualizar.',
'ai.externalMcp.conflict.description':'Ya existe una entrada netcatty-external que apunta a otro lugar. Quítala o edítala manualmente.',
'ai.externalMcp.enableForLauncher':'Habilita el MCP externo para obtener una ruta de launcher utilizable.',
'ai.externalMcp.cursor.title':'Cursor',
'ai.externalMcp.cursor.description':'Combina esto con la configuración MCP de Cursor sin reemplazar los servidores existentes.',
'ai.externalMcp.other.description':'Copia el prompt de instalación en otro agente de terminal o combina el fragmento MCP con la configuración de su cliente.',
'ai.externalMcp.status.codexNotFound':'Codex no encontrado',
'ai.externalMcp.status.claudeNotFound':'Claude Code no encontrado',
'ai.externalMcp.status.grokNotFound':'Grok no encontrado',
'ai.externalMcp.status.conflict':'Conflicto',
'ai.userSkills.title':'Habilidades de usuario',
'ai.userSkills.description':'Abre la carpeta de habilidades de Netcatty para agregar tus propios directorios de habilidades. Netcatty escanea estas habilidades automáticamente e inyecta solo índices ligeros, salvo que una habilidad coincida claramente con la petición actual.',
'ai.userSkills.openFolder':'Abrir carpeta de habilidades',
'ai.userSkills.reload':'Recargar habilidades',
'ai.userSkills.location':'Ubicación',
'ai.userSkills.loading':'Escaneando habilidades de usuario...',
'ai.userSkills.empty':'Aún no se encontraron habilidades de usuario. Abre la carpeta para agregar directorios de habilidades con un archivo SKILL.md.',
'ai.userSkills.unavailable':'Las habilidades de usuario no están disponibles en este entorno.',
'ai.userSkills.status.ready':'Lista',
'ai.userSkills.status.warning':'Advertencia',
// AI Quick Messages
'ai.quickMessages.title':'Mensajes rápidos',
'ai.quickMessages.description':'Crea prompts reutilizables que puedes insertar desde el chat de IA con / o con el botón de mensajes rápidos. A diferencia de las habilidades de usuario, los mensajes rápidos llenan el compositor con texto.',
'ai.chat.refreshModelList':'Actualizar la lista de modelos',
'ai.chat.noMatchingModels':'No hay modelos coincidentes',
'ai.chat.recent':'Recientes',
'ai.chat.viewAll':'Ver todo',
'ai.chat.untitled':'Sin título',
'ai.chat.justNow':'Ahora mismo',
'ai.chat.minutesAgo':'hace {n} min',
'ai.chat.hoursAgo':'hace {n} h',
'ai.chat.daysAgo':'hace {n} d',
'ai.chat.newChat':'Nuevo chat',
'ai.chat.undoLastTurn':'Deshacer el último turno',
'ai.chat.undoLastTurnNotice':"Se conserva el chat original. Los comandos y cambios de archivos no se deshacen. Consulta allí las salidas completas guardadas y las tareas en segundo plano existentes.",
'ai.chat.loadMoreSessions':'Cargar más sesiones ({n} más)',
'ai.chat.searchSessions':'Buscar títulos y contenido reciente…',
'ai.chat.clearSessionSearch':'Borrar la búsqueda de sesiones',
'ai.chat.searchSessionsHint':'Busca en los títulos y da prioridad a los mensajes y entradas más recientes. De los mensajes largos y las salidas de herramientas, solo se busca una parte limitada del comienzo. No es una búsqueda de texto completo.',
'ai.chat.searchingSessions':'Buscando...',
'ai.chat.noMatchingSessions':'Sin coincidencias en títulos ni contenido reciente conservado',
'ai.chat.noSessions':'No hay sesiones anteriores',
'ai.chat.forkFromHere':'Bifurcar desde aquí',
'ai.chat.forkCreated':'Conversación bifurcada en un chat nuevo; el original permanece en el historial.',
'ai.chat.retryHint':'Puedes reintentar enviando tu mensaje de nuevo.',
'ai.chat.approvalTimeout':'La aprobación de la herramienta expiró después de 5 minutos. Puedes reintentar enviando tu mensaje de nuevo.',
'ai.chat.mentionNoteUnavailable':'Este agente no puede leer notas de la bóveda en el modo de conexión actual.',
'ai.chat.mentionNoteTooMany':'No se pueden referenciar todas estas notas juntas. Selecciona menos notas.',
'ai.chat.mentionNoteInvalid':'No se pudo adjuntar «{{title}}»: la nota tiene un identificador no válido.',
'ai.chat.untitledNote':'Nota sin título',
'ai.chat.menuUserSkills':'Habilidades de usuario',
'ai.chat.menuSlashCommands':'Comandos de barra',
'ai.chat.slashCommands':'Comandos de barra',
'ai.chat.slashSystemCommands':'Comandos',
'ai.chat.slashQuickMessages':'Mensajes rápidos',
'ai.chat.slashUserSkills':'Habilidades de usuario',
'ai.chat.quickMessages':'Comandos de barra',
'ai.chat.slashNoResults':'No hay comandos que coincidan',
'ai.chat.slashEmptyHint':'Agrega prompts en Configuración → IA → Mensajes rápidos.',
// AI Chat Shortcuts
'ai.chatShortcuts.title':'Atajos de chat',
'ai.chatShortcuts.selectionAction':'Mostrar "Agregar a la conversación" al seleccionar texto de la terminal',
'ai.chatShortcuts.selectionAction.description':'Muestra un pequeño botón de IA junto al texto seleccionado de la terminal.',
// AI Error
'ai.codex.bridgeError':'Los controladores del proceso principal de Codex aún no están cargados. Reinicia Netcatty por completo o reinicia el proceso de desarrollo de Electron y vuelve a intentarlo.',
// AI Web Search
'ai.webSearch.title':'Búsqueda web',
'ai.webSearch.enable':'Habilitar búsqueda web',
'ai.webSearch.enable.description':'Permite que el agente de IA busque en la web información actual.',
'ai.webSearch.provider':'Proveedor de búsqueda',
'ai.webSearch.provider.description':'Elige un proveedor de API de búsqueda web.',
'ai.webSearch.apiKey':'Clave de API',
'ai.webSearch.apiKey.description':'Clave de API para el proveedor de búsqueda seleccionado.',
'ai.webSearch.apiKey.placeholder':'Ingresa la clave de API...',
'ai.webSearch.apiHost':'Host de API',
'ai.webSearch.apiHost.description':'Endpoint de API personalizado. Deja el predeterminado salvo que uses un proxy.',
'ai.webSearch.apiHost.searxngDescription':'URL de tu instancia de SearXNG (obligatoria).',
'ai.webSearch.maxResults':'Máximo de resultados',
'ai.webSearch.maxResults.description':'Cantidad máxima de resultados de búsqueda a devolver (1-20).',
// AI Safety Settings
'ai.safety.title':'Seguridad',
'ai.safety.permissionMode':'Modo de permisos',
'ai.safety.permissionMode.description':'Controla cómo interactúa la IA con tus sesiones de terminal de Netcatty. El modo Observador bloquea las operaciones de escritura que pasan por Netcatty. Las CLIs de agentes externos pueden seguir teniendo sus propias herramientas locales y flujo de aprobación.',
'ai.safety.permissionMode.observer':'Observador: solo lectura, sin acciones',
'ai.safety.permissionMode.confirm':'Confirmar: preguntar antes de las acciones',
'ai.safety.commandTimeout':'Tiempo de espera de comandos',
'ai.safety.commandTimeout.description':'Máximo de segundos que un comando puede ejecutarse antes de ser terminado mediante la ejecución de Netcatty.',
'ai.safety.commandTimeout.unit':'seg',
'ai.safety.responseIdleTimeout':'Espera de respuesta de la IA integrada',
'ai.safety.responseIdleTimeout.description':'Cancela una solicitud de la IA integrada tras este número de segundos sin una nueva respuesta. No controla la duración total ni la ejecución de comandos.',
'ai.safety.responseIdleTimeout.unit':'seg',
'ai.safety.maxIterations':'Máximo de iteraciones',
'ai.safety.maxIterations.description':'Máximo de bucles de uso de herramientas de IA para evitar una ejecución descontrolada. Los agentes externos pueden tener sus propios límites de iteración internos que tienen prioridad.',
'ai.safety.blocklist':'Lista de bloqueo de comandos',
'ai.safety.blocklist.description':'Patrones regex para bloquear comandos peligrosos ejecutados mediante Netcatty.',
'ai.safety.blocklist.reset':'Restablecer a los valores predeterminados',
'ai.safety.blocklist.add':'Agregar patrón',
'ai.safety.grants.title':'Memoria de permisos',
'ai.safety.grants.heading':'Reglas de permitir en modo Confirmar',
'ai.safety.grants.description':'El modo Confirmar pregunta antes de ejecutar una operación. Las reglas guardadas permiten automáticamente las operaciones que coinciden en todas las sesiones/nodos de terminal, y se pueden editar manualmente.',
'ai.safety.grants.empty':'Aún no hay reglas guardadas. Aprueba una herramienta con "Permitir siempre" o agrega una manualmente.',
'ai.safety.grants.capability':'Capacidad',
'ai.safety.grants.sessionPattern':'Patrón de sesión',
'ai.safety.grants.commandPattern':'Patrón de comando (opcional)',
'ai.safety.grants.note':'Nota (opcional)',
'ai.safety.grants.add':'Agregar regla',
'ai.safety.grants.remove':'Quitar',
'ai.safety.grants.export':'Exportar JSON',
'ai.safety.grants.import':'Importar JSON',
'ai.safety.note':'Estos ajustes de seguridad se aplican a las acciones que pasan por Netcatty. Las CLIs de agentes externos también pueden exponer herramientas locales regidas por el propio agente.',
// Unified tooltips for terminal workspace and top tabs (issue #954)
'terminal.layer.addTerminal':'Agregar terminal',
'terminal.layer.switchToSplitView':'Cambiar a vista dividida',
'terminal.layer.sftp':'SFTP',
'terminal.layer.scripts':'Scripts',
'terminal.layer.history':'Historial',
'terminal.layer.theme':'Tema',
'terminal.layer.notes':'Notas',
'terminal.layer.aiChat':'Chat de IA',
'terminal.layer.movePanelLeft':'Mover panel a la izquierda',
'terminal.layer.movePanelRight':'Mover panel a la derecha',
'terminal.layer.movePanelBottom':'Mover panel a la parte inferior',
"monitor.diskIoHint":"E/S agregada de dispositivos de bloques físicos de Linux, sin contar particiones ni capas virtuales. No disponible hasta tener dos muestras válidas; no compatible con macOS.",
"monitor.actions":"Acciones del panel",
"monitor.unavailable":"La monitorización no está disponible. Iníciala de nuevo cuando se restablezca la conexión.",
"monitor.username":"Usuario",
"monitor.password":"Contraseña (solo esta ejecución)",
"monitor.status.limit":
"Se pueden monitorizar hasta 256 equipos únicos a la vez.",
"sync.entityType.monitorPanels":"paneles de monitorización",
"monitor.title":"Monitorización",
"monitor.create":"Nuevo panel",
"monitor.createFirst":"Crear el primer panel",
"monitor.empty":"Tus equipos de un vistazo",
"monitor.emptyHint":
"Elige equipos guardados de cualquier grupo e inicia cuando quieras.",
"monitor.choosePanel":"Elegir panel",
"monitor.edit":"Editar panel",
"monitor.copy":"Duplicar panel",
"monitor.copyName":"Copia de {name}",
"monitor.delete":"Eliminar panel",
"monitor.deleteHint":
"¿Eliminar «{name}»? Se detendrá su monitorización. Se conservarán los equipos y otros paneles.",
"monitor.start":"Iniciar monitorización",
"monitor.stop":"Detener",
"monitor.stopAll":"Detener todo",
"monitor.list":"Lista",
"monitor.charts":"Gráficos",
"monitor.search":"Buscar hosts, grupos, etiquetas o notas",
'scripts.meta.descriptionPlaceholder':'Notas opcionales sobre este script',
'scripts.meta.trigger':'Disparador',
'scripts.meta.triggerPattern':'Patrón de salida (regex)',
'scripts.meta.code':'Script',
'scripts.trigger.manual':'Ejecución manual',
'scripts.trigger.onConnect':'Ejecutar al conectar',
'scripts.trigger.onOutput':'Ejecutar al coincidir con la salida',
'scripts.trigger.onOutputHint':'Se dispara cuando la salida del servidor coincide con el patrón (el eco de las teclas del usuario se ignora). Sin hosts de destino configurados, escucha en la sesión conectada actual; con hosts de destino, solo en esos hosts. Deshabilitado en aplicaciones de pantalla alterna como vim o htop. Se suprime mientras otro script se ejecuta en esta sesión; se vuelve a revisar cuando termina. Para esperas dentro del flujo, usa nct.screen.waitForText o nct.screen.waitForRegex en el código del script.',
'scripts.actions.save':'Guardar',
'scripts.actions.runNow':'Ejecutar ahora',
'scripts.actions.openEditor':'Abrir editor',
'scripts.actions.openEditorHint':'Editar el script en una ventana más grande',
'scripts.editor.modalTitle':'Editor de scripts',
'scripts.editor.modalSubtitle':'Edita los metadatos y el código del script en un espacio de trabajo más amplio.',
'scripts.editor.lineCount':'{count} líneas',
'scripts.editor.resize':'Redimensionar el editor',
'scripts.targets.hint':'Los grupos seleccionados se resuelven dinámicamente, por lo que los hosts agregados después se incluyen automáticamente.',
'scripts.targets.connectOrderHint':'El orden de ejecución de los scripts de conexión se configura por host en Detalles del host → Automatización.',
'scripts.targets.currentHostMismatch':'Este script no está asignado al host actual.',
'hostDetails.automation.groupScripts':'Scripts de grupo heredados',
'hostDetails.automation.groupScriptsHint':'Estos scripts siguen al grupo del host dinámicamente y se ordenan antes de la cola específica del host.',
'scripts.actions.runNowHint':'Ejecutar en los destinos seleccionados, o en todos los hosts conectables cuando esa opción está habilitada.',
'scripts.actions.runParallel':'Ejecutar en todas las pestañas (en paralelo)',
'scripts.actions.runSequential':'Ejecutar en todas las pestañas (en secuencia)',
'scripts.actions.runOnAllTabs':'Ejecutar en todas las pestañas',
'scripts.running.waitingForShellPrompt':'indicador del shell (# o $)',
'scripts.running.lastSent':'Enviado: {command}',
'scripts.recording.start':'Comenzar grabación',
'scripts.recording.active':'Detener grabación',
'scripts.recording.startHint':'Graba las acciones en la terminal enfocada y genera código de script nct',
'scripts.recording.unavailableHint':'La grabación no está disponible aquí — usa la barra lateral de scripts a la derecha',
'scripts.recording.activeHint':'Grabando esta terminal. Escribe comandos como de costumbre; haz clic en Detener o usa el control REC en la barra de herramientas para finalizar y guardar.',
'scripts.recording.started':'Grabación iniciada — opera en la terminal',
'scripts.recording.noSession':'Conecta una terminal primero (pestaña única o espacio de trabajo)',
'scripts.recording.alreadyActive':'Otra terminal ya está grabando',
'scripts.recording.namePlaceholder':'Nombre del script',
'scripts.recording.packagePlaceholder':'Guardar en carpeta',
'scripts.recording.rootPackage':'Raíz',
'scripts.recording.save':'Guardar',
'scripts.recording.saveAndEdit':'Guardar y editar',
'scripts.recording.helpTitle':'Cómo grabar un script',
'scripts.recording.helpIntro':'La grabación convierte lo que haces en la terminal en un script de automatización reutilizable — útil para despliegues, verificaciones de salud y otras tareas repetitivas.',
'scripts.recording.helpStep1':'Conecta a un host. Una pestaña de terminal independiente o una terminal dentro de un espacio de trabajo funcionan igual — abre la barra lateral de scripts a la derecha.',
'scripts.recording.helpStep2':'Haz clic en Comenzar grabación. Una insignia REC roja en la barra de herramientas de la terminal indica que la grabación está activa.',
'scripts.recording.helpStep3':'Escribe comandos en la terminal como lo harías normalmente. Cada pulsación de Enter se captura como un paso.',
'scripts.recording.helpStep4':'Cuando termines, haz clic en Detener grabación, o usa el control de detención junto a REC en la barra de herramientas de la terminal.',
'scripts.recording.helpStep5':'Nombra el script y guárdalo. Elige Guardar y editar si quieres ajustar el código generado en el editor de scripts.',
'scripts.recording.helpTipsTitle':'Consejos',
'scripts.recording.helpTip1':'Las pausas de más de 1 segundo entre acciones se registran como tiempo de espera para que la reproducción no sea demasiado rápida.',
'scripts.recording.helpTip2':'Después de cada comando, la grabación espera un indicador del shell (como $ o #) antes del siguiente paso.',
'scripts.recording.helpTip3':'La entrada de contraseñas se marca como sensible y no se almacena en texto plano en el script.',
'scripts.recording.helpTip4':'Para vim, menús u otros flujos interactivos, graba primero los comandos principales y luego refina el script manualmente.',
'scripts.dialog.title':'Script',
'scripts.dialog.ok':'Aceptar',
'scripts.dialog.required':'Obligatorio',
'scripts.dialog.numberInvalid':'Ingresa un número válido',
'scripts.dialog.numberMin':'Debe ser al menos {min}',
'scripts.dialog.numberMax':'Debe ser como máximo {max}',
'scripts.dialog.numberStep':'Debe usar incrementos de {step}',
'scripts.dialog.waitForTimeoutTitle':'La espera agotó el tiempo',
'hostDetails.automation.equivalenceHint':'Los vínculos agregan este host a la lista de destinos del script en Scripts. Ambas vistas se mantienen sincronizadas.',
'hostDetails.automation.queueHint':'Los scripts se ejecutan en orden cuando este host se conecta. Los scripts globales se ejecutan primero, luego esta cola.',
'hostDetails.automation.globalScripts':'Scripts de conexión globales',
'hostDetails.automation.globalScriptsHint':'Se ejecutan primero en cada conexión. Reordena según el orden de clasificación de la biblioteca de Scripts.',
'hostDetails.automation.connectQueue':'Cola de ejecución de este host',
'hostDetails.automation.connectQueueEmpty':'Aún no hay scripts de conexión en cola para este host.',
'hostDetails.automation.addToQueuePlaceholder':'Agregar script a la cola…',
'hostDetails.automation.moveUp':'Subir',
'hostDetails.automation.moveDown':'Bajar',
'hostDetails.automation.removeFromQueue':'Quitar de la cola',
'hostDetails.automation.dragHandle':'Arrastra para reordenar',
'hostDetails.automation.queueDragHint':'Arrastra los elementos para reordenar la cola de conexión.',
'systemManager.popup.startupFailed':'El comando de inicio no se completó correctamente. Verifica que el destino siga disponible e inténtalo de nuevo.',
'systemManager.errors.loadProcesses':'No se pudieron cargar los procesos',
'systemManager.errors.loadTmux':'No se pudieron cargar las sesiones de tmux',
'systemManager.errors.loadTmuxWindows':'No se pudieron cargar las ventanas de tmux',
'systemManager.errors.loadTmuxPanes':'No se pudieron cargar los paneles de tmux',
'systemManager.errors.loadTmuxClients':'No se pudieron cargar los clientes de tmux',
'systemManager.errors.loadDocker':'No se pudieron cargar los contenedores',
'systemManager.errors.loadDockerStats':'No se pudieron cargar las estadísticas de los contenedores',
'systemManager.errors.loadDockerImages':'No se pudieron cargar las imágenes',
'systemManager.errors.loadKubernetes':'No se pudieron cargar los recursos de Kubernetes',
'systemManager.errors.loadPods':'No se pudieron cargar los pods',
'systemManager.errors.loadOverview':'No se pudo cargar el resumen del sistema',
'systemManager.errors.loadGpu':'No se pudieron cargar las estadísticas de GPU / NPU',
'systemManager.errors.loadPorts':'No se pudieron cargar los puertos en escucha',
'systemManager.errors.loadServices':'No se pudieron cargar los servicios de systemd',
'systemManager.errors.sshChannelUnavailable':'El servidor se negó a abrir un nuevo canal de ejecución. Inténtalo de nuevo más tarde o vuelve a conectar este host.',
'systemManager.errors.nerdctlUnreachable':'nerdctl no puede conectarse a containerd',
'systemManager.errors.clusterUnreachable':'No se puede conectar al clúster para el contexto {{context}}',
'systemManager.errors.clusterUnreachableGeneric':'No se puede conectar al clúster',
'systemManager.unsupportedRemote':'Este host no permite canales SSH de ejecución adicionales, por lo que el administrador del sistema no está disponible. Abrir este panel ya no desconectará la terminal.',
'systemManager.overview.empty':'Aún no hay datos de resumen del sistema.',
'systemManager.overview.loading':'Cargando resumen del sistema…',
'ai.providers.style.google':'Совместимый с Google',
'ai.providers.style.inherited':'авто',
'ai.providers.style.help':'Определяет, какой формат API используется для запросов. Переопределите, если стороннее API использует другой диалект.',
'ai.providers.openaiApi':'Формат API OpenAI',
'ai.providers.openaiApi.chat':'Chat Completions',
'ai.providers.openaiApi.responses':'Responses',
'ai.providers.openaiApi.help':'Chat Completions работает с большинством OpenAI-совместимых эндпоинтов. Responses может повысить попадание в кэш на ретрансляторах с поддержкой /v1/responses.',
'ai.providers.baseUrl.anthropicHelp':'Anthropic-совместимый: хост с /v1 или без (например https://gateway.example или https://gateway.example/v1). Проверка и чат используют /v1/models и /v1/messages.',
'ai.codebuddy.envVars.hint':'По одной записи KEY=VALUE на строку, передаются агенту CodeBuddy. Укажите CODEBUDDY_API_KEY или CODEBUDDY_AUTH_TOKEN для аутентификации. Хранятся локально в открытом виде.',
'ai.codebuddy.effort.hint':'Управляет глубиной рассуждения модели. Для простых команд используйте Minimal/Low, чтобы экономить токены, а для сложной диагностики — Max/High.',
'ai.codebuddy.maxTurns':'Максимум шагов',
'ai.codebuddy.maxTurns.hint':'Ограничивает максимальное число шагов диалога за запрос, предотвращая зацикливание. Оставьте пустым для значения по умолчанию.',
'ai.codebuddy.maxBudget':'Максимальный бюджет (USD)',
'ai.codebuddy.maxBudget.hint':'Максимальные расходы (USD) за запрос. При превышении выполнение останавливается автоматически. Оставьте пустым, чтобы не ограничивать.',
'ai.codebuddy.sandbox':'Режим песочницы',
'ai.codebuddy.sandbox.hint':'Выполняет вызовы инструментов в песочнице, ограничивая доступ к файловой системе и сети.',
'ai.codebuddy.persistSession.hint':'Сохраняет на диск собственную историю сессии CodeBuddy, чтобы CodeBuddy мог возобновить её позже. Отключите, чтобы запретить хранение сессии CodeBuddy; Netcatty всё ещё может сохранять историю чата и метаданные внешней сессии. Контрольные точки файлов пропускаются. Требуется CodeBuddy CLI 2.125.1 или новее.',
'ai.codebuddy.fileCheckpointing':'Контрольные точки файлов',
'ai.codebuddy.fileCheckpointing.hint':'Включает контрольные точки файловых операций, чтобы изменения файлов ИИ можно было откатить.',
'ai.codebuddy.elicitation.title':'CodeBuddy требуется ваш ответ',
'ai.codebuddy.elicitation.description':'Проверьте запрос, чтобы продолжить текущий ход.',
'ai.toolAccess.description':'Выберите, как внешние агенты получают доступ к сессиям Netcatty. MCP предоставляет встроенный сервер, а Skills + CLI указывает агентам на локальный skill Netcatty и команды CLI.',
'ai.toolAccess.mode.mcp':'MCP',
'ai.toolAccess.mode.skills':'Skills + CLI',
'ai.toolAccess.mcpPrompt.title':'Промпт для вашего ИИ-клиента',
'ai.toolAccess.mcpPrompt.description':'Вставьте этот промпт в ИИ-клиент (Codex, Claude Code и т. д.), и он сам зарегистрирует MCP Netcatty.',
'ai.toolAccess.mcpPrompt.enableHint':'Включите внешний MCP ниже, чтобы в промпт попал путь к лаунчеру.',
'ai.toolAccess.skills.file':'Файл skill',
'ai.toolAccess.skills.description':'В режиме Skills + CLI агенты автоматически получают путь к этому локальному skill-файлу. Путь к лаунчеру Netcatty CLI передаётся агенту в каждой сессии.',
'ai.toolAccess.skills.unavailable':'Путь к skill-файлу недоступен',
'ai.externalMcp.title':'Внешний MCP',
'ai.externalMcp.allowCommandOnHost':'Всегда',
'ai.externalMcp.allowCommandOnHostHint':'Всегда разрешать это действие на этом сервере.',
'ai.externalMcp.allowAll':'Разрешить все действия',
'ai.externalMcp.allowSession':'Это подключение',
'ai.externalMcp.allowSessionHint':'Разрешать все действия клиента до отключения.',
'ai.externalMcp.allowHost':'Этот сервер',
'ai.externalMcp.allowHostHint':'Всегда разрешать действия на этом сервере.',
'ai.externalMcp.description':'Откройте Netcatty как MCP-сервер для Codex, Claude Code, Cursor и Grok. Набор инструментов тот же, что у встроенных агентов. Держите Netcatty запущенным, пока клиенты подключены.',
'ai.externalMcp.sessionsExposed':'Сессий в области: {count}',
'ai.externalMcp.focusOnHostOpen':'Активировать окно при host_open',
'ai.externalMcp.focusOnHostOpen.description':'Когда MCP-клиент открывает хост, выводить главное окно на передний план. Отключите, чтобы работать без прерываний.',
'ai.externalMcp.approvalAlert':'Оповещения об одобрении',
'ai.externalMcp.approvalAlert.description':'Когда внешний MCP-клиент запрашивает подтверждение, показать системное уведомление. Пока Netcatty не в фокусе, панель задач или Dock привлекают внимание. Уведомление использует системный звук.',
'ai.externalMcp.silentSessions.description':'Сеансы, открытые ИИ, не отображаются на панели вкладок и не восстанавливаются после перезапуска. Их можно посмотреть в любой момент через панель в трее.',
'ai.externalMcp.security.description':'Слушает только 127.0.0.1 с ротацией token, использует AI Permission Mode для записи и удаляет discovery при отключении.',
"ai.privacy.title":"Конфиденциальность ИИ",
"ai.privacy.description":"Фильтрует известные шаблоны конфиденциального текста в контексте и результатах инструментов, управляемых Netcatty, для Catty, встроенных агентов и внешних MCP-клиентов. По умолчанию отключено. Не обнаруживает все секреты. При включении двоичные ответы инструментов блокируются.",
"ai.privacy.boundary":"Защищаются только данные и инструменты, управляемые Netcatty. Собственные инструменты сторонних агентов, их прямой доступ к файлам и другие MCP-серверы не входят в эту область.",
"ai.privacy.patterns.description":"Один RE2-совместимый шаблон на строку, без разделителей / и флагов. Неподдерживаемые или небезопасные шаблоны отклоняются.",
"ai.privacy.connectionScope":"Маркеры постоянны в пределах одного управляемого диалога или внешнего MCP-подключения. Исходные значения восстанавливаются только через вызовы инструментов, управляемых Netcatty, в той же области.",
"ai.privacy.reconnect":"Примените изменения, начните новый управляемый диалог и переподключите внешние MCP-клиенты. Существующие диалоги и подключения не должны повторно использовать маркеры.",
"ai.privacy.apply":"Применить",
"ai.privacy.applying":"Применение...",
"ai.privacy.applied":"Настройки конфиденциальности ИИ применены. Начните новый управляемый диалог и переподключите внешние MCP-клиенты.",
"ai.privacy.failed":"Настройки конфиденциальности ИИ не применены.",
'ai.externalMcp.http.title':'Streamable HTTP',
'ai.externalMcp.http.description':'Отдает те же инструменты External MCP по Streamable HTTP клиентам, которые не могут запустить stdio CLI, например Codex в WSL. По умолчанию выключено.',
'ai.externalMcp.http.enable':'Включить HTTP',
'ai.externalMcp.http.port':'Порт',
'ai.externalMcp.http.portHint':'Фиксированный порт, по умолчанию 47391. URL клиентов не меняется после перезапуска. Допустимый диапазон: 1024–65535.',
'ai.externalMcp.http.allowCustomBind':'Слушать другой адрес',
'ai.externalMcp.http.allowCustomBind.description':'По умолчанию endpoint привязан к 127.0.0.1. Включите для WSL2 NAT и укажите 0.0.0.0 или IP vEthernet.',
'ai.externalMcp.http.clientHost.description':'Подставляется в копируемые конфиги, если слушатель 0.0.0.0 или ::. Укажите доступный IP, например IP узла Windows.',
'ai.externalMcp.http.clientHostRequired':'Укажите IP, к которому должны подключаться клиенты. 0.0.0.0 недоступен для подключения.',
'ai.externalMcp.http.invalidClientHost':'Адрес клиента должен быть доступным IP, а не 0.0.0.0 или ::.',
'ai.externalMcp.http.warning':'Прослушивание не только 127.0.0.1 открывает External MCP другим машинам, которые могут достичь этого адреса. Храните bearer token в секрете и не публикуйте этот порт.',
'ai.externalMcp.http.regenerateHint':'Выпускает новый bearer token. HTTP-клиенты должны использовать новый token. Файл discovery для stdio обновляется; уже подключенные stdio-клиенты остаются подключенными.',
'ai.externalMcp.http.wslHint':'При mirrored networking в WSL достаточно localhost. Иначе слушайте 0.0.0.0 или IP vEthernet и укажите IP узла Windows как адрес клиента.',
'ai.externalMcp.http.envLine':'Переменная окружения token для Codex',
'ai.externalMcp.http.notListening':'Не слушает',
'ai.externalMcp.http.listening':'Слушает',
'ai.externalMcp.http.portInUse':'Порт {port} уже занят.',
'ai.externalMcp.http.requiresExternalMcp':'Сначала включите External MCP, чтобы endpoint начал слушать.',
'ai.externalMcp.http.invalidPort':'Введите порт от 1024 до 65535.',
'ai.externalMcp.http.invalidAddress':'Адрес прослушивания должен быть IP, например 0.0.0.0 или IP vEthernet.',
'ai.externalMcp.http.listenFailed':'Не удалось начать прослушивание HTTP.',
'ai.externalMcp.http.legacySseNote':'Устаревший HTTP+SSE (2024-11-05) не предоставляется. Используйте этот URL Streamable HTTP.',
'ai.externalMcp.permissionMode':'Текущий режим разрешений: {mode}',
'ai.externalMcp.permissionMode.label':'Режим разрешений на запись',
'ai.externalMcp.permissionMode.hint':'Тот же параметр, что Settings → AI → Safety. Auto выполняет write-инструменты Netcatty без запросов; Confirm спрашивает каждый раз. Внешние клиенты (Codex / Claude / Grok) могут показывать своё подтверждение инструментов.',
'ai.externalMcp.addToClaude':'Добавить в Claude Code',
'ai.externalMcp.addToGrok':'Добавить в Grok',
'ai.externalMcp.codexAdded':'Запись MCP для Codex добавлена. Перезапустите Codex или откройте новую сессию.',
'ai.externalMcp.claudeAdded':'Запись MCP для Claude Code добавлена. Перезапустите Claude Code или откройте новую сессию.',
'ai.externalMcp.grokAdded':'Запись MCP для Grok добавлена. Перезапустите Grok или откройте новую сессию.',
'ai.externalMcp.codexAdded':'Запись MCP для Codex и навык Netcatty добавлены. Перезапустите Codex или откройте новую сессию.',
'ai.externalMcp.claudeAdded':'Запись MCP для Claude Code и навык Netcatty добавлены. Перезапустите Claude Code или откройте новую сессию.',
'ai.externalMcp.grokAdded':'Запись MCP для Grok и навык Netcatty добавлены. Перезапустите Grok или откройте новую сессию.',
'ai.externalMcp.installCodex':'Сначала установите Codex, затем нажмите Обновить.',
'ai.externalMcp.installClaude':'Сначала установите Claude Code, затем нажмите Обновить.',
'ai.externalMcp.installGrok':'Сначала установите Grok CLI, затем нажмите Обновить.',
'ai.externalMcp.conflict.description':'Запись netcatty-external уже существует и указывает в другое место. Удалите или измените её вручную.',
'ai.externalMcp.enableForLauncher':'Включите External MCP, чтобы получить путь launcher.',
'ai.externalMcp.cursor.title':'Cursor / другие клиенты',
'ai.externalMcp.cursor.description':'Объедините с MCP-конфигом (например ~/.cursor/mcp.json). Не заменяйте весь файл, если там уже есть другие серверы.',
'ai.externalMcp.cursor.title':'Cursor',
'ai.externalMcp.cursor.description':'Добавьте этот фрагмент в конфигурацию MCP Cursor, сохранив существующие серверы.',
'ai.externalMcp.other.description':'Скопируйте запрос установки в другой терминальный агент или добавьте фрагмент MCP в конфигурацию его клиента.',
'ai.chat.undoLastTurnNotice':"Исходный чат сохранён. Команды и изменения файлов не отменяются. Полные сохранённые результаты и фоновые задачи доступны в исходном чате.",
'ai.chat.allSessions':'Все сессии',
'ai.chat.loadEarlierMessages':'Загрузить более ранние сообщения (ещё {n})',
'ai.chat.searchSessionsHint':'Поиск по заголовкам с приоритетом более новых сообщений и записей. В длинных сообщениях и результатах инструментов поиск охватывает только ограниченную начальную часть. Это не полнотекстовый поиск.',
'ai.chat.searchingSessions':'Поиск...',
'ai.chat.noMatchingSessions':'Нет совпадений в заголовках и недавнем сохранённом содержимом',
'ai.chat.noSessions':'Предыдущих сессий нет',
'ai.chat.forkFromHere':'Ветвление отсюда',
'ai.chat.forkCreated':'Разговор перенесён в новый чат; оригинал остаётся в истории.',
'ai.chat.retryHint':'Вы можете повторить попытку, отправив сообщение ещё раз.',
'ai.chat.approvalTimeout':'Время ожидания одобрения инструмента истекло через 5 минут. Вы можете повторить попытку, отправив сообщение ещё раз.',
'ai.safety.commandTimeout.description':'Максимальное число секунд, которое команда может выполняться до принудительного завершения. Применяется как к встроенным, так и к внешним агентам.',
'ai.safety.responseIdleTimeout.description':'Отменяет запрос встроенного ИИ после указанного числа секунд без нового ответа. Не управляет общей длительностью ответа и выполнением команд.',
'ai.safety.responseIdleTimeout.unit':'с',
'ai.safety.maxIterations':'Макс. число итераций',
'ai.safety.maxIterations.description':'Максимальное число циклов использования инструментов AI, чтобы предотвратить бесконтрольное выполнение. У внешних агентов могут быть собственные внутренние лимиты итераций, имеющие приоритет.',
'placeholder.workspaceName':'Имя рабочего пространства',
'placeholder.sessionName':'Имя сессии',
'placeholder.searchHosts':'Поиск хостов...',
'placeholder.searchHosts':'Поиск хостов или тегов...',
'toast.settingsUnavailable':'Окно настроек недоступно на этой платформе.',
'credentials.protectionUnavailable.title':'Защита учётных данных недоступна',
'credentials.protectionUnavailable.message':'Сохранённые пароли и ключи не могут быть автоматически расшифрованы на этом устройстве. Перед подключением введите учётные данные заново.',
'settings.system.credentials.unavailableHint':'Учётные данные, зашифрованные в другом профиле пользователя или на другой машине, здесь расшифровать нельзя. Повторно введите и сохраните их на этом устройстве.',
'settings.system.credentials.portabilityHint':'Облачная синхронизация переносима, потому что использует шифрование вашим мастер-ключом. Локальное шифрование safeStorage привязано к устройству и пользователю.',
// Settings > System > App Lock
'settings.appLock.title':'Блокировка приложения',
'settings.appLock.description':'Требовать локальный пароль блокировки при открытии Netcatty, после бездействия или при ручной блокировке.',
'settings.appLock.disableDescription':'Введите текущий пароль блокировки, чтобы Netcatty перестал блокироваться. Сохранённый пароль будет удалён; для повторного включения нужно создать новый.',
'settings.globalHotkey.toggleWindowDesc':'Нажмите сочетание клавиш, чтобы задать глобальную горячую клавишу для показа или скрытия окна.',
'settings.globalHotkey.notSet':'Не задано',
'settings.globalHotkey.reset':'Сбросить по умолчанию',
'settings.globalHotkey.closeToTray':'Сворачивать в системный трей',
'settings.globalHotkey.closeToTrayDesc':'Если включено, при закрытии окно будет сворачиваться в системный трей вместо выхода из приложения.',
'settings.globalHotkey.closeToTray':'Оставлять приложение в фоне при закрытии окна',
'settings.globalHotkey.closeToTrayDesc':'При закрытии окно скрывается, а приложение продолжает работать. Отображение значка в трее настраивается отдельно.',
'settings.globalHotkey.showTrayIcon':'Показывать значок в системном трее',
'settings.globalHotkey.showTrayIconDesc':'Если отключено, значок в трее скрыт, но приложение продолжает работать в фоне.',
'settings.globalHotkey.showTrayIconHiddenHint':'Значок в трее скрыт. Запустите приложение снова или используйте глобальную горячую клавишу, чтобы вернуть окно, затем снова включите этот параметр, чтобы восстановить значок.',
'settings.globalHotkey.enabledDesc':'Регистрировать системные сочетания клавиш. Когда отключено, все глобальные горячие клавиши снимаются с регистрации.',
'settings.globalHotkey.hint':'Глобальная горячая клавиша работает на уровне всей системы и позволяет быстро показывать или скрывать окно (терминал в стиле Quake).',
'settings.vault.autoImportSystemKnownHostsDesc':'При открытии хранилища тихо импортировать записи из OpenSSH known_hosts. Отключите, чтобы держать известные хосты Netcatty отдельно; кнопка «Сканировать систему» по-прежнему импортирует вручную.',
'settings.vault.notesFont':'Шрифт кода',
'settings.vault.notesFontDesc':'Шрифт для блоков кода и встроенного кода в заметках',
'Добавьте пользовательский CSS, чтобы настроить внешний вид. Изменения применяются сразу.',
'Добавьте пользовательский CSS, чтобы настроить внешний вид. Изменения применяются сразу. Выбранные верхние вкладки (`.top-tab[data-active="true"]`) читают `--top-tab-active-bg`, `--top-tab-active-fg` и `--top-tab-active-border`. Не задавайте их, чтобы сохранить текущий вид.',
'settings.appearance.customCss.help.ariaLabel':'Справка по пользовательскому CSS',
'settings.appearance.customCss.help.title':'Руководство по пользовательскому CSS',
'settings.appearance.customCss.help.body':`Основные области интерфейса помечены атрибутом \`data-section\` для CSS-селекторов. Изменения применяются сразу после правки.
Увыбранныхверхнихвкладокестькласс\`top-tab\` и атрибут \`data-active="true"\`. Задайте эти переменные, чтобы перекрасить выбранную вкладку. Не задавайте их, чтобы сохранить текущий вид. \`!important\` не нужен.
-\`--top-tab-active-bg\` — заливка выбранной вкладки (по умолчанию текущий фон вкладки)
-\`--top-tab-active-fg\` — цвет подписи (по умолчанию текущий цвет текста)
-\`--top-tab-active-border\` — внутренняя обводка 1px (по умолчанию прозрачная, то есть без рамки)
'settings.appearance.windowOpacity.desc':'Настройте прозрачность всего окна приложения. При низких значениях текст терминала тоже бледнеет. В некоторых средах Linux это может не поддерживаться.',
'settings.appearance.windowBackgroundBlur.desc':'Размывает рабочий стол за окном. При непрозрачности 100 % эффект не виден, но ваш выбор сохраняется.',
'settings.appearance.appIcon.desc':'Выберите стиль значка для Dock/панели задач, чтобы Netcatty было проще найти при переключении приложений. Применяется во время работы; значок установленного пакета не меняется.',
'settings.appearance.appIcon.desc':'Выберите стиль значка для Dock/панели задач, чтобы Netcatty было проще найти при переключении приложений. В Windows значок области уведомлений использует тот же стиль. Применяется во время работы; значок установленного пакета не меняется.',
'Подстраивать цвета под требования контрастности (1 = отключено, 21 = максимум)',
'settings.terminal.behavior.rightClick':'Поведение правой кнопки мыши',
'settings.terminal.behavior.rightClick.desc':'Действие при щелчке правой кнопкой в терминале',
'settings.terminal.behavior.rightClick.longPressMenu':'Удержание правой кнопки открывает меню',
'settings.terminal.behavior.rightClick.longPressMenu.desc':'Если правая кнопка вставляет текст или выделяет слово, удерживайте её полсекунды для открытия меню. Не влияет на приложения, захватывающие мышь.',
'Показывать контекстное меню, даже когда tmux, vim или другие полноэкранные приложения перехватывают мышь. Когда выключено, правый щелчок передаётся приложению (Shift+правый щелчок всё равно открывает меню).',
'settings.terminal.behavior.autoCloseOnExit':'Автоматически закрывать терминал при выходе',
'Показывать компактное уведомление внутри уже подключённого терминала или полный диалог. Ошибки первого подключения по-прежнему показываются в диалоге.',
'Когда в буфере обмена есть изображение, вставка в удалённой сессии загружает его по SFTP в каталог .netcatty-paste-images/ в текущем удалённом каталоге и вводит удалённый путь вместо вставки текста.',
'settings.terminal.behavior.confirmMultilinePaste':'Спрашивать перед вставкой нескольких строк',
'Используйте \\n для перевода строки, \\t для табуляции и \\\\ для обратной косой черты.',
'Используйте \\n для перевода строки, \\t для табуляции, \\e для Escape и \\\\ для обратной косой черты. По умолчанию \\e\\r (Escape + Enter, классическая кодировка Alt+Enter), которую большинство TUI-CLI распознают как перевод строки.',
'settings.terminal.behavior.shiftEnterForceText':'Отправлять текст и в режиме ввода Win32',
'Отправлять указанный выше текст даже когда локальная сессия Windows ConPTY согласует режим ввода Win32. Нужно для CLI на Node/Bun (Claude Code, CodeBuddy), которые не читают модификаторы из записей ввода консоли.',
'Показывать системное уведомление, когда программа отправляет OSC 9, OSC 777 notify или kitty OSC 99. Так Codex и другие агенты сообщают о завершении хода и запросе подтверждения.',
'settings.terminal.connection.temporaryLegacyAlgorithms':'Устаревшие алгоритмы для временных подключений',
'settings.terminal.connection.temporaryLegacyAlgorithms.desc':'SSH-подключения без сохранённого хоста, включая запуск в стиле PuTTY и быстрое подключение, предлагают те же устаревшие алгоритмы, что и настройка хоста. Сохранённые хосты используют свою настройку. По умолчанию выключено: у этих алгоритмов есть известные слабости.',
'settings.terminal.connection.temporaryNetworkDeviceMode':'Режим сетевого устройства для временных подключений',
'settings.terminal.connection.temporaryNetworkDeviceMode.desc':'SSH-подключения без сохранённого хоста, включая внешний запуск и быстрое подключение, открываются в режиме сетевого устройства. Сохранённые хосты и группы сохраняют свой тип устройства. По умолчанию выключено.',
'settings.terminal.connection.keepaliveInterval.desc':'Как часто (в секундах) отправлять keepalive-пакеты на уровне SSH. Установите 0, чтобы отключить глобально. Учтите, что отдельные хосты могут переопределять это значение в своих настройках.',
'settings.terminal.connection.keepaliveCountMax':'Макс. число пропущенных keepalive',
'settings.terminal.connection.x11Display.desc':'Необязательный адрес локального дисплея для перенаправления X11. Оставьте пустым, чтобы использовать системное значение по умолчанию.',
'settings.terminal.connection.x11Display.placeholder':'Авто (:0 или DISPLAY)',
'settings.terminal.connection.defaultProxy':'Прокси по умолчанию',
'settings.terminal.connection.defaultProxy.desc':'Сохраненный прокси, если у хоста нет своего и он не наследует прокси группы. «Нет» — прямое подключение.',
'settings.terminal.hostInfoBar.show':'Показывать сведения о хосте',
'settings.terminal.hostInfoBar.show.desc':'Показывать адрес хоста и сведения о сервере над терминалом. Кнопки действий остаются доступными после скрытия.',
'vault.groups.deleteDialog.desc':'Группа будет безвозвратно удалена, а все хосты будут перемещены в корень.',
'vault.groups.deleteDialog.managedDesc':'Это управляемая группа SSH-конфига. При её удалении также будут удалены все хосты и снята связь с исходным файлом.',
'vault.groups.deleteDialog.mixedDesc':'Эта группа содержит хосты, импортированные из связанного SSH-конфига. Все хосты под управляемой подгруппой будут удалены, включая хосты, не управляемые этим файлом. Остальные хосты будут перемещены в корень, если вы также не удалите их ниже.',
'vault.groups.deleteDialog.managedWarning':'При удалении этой группы её записи также будут удалены из связанного файла конфигурации SSH. Записи, импортированные из этого файла, могут быть утеряны, это затронет и другие приложения, использующие файл. Сделайте резервную копию файла перед продолжением.',
'vault.groups.deleteDialog.sourcesChanged':'Управляемые файлы, затрагиваемые этим удалением, изменились. Проверьте файлы и подтвердите удаление снова.',
"settings.plugins.manager.removeNote":"Удаление очищает код пакета и локальные данные KV. Настройки пользователя, секреты и данные синхронизации сохраняются.",
"plugins.interaction.sharing":"Переданный текст доступен плагину и его разрешённым возможностям, включая сеть. Чтение не разрешает новый сетевой адрес.",
"plugins.interaction.copy":"Копировать в буфер обмена",
"plugins.interaction.failed":"Запрос истёк или не выполнен.",
"plugins.command.failed":"Не удалось выполнить команду плагина. Проверьте необходимые выбранные объекты и разрешения.",
"plugins.interaction.terminalDisclosure":"Only the current selection from this terminal is shared, up to 4096 UTF-8 bytes. Terminal text can contain passwords, tokens, and other secrets. Inspect the exact preview before sharing. No redaction guarantee is made.",
"plugins.interaction.draftNote":"Editable draft for review only. This dialog sends no terminal input or Enter. Copy text yourself and use the normal terminal UI if you choose to send it. Your edits stay in this dialog.",
"settings.plugins.permissions.audit":"Последние события безопасности (до 100)",
"settings.plugins.permissions.revoke":"Отключить и отозвать все разрешения",
"settings.plugins.permissions.revokeNote":"Плагин будет отключён, его задачи отменены, а сохранённые разрешения удалены. Включите его для нового запроса доступа.",
"settings.plugins.manager.permissionNote":"Заявленные поддерживаемые возможности по умолчанию разрешены. Для каждой можно выбрать запрос или запрет в настройках разрешений. Проверки приложения и область ресурсов действуют всегда.",
"settings.plugins.manager.noPermissions":"Привилегированные разрешения не заявлены.",
"settings.plugins.themes.description":"Предпросмотр меняет только это окно. Применение сохраняет выбор на этом устройстве. Личные настройки сохраняются.",
"settings.plugins.themes.empty":"Нет включённых тем для этого режима.",
"settings.plugins.themes.choose":"Выберите тему",
"settings.plugins.themes.unavailable":"Сохранённая тема недоступна; используется стандартная палитра",
"settings.plugins.themes.invalid":"Некоторые палитры недоступны или отклонены.",
"settings.plugins.themes.failed":"Не удалось применить тему.",
"settings.plugins.manager.dataTheme":"Пакет тем: проверенные цвета без выполнения кода.",
"settings.plugins.manager.browser":"Браузерная среда: привилегированный доступ проходит через проверку разрешений приложения.",
"settings.plugins.manager.advanced":"Расширенная среда: полный доступ Node к файловой системе и сети. Разрешения посредника не ограничивают этот доступ. Только для разработки.",
"settings.plugins.manager.data":"Данные KV остаются на устройстве. Синхронизируются только явно отмеченные несекретные настройки в зашифрованных данных. Пакеты и разрешения не синхронизируются.",
"settings.plugins.sources.offline":"Источник недоступен. Ранее проверенный каталог этого источника, если он есть в кэше, доступен только для просмотра. Установка недоступна до успешного обновления.",
"settings.plugins.sources.note":"Этот каталог предоставляет выбранная третья сторона; Netcatty не отбирает его содержимое. Перед установкой проверьте домен источника и разрешения. Обновления выполняются вручную.",
"settings.plugins.sources.sourceOnly":"Запись с исходным кодом от этого поставщика. Пакет для загрузки отсутствует; см. инструкции автора по сборке и импорту.",
"settings.plugins.market.offline":"Реестр недоступен. Показан последний корректный каталог или встроенные примеры. Для установки необходимо успешно обновить каталог.",
"settings.plugins.manager.details":"Подробнее",
"settings.plugins.manager.noDescription":"Описание не предоставлено.",
"settings.plugins.market.note":"Отобранные плагины с открытым кодом. Проверка не гарантирует безопасность. Обновления выполняются вручную.",
"settings.plugins.market.back":"Все плагины",
"settings.plugins.market.install":"Установить эту версию (отключённой)",
"settings.plugins.market.installNote":"Установленная версия будет заменена и остановлена. Проверьте разрешения перед включением.",
"settings.plugins.market.sourceOnly":"Пример из этого проекта. Соберите локальной CLI и импортируйте .ncpkg; готовый пакет для скачивания не опубликован.",
"settings.plugins.market.readmeNote":"Зафиксирован указанный коммит. HTML не выполняется, внешние изображения не загружаются. Текст автора плагина.",
"settings.plugins.manager.noVault":"Системное хранилище ключей недоступно. Операции с секретами и учётными данными заблокированы; каталог и плагины без секретов доступны.",
"settings.plugins.manager.sensitive":"Чувствительный доступ: эта возможность может раскрыть личные данные. Её режим можно изменить в настройках разрешений.",
"settings.plugins.manager.dual":"По умолчанию используется браузерная среда. Пакет также содержит вход Node с полным доступом к устройству; только для разработчиков.",
"settings.plugins.manager.advancedDeclaration":"Браузерная точка входа с заявленными расширенными разрешениями. Не допускается в обычном режиме; проверьте все разрешения.",
"settings.plugins.market.retry":"Повторить",
"settings.plugins.market.empty":"Каталог пуст. Можно импортировать локальный браузерный плагин.",
"settings.plugins.market.search":"Поиск по имени, автору или описанию",
"settings.plugins.market.noResults":"Совпадений нет. Измените запрос.",
"settings.plugins.market.manifest":"Совместимость и разрешения",
"settings.plugins.manager.unbounded":"Более узкая область ресурсов не заявлена. Проверки приложения и выбранный режим разрешений продолжают действовать.",
"settings.plugins.openView":"Открыть",
"settings.plugins.openManager":"Открыть управление плагинами",
"settings.plugins.sources.conflict":"This ID is installed from another source. Uninstall and explicitly reset its private data before switching. Existing source:",
"settings.plugins.sources.clearPrivate":"Also clear this source’s private data, grants and secrets to reinstall from another source. This cannot be undone.",
"settings.plugins.sources.risk":"Third-party sources are not maintained or endorsed by Netcatty. Review the real domain, package permissions and author before installing.",
"settings.plugins.sources.accept":"I trust this domain to supply plugin metadata and packages. This does not grant plugin permissions.",
"settings.plugins.sources.removeNote":"Installed plugins and their data remain. Updates from this source stop; another source will not replace it.",
"settings.plugins.sources.disablePlugins":"Also disable plugins installed from this source",
'plugins.hostActions.selectionNote':'Передаются только метаданные выбранных хостов: сохранённые имена, адреса, пользователи, протоколы, группы и теги. Группа включает только выбранные сейчас хосты. Дальнейшие запросы изменения или подключения зависят от настроек разрешений плагина.',
'plugins.hostActions.fieldDiff':'Изменения для сохранения',
'plugins.hostActions.defaultsWarning':'Применяются сохранённые настройки подключения. Смена группы может изменить унаследованные настройки входа, прокси и скрипты.',
'plugins.hostActions.invalidDraft':'Введите корректные данные хоста.',
'plugins.hostActions.scriptsRun':'При подключении могут выполняться сохранённые скрипты запуска, подключения или вывода.',
'plugins.hostActions.noScripts':'Для этого хоста не настроены сохранённые скрипты запуска, подключения или вывода.',
'plugins.hostActions.connectionNote':'Netcatty откроет подключение с сохранёнными настройками. Содержимое сеанса не передаётся плагину.',
'plugins.hostActions.localExecution':'Локальное выполнение: откроется оболочка на этом устройстве.',
'plugins.hostActions.managedUnsupported':'Изменяйте управляемые настройки SSH в редакторе хостов Vault.',
'plugins.hostActions.failed':'Запрос истёк, был отозван или хост изменился. Выберите хост заново.',
'plugins.hostActions.selectionLimit':'Выберите не более 50 текущих хостов.',
"monitor.diskIoHint":"Суммарный ввод-вывод физических блочных устройств Linux, без разделов и виртуальных слоёв. Доступен после двух корректных замеров; macOS не поддерживается.",
"monitor.actions":"Действия панели",
"monitor.unavailable":"Мониторинг недоступен. Запустите его снова после восстановления связи.",
"monitor.username":"Пользователь",
"monitor.password":"Пароль (только для этого запуска)",
"monitor.status.limit":"Одновременно можно наблюдать не более 256 серверов.",
'systemManager.errors.loadDocker':'Не удалось загрузить контейнеры',
'systemManager.errors.loadDockerStats':'Не удалось загрузить статистику контейнеров',
'systemManager.errors.loadDockerImages':'Не удалось загрузить образы',
'systemManager.errors.loadKubernetes':'Не удалось загрузить ресурсы Kubernetes',
'systemManager.errors.loadPods':'Не удалось загрузить поды',
'systemManager.errors.loadOverview':'Не удалось загрузить обзор системы',
'systemManager.errors.loadGpu':'Не удалось загрузить статистику GPU / NPU',
'systemManager.errors.loadPorts':'Не удалось загрузить слушающие порты',
'systemManager.errors.loadServices':'Не удалось загрузить службы systemd',
'systemManager.errors.sshChannelUnavailable':'Сервер отказался открыть новый канал выполнения. Повторите попытку позже или переподключите этот хост.',
'systemManager.unsupportedRemote':'Этот хост не разрешает дополнительные SSH exec-каналы, поэтому системная панель недоступна. Открытие панели больше не разрывает терминал.',
'systemManager.overview.empty':'Данных обзора пока нет.',
'terminal.dragDrop.errorMessage':'Не удалось обработать перетащенные файлы',
'terminal.dragDrop.destinationUnknown':'Не удалось определить текущую папку терминала. Включите отслеживание каталогов или сначала откройте SFTP и выберите папку загрузки.',
'terminal.dragDrop.uploadCancelled':'Загрузка отменена: соединение терминала изменилось или его не удалось повторно использовать. После переподключения перетащите файлы снова.',
'terminal.dragDrop.needsSudoElevation':'Эта папка недоступна для записи пользователю входа. Включите повышение прав Sudo в настройках хоста или вернитесь в домашний каталог и перетащите файлы снова.',
'terminal.auth.continueSave':'Продолжить и сохранить',
'terminal.auth.credentialsUnavailable':'Сохранённые учётные данные не могут быть расшифрованы на этом устройстве. Пожалуйста, введите и сохраните их заново.',
'terminal.auth.jumpCredentialsUnavailable':'У jump-хоста сохранены учётные данные, которые нельзя расшифровать на этом устройстве. Откройте настройки хоста и введите их заново.',
'cloudSync.provider.disconnect.confirmMessage':'Это устройство перестанет синхронизироваться с {name}. Сохранённый адрес и данные для входа будут удалены. Локальное хранилище, резервные копии и пароль шифрования синхронизации останутся здесь. Если системное хранилище учётных данных недоступно, данные для входа плагинов может потребоваться удалить вручную.',
'cloudSync.provider.disconnect.cloudRemains':'Зашифрованные копии синхронизации могут остаться в хранилище {name}. Чтобы удалить их, удалите там файлы синхронизации. Отключение не очищает устройство для передачи другому человеку.',
'cloudSync.provider.disconnect.failed':'Не удалось завершить отключение от {name}. Сохранённые данные подключения могут остаться. Повторите попытку.',
'cloudSync.provider.webdav':'WebDAV',
'cloudSync.provider.webdav.desc':'Подключение к самостоятельно размещённому WebDAV endpoint',
'cloudSync.strategy.desc':'Выберите, что делать, когда изменились и локальные, и облачные данные.',
'cloudSync.strategy.smartMerge':'Умное объединение (рекомендуется)',
'cloudSync.aiSessions.title':'Синхронизировать историю ИИ-чатов',
'cloudSync.aiSessions.desc':'Включать сессии ИИ-чатов в облачную синхронизацию. Сообщения объединяются дополнением между устройствами; содержимое вложений остаётся на этом устройстве.',
'cloudSync.aiSessions.compatibility':'Старые версии могут перезаписать историю. Обновите Netcatty на всех синхронизируемых устройствах.',
'cloudSync.aiSessions.enableFailed':'Не удалось изменить синхронизацию чатов. Проверьте подключение к облаку и свободное место на устройстве, затем повторите попытку.',
'cloudSync.strategy.smartMergePreserveDesc':'Объединяет изменения с обеих сторон и никогда не передаёт удаление с одной стороны: запись сохраняется, пока её не убрали на всех устройствах. Рекомендуется при восстановлении из резервных копий или при работе с несколькими машинами.',
'cloudSync.strategy.smartMergeDesc':'По возможности объединять изменения с обеих сторон; если Netcatty не сможет безопасно выбрать, он попросит вас решить вручную.',
'cloudSync.localBackups.clearBusy':'Завершите восстановление хранилища перед удалением резервных копий.',
'cloudSync.localBackups.clear':'Удалить все копии',
'cloudSync.localBackups.clearConfirmTitle':'Удалить историю локальных копий?',
'cloudSync.localBackups.clearConfirmDesc':'Все локальные резервные копии хранилища на этом устройстве будут удалены безвозвратно. Текущие хосты, сохранённые учётные данные и облачные копии останутся. Это не очистка устройства для передачи другому человеку. При обычном использовании новые копии будут создаваться по-прежнему.',
"fido.recovery.timeout.desc":"Операция устройства остановлена. Проверьте подключение ключа, повторите попытку и подтвердите её на устройстве.",
"fido.recovery.pin-failed.title":"PIN ключа безопасности не принят",
"fido.recovery.pin-failed.desc":"Проверьте PIN FIDO2 этого устройства. Повторные ошибки могут заблокировать устройство.",
"fido.recovery.failed.title":"Проверка ключа не завершена",
"fido.recovery.failed.desc":"Проверьте подключение нужного устройства и поддержку ключа. Повторите попытку или отмените её и проверьте настройки OpenSSH.",
'serial.noPorts':'Последовательные порты не обнаружены. Подключите устройство и обновите список.',
'serial.field.customPort':'Путь к пользовательскому порту',
'serial.field.namedPipeHint':'Консоль ВМ Windows: введите \\\\.\\pipe\\name. ВМ должна создать канал как сервер. Скорость и параметры кадра игнорируются.',
'serial.field.customPortPlaceholder':'например, /dev/ttys001 или COM1',
'serial.field.byteOrientedBackspaceDesc':'Отправляет по одному DEL на байт, чтобы устройство удалило весь многобайтовый символ (для устройств, удаляющих побайтово). Отключите для редакторов, удаляющих посимвольно (например, readline в Linux).',
'serial.field.autoLoginDesc':'Если задано, запросы Login/Password в последовательной консоли автоматически заполняются этими сохранёнными учётными данными.',
'terminal.browser.bookmarkTitlePlaceholder':'Например, Документы команды',
'terminal.browser.websiteAddress':'Адрес сайта',
'terminal.browser.hostScopeHint':'Только в браузере текущего хоста.',
'terminal.browser.allScopeHint':'В браузерах всех хостов.',
'terminal.browser.downloadLocation':'Файлы сохраняются в папку «Загрузки»',
'terminal.browser.saveAs':'Сохранить как…',
};
Loaded 100 of 2307 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.