Do not replace an existing restore stash on later puts, and clear matching
stash entries when deleteByKeyPrefix wipes sync credentials on disconnect.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Only drop the prior plaintext after encrypt+upsert succeeds so a failed
restore can still retry and recover the saved SecretRef.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Move provider→plugin bindings into a dedicated DB table outside
plugin-writable secrets, validate provider namespace ownership, and drop
overwrite stashes when replacement writes fail.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Only the syncPutSecret path stashes prior plaintext/SecretRef so ordinary
secrets.set overwrites do not leave credentials in process memory.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Stash prior plaintext and SecretRef id on overwrite, restore them when
connect fails, and discard the stash after a successful reconnect so
saved provider credentials keep resolving.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Retain provider→plugin secret bindings so disconnect can wipe credentials
after a plugin is disabled, keep overwritten secrets on failed reconnect,
and snapshot live-empty sidecars in protective backups.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(#2737): automated Cursor CLI fix
* fix: address Codex review on PR #2739
* fix(vault): harden destination re-dedupe passphrase and counts
Keep collapsed destination imports aligned with CSV merge policy for key
passphrases, and correct plugin importer added/duplicate counters when
hosts collapse into one group.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): preserve SecureCRT and plugin hosts under destination group
Skip endpoint collapse for SecureCRT imports, and keep plugin hosts that
differ by configuration when an import destination rewrites their group.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): skip plugin imports that collide after destination rewrite
After rewriting imported hosts into the destination group, drop ones that
now share a fingerprint with existing vault hosts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): keep group case in host merge keys
Vault group paths are compared exactly, so Prod and prod must remain
distinct session identities during import/create dedupe.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): prune orphan credentials after destination collisions
When a destination rewrite drops colliding imported hosts, also remove
identities/keys that were only referenced by those skipped hosts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): only prune newly imported credentials on collision
Pass existing identity/key counts into destination handling so remapped
duplicates of vault credentials are never deleted when a host is skipped.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): merge credential refs when collapsing destination hosts
Carry identityId/telnetIdentityId/identityFileId onto the retained host
so destination-group collapse does not drop referenced auth.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): only graft credential refs onto bare collapsed hosts
Avoid copying identityId onto a retained host that already has key or
password auth, which would silently change resolveHostAuth preference.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(plugins): add sync providers and encrypted sidecar sync
Implement PR 8 of the plugin platform (#2269): namespaced sync Providers
with provider.sync, encrypted-object storage operations, WebDAV through the
shared storage surface, dynamic cloud provider IDs, and non-cascade sidecars
for sync:true plugin settings plus account/CRDT baselines.
Related to #2269
* fix(plugins): wire sync providers and sidecars into production paths
Close PR 8 integration gaps: dynamic plugin provider registry and
getConnectedAdapter createPluginStorage, WebDAV through EncryptedObjectStorage,
cloud payload collect/apply for pluginSidecars, and IPC/preload bridges.
Related to #2269
* fix(plugins): preserve WebDAV auth reuse and resourceId through EOS wrap
encryptedObjectStorageAsCloudAdapter now reports initiallyAuthenticated when
config exists (matching raw WebDAVAdapter) so getConnectedAdapter reuses the
cached instance, and preserves/refreshes resourceId from the backing adapter
instead of always forcing the default object key.
Related to #2269
* fix(plugins): address Codex PR 8 review findings
Carry pluginSidecars through merge/convergent uploads, lazy-connect before
object I/O with revision-aware writes, fail closed on sidecar collect errors,
expose connectPluginProvider, preserve remote timestamps, and drop deleted
installed-plugin settings from collection.
Related to #2269
* fix(plugins): close remaining Codex P1/P2 sync issues
Lazy stream cancel for inline sync reads, must-not-exist conditional writes,
three-way sidecar merge for local resets, validated apply path for settings,
and merge remote sidecars into convergent uploads.
Related to #2269
* fix(plugins): harden sidecar collect/apply and write verification
Preserve last-known sidecars when the host is offline, apply remote
deletions for installed plugins, force convergent upload on sidecar
divergence, verify plugin object writes, and pick plugin providers in
auto-sync startup checks.
Related to #2269
* fix(plugins): close remaining Codex sidecar/sync path gaps
Include sidecars in auto-sync payload and hash, keep explicit empty bundles,
fail apply when host is offline, require getCapabilities, and carry merged
sidecars into convergent local apply/upload decisions.
Related to #2269
* fix(plugins): close Codex P1 sidecar collect/apply/merge gaps
Treat null host responses as unavailable, persist collected settings into
the non-cascade table, retain decoded convergent payloads, use three-way
sidecar merge for deletions, and fail apply only on operational errors.
Also treat non-empty plugin sidecars as meaningful sync data and re-run
auto-sync when plugin contributions change.
Related to #2269
* fix(plugins): close remaining Codex P1 sidecar and provider gaps
Replay offline-cached sidecars before collect, keep explicit empty
sidecar fields after three-way merge, thread sidecars through convergent
conflict/downgrade materialization, keep gated-off plugin providers from
joining sync as connected, and rebind plugin EOS sessions after runtime
replacement.
Related to #2269
* fix(plugins): separate pending remote sidecars from last-known cache
Replay only host-offline remote applies into the DB, keep last-known as
upload fallback, use contribution reset for deleted settings, probe real
plugin host readiness, rebind storage events for plugin providers, clear
plugin bases on reset, and swallow disconnect rejections.
Related to #2269
* fix(plugins): hydrate retained sidecars and gate missing providers
Materialize installed-plugin settings from retained sidecars before
collect pruning, clear sidecars on setting reset, fail closed when
pending remote replay fails, and only restore plugin providers that are
contribution-available. Also initialize dynamic provider counters on
connect.
Related to #2269
* fix(plugins): surface plugin sync providers and preserve conflict sidecars
Collect live sidecars before convergent conflict apply/upload, include
dynamic providers in status and disconnect paths, and list plugin sync
providers in Cloud Sync settings for connect/disconnect.
Related to #2269
* fix(plugins): per-provider sidecar baselines and safer collect/UI
Merge convergent sidecars against each provider's own baseline, keep
retained settings in sidecars without unvalidated plugin_settings writes,
guard isConnectDisabled for never-connected plugins, and fall back plugin
provider labels/icons in the status popup.
Related to #2269
* fix(plugins): correct plugin provider discovery and sidecar reverify
Read nested contribution metadata for sync provider cards, gate empty-config
connects, enforce single-provider disconnect for plugins, and re-verify after
sidecar-only convergent uploads so baselines track the post-write payload.
Related to #2269
* fix(plugins): refresh sync provider availability from live contributions
Replace the contribution-available provider ID set when plugins change so
disabled or uninstalled sync providers leave the auto-sync ready set and
drop to disconnected with config retained.
Related to #2269
* fix(plugins): preserve sidecars in backups, pending queue, and migration
Attach last-known plugin sidecars to protective local vault backups, fail
operationally when pending/last-known storage writes are rejected, carry
sidecars through convergent migration publish, and reject trailing garbage
on encrypted object parse.
Related to #2269
* fix(plugins): abort conflict collect failures and merge migration sidecars
Rethrow operational sidecar collection errors during convergent conflict
resolution, LWW-union all local/provider sidecars during convergent
migration, re-collect last-known after apply, and prefer live host collect
for protective local vault backups.
Related to #2269
* fix(plugins): restore pluginBridge tests, fix import path, union downgrade sidecars
Correct SettingsSyncTab dynamic import path so production builds resolve,
add ipcMain.on to the plugin bridge test double, union all provider
sidecars on convergent downgrade, keep empty last-known resets, and skip
force-delete when contribution reset validation rejects.
Related to #2269
* fix(plugins): abort backup collect failures and honor empty remote wipes
Only fall back to last-known sidecars when the host is unavailable during
protective backups, treat explicit empty remote sidecar bundles as
authoritative wipes, remove lint-blocking anys from harness tests, and
use a stable equal-timestamp sidecar tie-break.
Related to #2269
* fix(plugins): include sidecars in equality, preserve merge base on reconnect
Compare pluginSidecars in cloudSyncPayloadsEqual for migration freshness,
clear plugin merge state only when account/resource changes on reconnect,
cancel accepted sync read streams on failure, and stop re-connecting the
plugin EOS adapter on every I/O.
Related to #2269
* fix(plugins): drop cached adapters on contribution refresh
Invalidate plugin sync adapters when live contributions change so a
restarted runtime gets a fresh connect, take offline any non-disconnected
missing providers, and latch version-change backups only after cancellation
checks pass.
Related to #2269
* fix(plugins): preserve omitted sidecars and hydrate on plugin enable
Treat missing pluginSidecars as legacy/unsupported in three-way merge
(only explicit empty is a wipe), hydrate retained settings when a plugin
enables, and re-enable retained provider configs when contributions return.
Related to #2269
* fix(plugins): keep newer retained sidecars over older stored settings
When collection merges existing sidecars with plugin_settings rows, prefer
the newer updatedAt so a schema-rejected remote value retained only in
the sidecar table is not overwritten by a stale local setting.
Related to #2269
* fix(plugins): seal plugin provider config and clear base on config change
Encrypt opaque plugin provider configuration before persistence, clear
merge state when reconnect configuration changes, and stream sync objects
below a base64-safe inline threshold so control-plane JSON stays in budget.
Related to #2269
* fix(plugins): seal plugin configs by provider id and hydrate before start
Encrypt plugin provider configuration only for non-builtin provider IDs
so field-name collisions cannot skip sealing, hydrate retained sidecars
before plugin enable starts the runtime, and stop tearing down live
adapters on no-op availability refreshes.
Related to #2269
* fix(plugins): seal scalar configs, safe inline reads, stable config fingerprint
Encrypt any non-builtin plugin configuration JSON shape, enforce the
base64-safe inline read cutoff, and compare reconnect configurations with
order-independent fingerprints so merge bases are not cleared spuriously.
Related to #2269
* fix(plugins): thread sync credentials and harden availability checks
Pass SyncConnectPayload.credential through the object-storage and IPC host
path, treat only null/undefined config as missing, and avoid wiping the
plugin provider availability catalog on transient discovery failures.
Related to #2269
* fix(plugins): unblock schema-required sync connect and scalar configs
Add a JSON config dialog for plugin sync providers that declare a
required configurationSchema, reuse retained configs on reconnect, and
treat falsy scalar configs as present in readiness and adapter lookup.
Related to #2269
* fix(plugins): rebind sync adapters on contribution refresh
Drop cached plugin sync adapters when live contributions still include
the provider so replaced runtimes get a fresh connect, and seal falsy
scalar plugin configs with nullish presence checks end-to-end.
Related to #2269
* fix(plugins): apply sidecars under current declared setting scopes
When a plugin update changes a syncable setting scope, write under the
live declaration and drop the obsolete scope row so collection does not
republish duplicates.
Related to #2269
* fix(plugins): seal config envelopes and stabilize provider counters
Use an unambiguous host-owned sealed-config envelope, initialize
dynamic-provider sequence counters before cross-window decrypt, keep
three-way sidecar deletions during convergent migration, and create the
sidecar table for existing schema-1 plugin databases.
Related to #2269
* fix(plugins): avoid mid-sync adapter drops and sidecar resurrection
Keep plugin adapters across setting-only contribution refreshes and
rebind sessions on ensureConnected; stop preserving local-only missing
plugin sidecars when remote already mentions the plugin; compare configs
with nullish semantics across windows; swallow quick-connect failures
after toasting.
Related to #2269
* fix(plugins): restore remoteKeys for installed sidecar resets
Reintroduce the remote key set used when dropping installed-plugin
settings the remote no longer carries so applyFromSync cannot throw
after replacing the sidecar table.
Related to #2269
* fix(plugins): honor conflict policy and re-key scoped sidecars
Pass preferCloud/preferLocal into three-way sidecar merge, use
per-provider baselines on convergent downgrade so local resets are not
resurrected, and re-key applied settings under the current declared
scope coordinates.
Related to #2269
* fix(plugins): keep strategy on final sidecar merge and block dual legacy
Pass preferCloud/preferLocal through the final verified sidecar merge,
avoid auto-reactivating a retained plugin while another legacy provider
is already connected, cache plugin adapters only after initializeSync,
and preserve explicit null configuration values.
Related to #2269
* fix(plugins): strip plugin avatars and protect last-known sidecars
Do not load plugin-supplied avatar URLs in the main renderer, and avoid
overwriting last-known sidecars with an unmerged remote bundle when the
post-apply collect fails or is non-authoritative.
Related to #2269
* fix(plugins): strip top-bar plugin avatars and stop always-rebind
Hide plugin avatar URLs in the sync status popover, drop always-on
rebindSession for plugin adapters, and read convergent mode from the
local config module when deciding whether to auto-reactivate providers.
Related to #2269
* fix(plugins): use window.netcatty for sync IPC and allow null configs
Read plugin sync and sidecar APIs from the production preload global,
and treat an explicit config property (including JSON null) as present
for readiness and adapter lookup.
Related to #2269
* fix(plugins): close residual Codex P2 sync edge cases
Three-way merge sidecars during v2 convergent migration so local
resets are not resurrected, preserve explicit null plugin configs on
adapter rebuild, rebind EOS sessions after I/O failure for replaced
runtimes, and accept the public inlineObjectBytes limit on readObject.
Related to #2269
* fix(plugins): keep inline read cutoff under control-plane budget
Restore the base64-safe inline cutoff so advertised-size reads cannot
overflow the 128 KiB provider JSON envelope; providers near the public
inlineObjectBytes limit must stream.
Related to #2269
* fix(plugins): harden sidecar empty-vault, hydrate LWW, and credential seals
Close remaining Codex/review gaps for PR8: keep empty sidecar shells from
bypassing upload guards, skip startup hydrate when local settings are newer,
seal/validate durable plugin credential refs, and cover schema 1→2 migration.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): rebind plugin sync sessions and keep null configs
Address Codex P2 on 8ae52c54: re-issue connect after runtime replacement,
and treat stored config:null as present in the dashboard reconnect path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): defer empty sidecar last-known until sync succeeds
Keep prior non-empty last-known across an authoritative empty collect so the
empty-vault guard can still recognize a plugin-only reset, then commit the
empty cache after a successful upload.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): avoid restricted localStorage global in sidecar test
Bind the mock to a local binding so eslint no-restricted-globals stays clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): commit applied sidecar cache and reset orphan settings
Prefer merged payload sidecars when updating last-known after sync, and clear
syncable plugin_settings that lack sidecar rows on authoritative remote deletes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): reseal marker-shaped plugin configs instead of skipping
Exact __netcatty_plugin_config_v1 collisions no longer bypass encryption; trusted
host envelopes are unwrapped then resealed, and false collisions are sealed whole.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): keep merged sidecars in last-known after apply collect fails
Return applyFromSync entries from IPC and use them when follow-up collect fails,
so preserved missing-plugin rows are not dropped from the offline upload cache.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): delete stale-scope settings at stored coordinates on reset
After resetSetting against the current declared scope, also remove the row at
the sidecar's stored scope so scope migrations cannot republish remote deletes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): close sync-provider credential, stream, and sidecar gaps
Make SecretRef usable end-to-end via lease-bound network.request, keep
renderer/main transfers abortable and bounded, and stop sidecar
last-known/pending from corrupting backups and conflict apply.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): harden single-provider, secret schema, and conditional conflicts
Keep legacy restart from dual-connecting providers, extract writeOnly schema
secrets into OS storage, and surface conditional-write precondition failures
as conflict UI instead of a generic sync error.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): dedupe sync type imports for lint
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address Codex review on PR #2713
* fix(plugins): wait for sidecar host before version backup latch
Defer the one-shot upgrade snapshot until the plugin sidecar host is ready (or a short grace), and catch getDataHash failures so pending/baseline paths do not reject unhandled.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): require sidecar grace before version backup
Always wait for contributions/grace tick before the upgrade snapshot, and only latch after a non-cancelled attempt so cleanup cannot suppress retries.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* fix(#2727): automated Cursor CLI fix
* fix(sftp): drop external-edit retainers when browse sessions close
closeSftp already deletes registered temps on disconnect/reconnect; clear
the matching renderer retainers so hasActiveWork cannot stick on a gone file.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): clear external-edit retainers when watches force-stop
When the temp file disappears, fileWatcherBridge force-stops the watch
with localPath; drop the matching retainer so hasActiveWork cannot stick.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: address Codex review on PR #2730
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): stop OpenCode turns dying on terminal tool failures
OpenCode MCP terminal_execute treated non-zero exits as bare
"Error: Operation failed" (dropping stdout), and the OpenCode driver
aborted the whole turn on tool status=error. Preserve command output
and surface tool errors as tool results so multi-step agent runs can
continue.
Closes#2718
* fix(ai): use neutral MCP text for silent terminal_execute success
Codex P2 on #2724: ok:true empty serial/raw PTY results no longer fall
back to "Error: Operation failed". Prefer Catty's "Command completed
(no output)" and keep operational failures / non-zero exits unchanged.
Also prefer non-empty OpenCode tool error/output when formatting tool
results.
Add single-entry delete on the Global history side panel, remove the
matching host autocomplete record when no duplicate row remains, and
keep the change set intentionally slim (no multi-window journal/sequence
hardening).
Fixes#2701
* fix(terminal): keep broadcast through command colons
Treat trailing colons after a confirmed shell prompt as typed command
input so workspace broadcast is not paused mid-command (#2709).
Require terminator + whitespace so mid-label shapes like Challenge #1:
stay fail-closed; standalone auth prompts remain untrusted.
* fix(terminal): require real PS1 body for colon broadcast bypass
Title-case challenge labels that embed shell markers (Challenge # 1:,
Account $ code:) must stay fail-closed. Only bare markers, user@host,
paths, shell-version PS1s, and lowercase identities count as a real
prompt prefix for the #2709 mid-command colon exemption.
* fix(hosts): keep draft connect scripts before onConnect promote
Host details queue editing pruned non-onConnect scripts immediately, so
default manual automation scripts could be selected but never appeared
in the queue. Keep an editable draft queue and only prune missing IDs
until save promotes triggers.
Closes#2705
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): promote persisted manual connect-queue scripts on save
Editable queue retention left already-saved manual script IDs unpromoted
when reopen/save compared only newly added IDs. Promote every queued
script that still needs onConnect + host targeting.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(hosts): drop unused HostDetailsPanel snippet imports
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): do not demote global scripts during queue save sync
Promote-on-save must leave targetsAllHosts onConnect scripts unchanged
when they appear in a host connectScriptIds list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(hosts): cover global connect scripts during queue save sync
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): promote connect-queue scripts on new host save
New hosts have no initialData, so queue promote-on-save was skipped and
manual scripts stayed non-onConnect after the first save.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): harden connect-queue promote against Codex review
Preserve global scope when promoting manuals, skip re-promoting scripts
demoted while the host editor is open, sync peer host queues for retained
targets, and keep test fixtures ASCII-only.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): persist peer connect queues with edited host save
Peer queue sync via onHostsChange was overwritten by onSave rebuilding
from a stale hosts snapshot. Write peers+edited host together and make
save paths apply functional host updates.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): keep connect queues across hydration and concurrent edits
Skip queue rewrite while snippets are still empty, and do not promote
entries whose non-onConnect trigger changed while the host editor stayed
open.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): skip connect-queue rewrite before snippets hydrate
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): preserve concurrent target removals and merge peer deltas
Drop stale queue entries when targets were unlinked while editing, and
apply peer connectScriptIds updates via functional host state merges.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): hydrate connect-queue baseline and append peer deltas
Capture the first hydrated snippet catalog as the editor baseline, and
merge peer queue updates by appending only newly queued script IDs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): snapshot opening queue and keep peer updates host-safe
Use the editor-opening connect queue as sync previous IDs, and never
overwrite the edited host inside peer delta merges.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): clear deleted queues after hydrate and honor manual unlinks
Treat a hydrated empty snippet catalog as ready for queue reconciliation,
and drop concurrently untargeted non-onConnect queue entries on save.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hosts): clear loginScriptId when connect queue is emptied
sanitizeHost reconstitutes connectScriptIds from loginScriptId when the
explicit queue is absent, so clearing the last queued script must also
drop the legacy field.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Shell-open retries already applied to every Copy Tab reuse, but resilient
PID scans were still gated on jumpHosts. Direct bastion targets that
rate-limit session channels could reuse the shell while leaving the
copied tab without shellPid. Always retry rate-limited discovery on the
reuse path; keep the optional pre-scan pause jump-host-only.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
If the jump-host Copy Tab pre-open PID scan is rejected with channelOpen
too offen, a single delayed attempt left discovery unavailable and skipped
post-open assignment. Retry rate-limited pre-open scans so shellPid
tracking still succeeds before opening the reused shell.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
After a bastion-rate-limited shell open succeeds on Copy Tab, the follow-up
PID discovery exec can hit the same "channelOpen too offen" limit. Delay and
retry that discovery on jump-host reuse so the copied session still gets a
shellPid for unambiguous cwd lookup.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Address review feedback: permanently skipping PID discovery on jump-host
Copy Tab left shared transports without shellPid and broke cwd lookup.
Delay discovery briefly instead, and keep shell-open retries for the
bastion "channelOpen too offen" rate limit.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
Bastion/jump hosts often reject rapid session channel opens with
"channelOpen too offen". Copy Tab was opening a discovery exec then a
shell on the same authenticated transport, failing reuse and falling
back to a fresh login that prompts for OTP again.
Skip shell PID discovery when jump hosts are present, and retry shell
opens with short backoff on the bastion rate-limit error so duplicated
tabs keep reusing the live connection.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
If residual/stale HKLM keys cannot be rewritten without elevation, register
per-user HKCU verbs instead so Settings can still enable Open in Netcatty
for portable and unelevated installs.
Persist and compare development app entry args so moving a checkout
re-registers Explorer shell commands instead of keeping a stale
electron.exe + old app path launch line.
Clear only ProgrammaticAccessOnly when unsuppressing keys that still have
commands, and unsuppress after a failed HKLM refresh so preference-write
rollback cannot leave the only working portable integration hidden.
When only one ProgrammaticAccessOnly write succeeds, remove just that
value (or a newly created empty key) instead of deleting the whole HKCU
shell subtree that may still hold a working portable command.
When the saved preference says enabled, still confirm both shell verbs
remain registered; otherwise re-apply so interrupted installer rewrites
or partial cleanup cannot leave a permanently missing menu entry.
When HKLM registration exists, disable only writes ProgrammaticAccessOnly.
Deleting portable HKCU install verbs before a failed suppression left users
with no working Explorer integration and an unrepairable machine path.
Record a separate probe marker for ZIP/portable no-verb defaults so a
shared AppData profile cannot suppress a later NSIS install. Register
development Explorer verbs as electron.exe + absolute app entry.
After a failed machine repair, report any surviving shell verb as enabled
so Settings can disable it. Persist enabled:false for ZIP/portable defaults
so warm starts skip repeated registry probes.
When a partial machine registration exists, keep working portable HKCU
verbs until both HKLM shell entries are current, so unelevated enable
attempts cannot destroy the user's only working context-menu path.
Treat residual portable HKCU verbs as a hard enable failure when HKLM
exists, require suppression cleanup on user-scope enable, and restore
the previous preference when apply partially mutates the registry.
Codex P2 on #2671: portable/ZIP builds leave real HKCU shell verbs that
shadow later all-users HKLM registrations. When machine keys exist,
delete the per-user Netcatty verbs so Explorer uses the current install.
Codex P2 on #2671: if only one ProgrammaticAccessOnly value is written
during disable, delete the partial key so disable never leaves a single
hidden verb while the other remains visible.
Omit sourceSessionId for sftpSudo hosts in the picker and normalize the
browse connect path so reusedConnection (spinner/overlay/logs) matches the
dedicated sudo open. Addresses Codex review on #2674.
Codex P2s on #2671: remove treats any leftover runnable verb as still
enabled, and install restores ProgrammaticAccessOnly when enabling fails
after clearing a prior per-user suppression.
Codex P2 on #2671: when no preference exists but a single leftover shell
verb remains, still run install/repair so the Settings toggle cannot stay
off while Explorer still shows one Open-in-Netcatty entry.
Skip used to mark transfers cancelled without clearing conflict metadata, so reopening SFTP rehydrated the dialog while the transfer center looked empty. Clear conflict on skip/stop, only restore attention conflicts in the dialog, and strip stale payloads when loading persisted history.
Fixes#2636
Codex P2 on #2671: unelevated refreshes must not report success merely
because non-empty HKLM commands exist. Require writes to succeed or both
verbs to already match the expected executable/command contract.
Codex P2 on #2671: treat the integration as user-suppressed only when
both folder and background keys have ProgrammaticAccessOnly, so a partial
suppress write cannot hide one menu while claiming the feature is off.
List open SSH terminals in the SFTP Connected section even when sftpSudo
is enabled. Connection still opens a dedicated sudo SFTP channel and does
not reuse the terminal shell. Closes#2656.
Address Codex P2s on #2671: disabled preferences re-suppress on every
startup so NSIS updates cannot resurrect the menu, and registration is
only considered complete when both folder and background commands exist.
Codex P2 on #2671: a partially written registry verb (key exists, no
command value) must not count as an active menu registration, so install
and Settings toggle state stay honest and repair can still run.
Persist download fingerprints as sha256:pN:hex so coverage is explicit,
treat planned zero bytes as the empty digest, and fall back to a full-file
check for legacy bare sha256:hex identities saved by older builds.
Standalone dedicated reconnect was still coercing totalBytes=0 to
undefined via ||, so the bridge re-statted a grown remote and abandoned
the empty snapshot plan. Keep finite zeros in the stream payload.
Persist the last-applied executablePath in the preference record and
re-apply shell verbs when it differs from the current launcher path so
renamed/moved portable builds keep Open-in-Netcatty working.
Drop growth-aware compact identity experiments that either lost rewrite
proof or needed renderer node:fs. Compact entries keep full size+mtime
identities (retransfer on remote growth). Preserve explicit totalBytes=0
through bridge preflight so empty download snapshots do not re-stat into
a grown remote size.
Codex P2 on #2671: electron-builder portable apps expose the stable
launcher via PORTABLE_EXECUTABLE_FILE while process.execPath points at a
temp unpack that is deleted on exit. Resolve that path for shell verbs
and bump the preference schema so existing installs re-register once.
Path-only compact identities were too weak for rewritten/truncated files.
Download trees now key entry identity to path+planned size (omit mtime),
revalidate covered compact entries against local destination sizes, and
preserve explicit zero-byte snapshot plans through resume validation.
Address Codex P2 on #2671: the Settings toggle is per-user, so disable
only clears HKCU and suppresses machine verbs via ProgrammaticAccessOnly
instead of deleting HKLM (even when elevated). Also persist a schemaVersion
so healthy warm starts skip registry work after the first migration.
Codex P2 on #2671: warm starts no longer spawn a full reg.exe write pass
when installer/user shell verbs already match the expected command, label,
and icon. Still rewrites stale --open-terminal-path entries on upgrade.
Preserve the planned snapshot size for interrupted directory download
children, use path-stable compact identities for download trees so
append-only growth does not invalidate covered prefixes, and treat
zero-byte planned snapshots as complete in the sequential fallback.
Codex review on #2670: accept remote growth only after the full planned
prefix still matches the staged file, and skip the sequential stream when
the checkpoint already covers the snapshot so grown logs do not resume
from the old EOF.
Address Codex review on #2671: when all-users (HKLM) shell verbs cannot be
deleted unelevated, hide them for the current user via ProgrammaticAccessOnly
instead of failing the Settings toggle. Also stop mirroring machine verbs into
HKCU so uninstall does not leave a broken Open-in-Netcatty entry.
Add a Settings > System switch to enable/disable the Explorer folder
context menu, and fix shell launch so Chromium does not swallow
--open-terminal-path (args after --, =path form, trailing-dot roots).
Closes#2651Closes#2655
Address Codex review: after the async clipboard read, only insert when
the find input is still document.activeElement and still connected, so
a delayed paste cannot steal focus or hit the editor body.
SFTP downloads take a fixed size snapshot at start, then rejected the
transfer if the remote file grew before completion. Live logs and other
append-only sources commonly hit that post-check and failed with a size
mismatch. Accept remote growth when the planned prefix was transferred,
and keep resume identity keyed to that snapshot so pause/resume still works.
Closes#2653
Custom Ctrl/Cmd+V handling for Electron overwrote paste while the
snippet script editor find bar had focus, so search text landed in the
script. Route find-widget paste through shared helpers and reuse them
in the text editor.
Fixes#2661
Bound the main-process TCC probe to 3s, only treat real private IPv4/IPv6
literals as LAN, prefer proxy as first hop when set, and annotate
unreachability using either the target or the first hop. Closes the
remaining merge blockers for #2663.
Show manual compact for any Catty session with a configured model, not
only when enough history exists for a large head. External agents still
do not get the command.
Gate /compact behind canCompact so the composer is not cleared as a
silent no-op, hide the usage ring until a real context snapshot arrives,
and summarize only the UI head that the persisted compaction boundary
will drop. Drop unused clearSessionMessages surface and add slash-command
regression tests.
Emit plan items as { text, completed } with top-level running|completed status so sdkAgentAdapter and the activity UI can render labels and completion instead of empty rows stuck on running.
Remove the async rpc.request hop that let promptDone win Promise.race on session/prompt JSON-RPC errors. Gate emitDone on turnCompleted, treat any incomplete process close (including exit 0) as failure, and align streaming-json toolResult emission with ACP when rawOutput is present.
Permission replies must select an optionId offered in params.options instead of inventing allow-once. Process close(null, signal) before turnCompleted is a failure for both ACP and streaming-json drivers.
Attach a stdin error listener so async EPIPE after peer exit is not an
unhandled stream error. Treat EPIPE/ERR_STREAM_DESTROYED as benign
(processErrorGuards/terminalBridge); fail the turn only for other mid-turn
write errors.
Only turnCompleted (headless end / ACP prompt success) may ignore teardown
exit codes. streamedAssistantText must not suppress errors, or mid-response
crashes look successful.
Root cause of Token ~1: onPromptComplete resolved promptDone in the same
tick as session/prompt, so Promise.race often discarded the result and the
UI fell back to estimated usage (prompt length / 4). Emit usage when the
prompt response is handled, and map live Grok _meta.usage fields including
cachedReadTokens.
Drop the inactive acp/streaming-json in-memory session when switching
runtimes so switch-back cannot resume a pre-switch thread without
intervening turns. Forward optional session/prompt usage to emitter.usage.
Tool-only turns leave no assistant text; Windows teardown often exits with
code 1. Mark turnCompleted when session/prompt succeeds so close does not
emitError or skip emitDone.
Windows .cmd/.bat installer shims fail with spawn EINVAL when launched
without shell. Route ACP, headless, and models spawns through the same
helper other managed agents use.
When a stored session id cannot resume/load, still inject renderer history
into the new session prompt so multi-turn context is not dropped. Successful
resume/load paths remain unseeded to avoid stacked prior replies.
Prefer ACP session/resume (then load/new) without replaying history into the
current turn, authenticate when Grok advertises methods, isolate acp vs
streaming-json session identities, and apply MCP local-tool lockdown plus
--no-auto-update on both transports.
Suppress session/update emissions until session/prompt starts so
session/load history replay does not fill the current assistant bubble.
Also treat Grok like CodeBuddy: when a resume session id exists, do not
also inject renderer conversation history into the prompt.
Expose a Settings toggle on the Grok Build card (like Codex App Server)
to choose ACP agent stdio or the original headless streaming-json path.
Persist grokRuntime on ExternalAgentConfig and pass it into the driver
via NETCATTY_GROK_RUNTIME on the agent env.
Grok agent stdio session/new rejects plain object env (Invalid params /
McpServer enum). Emit type:stdio servers with env as [{name,value},...]
matching the live protocol, and assert the shipped shape in unit tests.
In MCP tool-integration mode, pass --disallowed-tools for Grok built-in
shell/edit/write so remote session work goes through injected Netcatty MCP,
matching Claude-style policy. Skills mode leaves local shell available for
the Netcatty CLI skill path.
Curate GROK_MODEL_PRESETS and wire getAgentModelPresets so the chat model
picker still has a non-empty list when live grok models discovery fails,
matching the Claude/Codex fallback pattern.
Expose Grok as a managed Settings agent card, icon, coding-CLI provider,
and runtime model discovery target. Add en/zh/ru strings for in-app Grok
Build (distinct from External MCP client install).
Add grok to ManagedAgentKey/meta, discovery known-agents list, XAI_API_KEY
and ~/.grok/auth.json auth probe, and closed sdkBackend unions so the CLI
is discoverable and routable like other managed agents.
Register a headless streaming-json driver for backend key grok that maps
ACP-derived NDJSON events into the shared emitter protocol and injects
Netcatty MCP via a project .grok/config.toml merge with restore.
Codex P2: restoreWorkspace() rebuilds the workspace object field-by-field
and omitted autoTitle, so with Restore Previous Session enabled an
explicitly named "Workspace" (rename / creation dialog / snippet) lost its
autoTitle:false on restore and got relabeled with a host name.
Carry autoTitle through restoreWorkspace with a boolean guard so a
deliberate false survives and legacy workspaces stay flagless. Adds restore
coverage for both the explicit-false and legacy-absent cases.
Codex P2: resolveWorkspaceTabLabel treated any workspace whose title equals
"Workspace" as an untouched default and replaced it with a host label — so a
workspace a user renamed to "Workspace" (or a snippet named "Workspace")
lost its chosen name.
Add an explicit Workspace.autoTitle flag. It is authoritative when present:
- set false on user rename, snippet-created, named "New Workspace", and any
createWorkspaceFromTargets call given an explicit name;
- absent/true means the tab may derive a host label.
Legacy workspaces without the flag fall back to the old default-title string
check, so already-open default splits still get host labels and previously
renamed workspaces keep their names.
- domain/models/workspace.ts: add autoTitle field.
- domain/workspace.ts: thread autoTitle through createWorkspaceFromSessionIds.
- application/state/useSessionState.ts: mark rename / snippet / named
creation paths as explicit; createWorkspaceFromTargets takes an optional
name and only marks custom when one is given.
- domain/sessionTabTitle.ts: derive only when autoTitle (with legacy string
fallback); tests for explicit "Workspace" name and explicit auto-title.
Third Codex P2: submitSessionRename rewrites BOTH customName and hostLabel
(useSessionState.ts:585,597), so counting distinct hosts by hostLabel still
mislabeled a renamed same-host pane as a separate host and added a spurious
"+1".
Count by session.hostId instead — the only identity a rename leaves
untouched. All local terminals share the stable LOCAL_TERMINAL_HOST_ID and
remote sessions carry their host config id, so same-host panes collapse to
one regardless of renames. The focused pane still shows its rename as the
primary label. Tests updated to carry hostId (including a rename that
rewrites customName + hostLabel but keeps hostId).
Follow-up Codex P2: the "+N" distinct-host set used getSessionConnectionLabel
(customName || hostLabel), so two panes on the same host where one session
was renamed produced different keys and a spurious "+1", presenting the
workspace as spanning multiple hosts.
Count distinct hosts by hostLabel alone. The focused pane still shows its
rename as the primary label; only the host count ignores per-session
renames. Adds coverage for the renamed-same-host and rename-with-multi-host
cases.
Review feedback (Codex P2 + Copilot): resolveWorkspaceTabLabel derived
its label and its distinct-host "+N" count from resolveSessionTabTitle,
which folds in the dynamic shell/agent title. Two problems:
- those live titles are published only to the session presentation store,
not the plain session records passed to the tab, so the label went stale
under dynamicTabTitleMode 'agent'/'all';
- per-pane dynamic titles differ on a single host, so the "+N" suffix
fired even when every pane was on the same host.
Use getSessionConnectionLabel (rename / host label) — the stable host
identity — for both the primary label and the distinct-host set. This is
also what the tab is meant to convey (which host), and it needs no
presentation-store subscription. Drops the now-unused dynamicTabTitleMode
argument from resolveWorkspaceTabLabel.
A tab holding a split workspace showed the generic default title
"Workspace", giving no hint which host it belonged to. Derive the tab
label from the workspace's focused session when the workspace still
carries the default title: a local shell reads as its host label
(e.g. "Localhost"), an SSH session as its configured host name. If the
workspace spans more than one distinct host, the focused host is shown
with a "+N" suffix. A user-renamed workspace keeps its custom title.
- domain/sessionTabTitle.ts: add DEFAULT_WORKSPACE_TITLE sentinel and
resolveWorkspaceTabLabel(), reusing resolveSessionTabTitle so labels
match what single-pane tabs already show.
- domain/workspace.ts, application/state/useSessionState.ts: reference the
shared DEFAULT_WORKSPACE_TITLE constant for the default title.
- components/top-tabs/TopTabItems.tsx: render the derived label (with a
title tooltip) instead of workspace.title.
- domain/sessionTabTitle.test.ts: cover rename, single/multi-host, empty.
Copilot review caught that the new showContextMenuOverFullscreenApps flag
only affected the TerminalContextMenu suppression helpers, but the
capture-phase contextmenu interceptor in useTerminalEffects
(shouldInterceptMouseTrackingContextMenu) still called preventDefault +
stopImmediatePropagation on every non-Shift right-click while mouse
tracking was active. That killed the contextmenu event before it could
reach Radix, so the menu never opened even with the setting on.
Route an unmodified right-click through the same path as Shift+right-click
when the setting is on and rightClickBehavior is 'context-menu':
- shouldInterceptMouseTrackingContextMenu returns false (let contextmenu
reach Radix so the app menu opens)
- shouldStopShiftRightClickMouseTrackingMouseDown returns true (stop the
button press from reaching the TUI, so tmux/vim don't also react)
The setting is menu-only: paste / select-word right-click behaviors are
unchanged. Adds predicate coverage in middleClickBehavior.test.ts.
Add an opt-in "Show menu over fullscreen apps" terminal setting
(showContextMenuOverFullscreenApps, default off). When enabled, the
right-click context menu opens even while a fullscreen app (tmux, vim,
less) holds mouse tracking, instead of the click being passed through to
the app.
Threaded via a new forceMenuInAlternateScreen flag through the
shouldSuppressMouseTrackingContextMenu predicate (single source of truth)
and its callers. Default off keeps current behavior unchanged; Shift+
Right-Click still works as the escape hatch when off.
- domain/models/terminal.ts: new field + default false
- application/syncPayload.ts: include key in synced terminal settings
- TerminalContextMenu.tsx: forceMenuInAlternateScreen through predicates + prop
- TerminalView.tsx: wire setting to prop
- TerminalBehaviorSettings.tsx: toggle row
- i18n en/ru/zh-CN/zh-TW: label + description
- TerminalContextMenu.test.ts: coverage for the opt-out
Chinese glyphs (PingFang etc.) paint outside a 1.0 em box, so leading-none
plus truncate was slicing vault tree, tabs, and dialog titles. Use safer
line-heights and slightly taller note title inputs.
The title-bar terminal icon only appeared on Vault/SFTP and vanished on
terminal surfaces with the host tree open. Drop the redundant control;
local shell remains on the host-tree toolbar and quick switcher.
Finish-path metadata checks treated mtime/ctime bumps as hard source
changes. On macOS, xattr/Spotlight can move ctime without rewriting
bytes, so repeated pause/resume often failed near 100%. Prefer digest
verification for uploads; keep strict timestamps for downloads.
After soft-resume, main may advance lifecycleEpoch past the worker-local
value. Progress fanout now stamps max(main, worker) so the transfer center
does not reject live progress as stale.
In worker-backed mode soft-resume must stamp the normalized main epoch
(broadcast by nextWorkerLifecycleEpoch), not the worker-local value, so
later progress is not rejected as stale.
Foreground Resume no longer joins the multi-minute background wait promise;
each caller waits with its own maxWaitMs, and only the final truncate is
single-flight. Failed settle attempts are not sticky on later Resume clicks.
Codex P1s: exclusive single-flight deferred truncate so background settle
cannot race resume unpause, and soft resume waits for/uses the pause-time
full SHA-256 fingerprint when available before unpausing.
- Soft resume identity: size + mtime (+ local head sample), not size-only,
so same-size in-place rewrites are rejected without full-file SHA-256.
- resumeQueuedTransfer returns lifecycleEpoch so soft-resume does not invent
a higher epoch and drop live progress.
- Ignore left-/right- SFTP pane connection ids as openSftpForSession targets.
- Pass resumeHosts (vault + ephemeral) separately from vault hosts used by tray.
Pause then resume was flaky: soft resume cleared lifecycle epochs so stale
paused events re-applied, soft failures always rehomed to dedicated-resume
(panel queue vanished, "reconnecting" then stuck paused), and concurrent
range soft-drain only waited 1.5s before "still finishing".
Soft resume now stamps bridge lifecycle epochs, only hard-reconnects when
the live stream is gone, dedicated resume reuses terminal/parked transports
(including ephemeral hosts), and deferred sparse settle runs in the
background with a longer resume wait for in-flight ranges.
Call the existing focus primitive directly on tab switch (no outer rAF
wrap). Resolve workspace Detach Session labels via per-session
usePresentedSession so dynamic titles stay live without remapping the
whole TopTabs bar.
Stop TerminalLayerWorkspaceSection from falling back to memoized ctx when
sidePanelLiveStore clears activeWorkspace (workspace → solo tab). Refuse
session-restore active-tab patches without a trusted in-memory full payload
so a connect+activate race cannot write stale disk sessions under a new
activeTabId; force full rebuild from live state instead.
Stop AppView from re-subscribing to activeTabId so shell composition no
longer rebuilds on every top-tab click; move plugin keybindings and host
editor surface visibility into leaf subscribers. Patch session-restore
activeTabId without re-serializing every session on tab switches, defer
chrome theme CSS work with fingerprint short-circuit + rAF, let terminal
workspace chrome read active workspace from the live store, apply TopTabs
presentation per tab, and keep hibernate wake chunked/idle with leaner
focus scheduling.
Encode null vs missing presentation fields for useSyncExternalStore, and
compare AI history list title chrome so renames refresh without stream thrash.
Skip clipboard writes on selection reattach, bound xterm wait polling, and
detect AI session create/delete for history lists without sibling stream thrash.
- Store null presentation tombstones so clears override stale snapshots
- Compare selected AI session identity for cross-scope history resumes
- Attach selection overlay after xterm runtime exists; skip copy-on-select
while preserveSelectionOnInput restores the selection
- Align system-sidebar hibernation source contract with resolvedSession
updateHosts now reuses unchanged host object refs instead of re-sanitizing the
whole vault, and terminal host patches use a functional update. Line-timestamp
toolbar toggles optimistically so the gutter paints before vault write work.
Auto-focus no longer claims keyboard focus when the caret is in the AI/side
panel. Selection overlay and copy-on-select move into TerminalSelectionAIOverlay
so clearing selection (e.g. focusing the AI input) does not re-render the whole
Terminal tree.
App/TopTabs consume useEditorTabChromeList so content keystrokes no longer
rebuild shell domains; dirty dots use useEditorTabDirty. AI side panels keep
the full sessions list for fuzzy history but areEqual only on exact-scope
session object refs so sibling stream ticks skip retained panels.
Stop appTerminalDomain from depending on the whole settings bag and stabilize
hotkey/capture callbacks via refs so opacity/settings/log writes do not rebuild
the terminal domain. Gate hidden History shellHistory subscriptions, use a
stable empty quick-switcher list, and move TopTabs activity dots onto per-tab
store snapshots so sibling activity no longer re-renders the whole tab strip.
useSyncExternalStore now reads a per-session snapshot string so sibling title
updates notify the store but do not re-render unaffected TerminalPane instances.
Merge main into the PR branch and keep the top-tabs local terminal control as a
fallback when the host-tree toolbar is disabled, collapsed, or not on a work
surface (Codex review).
Use the shared show/collapse/hide layout for the host-list toolbar, drop the
redundant top-tabs local-terminal button, and switch the local shell control to
the borderless Terminal glyph as the primary one-click entry.
Address Codex P2/P3 on #2628: feed live titles/providers to TerminalFocusSidebar
and TerminalPane chrome via sessionPresentationStore, and keep aiPanelContextsEqual
on domain-local types so domain does not import UI modules.
useThemeRuntime now returns a memoized bag with setters-stable callbacks, and
App domain memos depend on clear/pick/resolve members instead of the whole
runtime object so unrelated App re-renders no longer rebuild appTerminalDomain.
Split the main-window mega bag into domain-scoped AppView slices so Vault,
terminal, chrome, dialogs, and mounts only re-render when their slice identity
changes. Drive tab titles/coding-CLI icons through sessionPresentationStore
(store-only live updates + orphan map overlay), retain presentation-stable
sessions for domain memos, stabilize AI panel context Maps with structural
equal/retain, and keep shell history on shellHistoryStore so Vault host lists
ignore history-only churn.
- Memo side-panel chrome on stable equal; read live theme when shell is open
- Theme/System live subscribe only when those panels need it; scripts runs
subscribe only while Scripts is visible
- Treat workspaceById focus-only identity as stable for side panels
- Include knownHosts / add-known-host / transfer-count handlers in stable keys
- setScriptRuns publishes scriptRunsStore so automation has a single source
Address correctness and residual jank found in review:
- Expand pane session equality for connect/startup fields (pendingInitialCwd,
startupCommand, pending scripts, serial/local shell, etc.).
- Cache coding-CLI provider in the signal controller so memo-skipped
TerminalLayer renders cannot clear titles via a stale sessionsRef.
- Heal terminalCwdStore drift when the renderer ref already has the path.
- Publish shell history synchronously on write/load and via useLayoutEffect.
- Move script runs to scriptRunsStore; Scripts slot subscribes so automation
ticks no longer re-render TerminalLayerInner.
- TerminalLayerView equal uses stable side-panel keys only (cwd/theme live
via sidePanelLiveStore, not chrome re-renders).
- Compare terminalFontFamilyId / session log props; stabilize HostTreeView
callbacks and end-session-drag handler.
Decouple high-frequency UI state from TerminalLayer's memo boundary so
npm run dev is less dominated by full-layer reconciliation.
- Publish shell history through shellHistoryStore; History side panel
subscribes directly instead of receiving shellHistory props.
- Track terminal CWD in terminalCwdStore so OSC 7 updates do not setState
TerminalLayerInner; TabBridge updates side-panel live snapshots only.
- Move remote-history ownership into the History side-panel slot.
- Treat sessions as equal for pane/layer memo when only dynamicTitle or
codingCliProviderId changes (TopTabs still sees full session updates).
- Stabilize AppView terminal handlers; thin AI vault catalog props when
the AI panel is not visible; memo HostTreeView.
Keep retained AI panels mounted for session continuity, but skip
ChatMessageList/ChatInput while CSS-hidden so tab switches do not
reconcile Streamdown trees. Render assistant text as plain content
during streaming, lazy-load Streamdown only after the turn settles,
and split message layout from the markdown chunk for a lighter open path.
Bump @xterm/* betas so shared-atlas clear (#6055), no-mipmap upload
(#5987), and capacity eviction (#6043) ship in addon-webgl. Replace the
local isolation/backport postinstall mutator with a verify-only gate.
Default new-terminal scrollback to 3000 (closer to VS Code's 1000).
The post-install userns probe runs as root under dpkg/rpm, and root can
create a user namespace even when unprivileged userns is restricted (e.g.
Ubuntu 23.10+ with apparmor_restrict_unprivileged_userns=1). The probe
therefore always passes at install time and leaves chrome-sandbox at 0755,
so on machines where the app itself cannot use the userns sandbox Chromium
aborts: 'The SUID sandbox helper binary was found, but is not configured
correctly ... must be owned by root and have mode 4755'.
Set 4755 unconditionally (the historical electron/chrome default) so the
SUID sandbox works as a fallback whenever the userns + AppArmor path fails.
Refs: binaricat/Netcatty#2607
A cloned SSH session whose first connection fails before the inherited
cd is consumed lost the directory on manual retry: startReconnect(manual)
calls prepareRestoredReconnect(), which clears restoreCwdIntentRef for
non-restored sessions, and the initial-cwd prep only ran at runtime boot.
Re-run prepareInitialCwdIntent() in the manual branch; it no-ops once the
cwd was consumed or when there is no pending inherited cwd.
- hostById is a Map, not a function: my previous commit called it as
hostById(id), throwing TypeError on every Copy Tab / Split. Use
hostById.get(id), and fall back to terminalHosts for ephemeral hosts absent
from the saved-host map (P1)
- classify the DETECTED host.distro (not getEffectiveHostDistro, which returns
a cosmetic manual override) when deciding the network-device probe gate, so a
pinned Linux icon can't re-enable the exec channel on a Huawei/Cisco host (P1)
- clear pendingInitialCwd via app state when the inherited cd is consumed
(report the new cwd through onTerminalCwdChange), so shells that never emit
OSC 7 don't keep the transient seed and re-inject a stale dir on remount (P2)
Adds regression tests: Map-based hostById, network-device gate (deviceType,
detected distro, ephemeral terminalHosts).
- Local clones now inherit the live tracked cwd: expose getSessionRestoreCwd
(the OSC 7 cwd map, which is never mirrored onto session.lastCwd) and feed it
into captureInheritedCwd as the primary source, so Copy Tab / Split of a
running local terminal open in its current dir, not the launch dir (P1)
- Gate the SSH /proc probe like the app's own cwd-probe path: never probe
network devices (Huawei VRP etc. can drop the session on the extra exec
channel) and consult the SSH banner for unclassified hosts (P1)
- Mark the inherited cd consumed only when it is actually sent (from the
restore-cwd consumption callback), so a failed first connect can re-arm the
intent on retry instead of starting in the login dir (P2)
- captureInheritedCwd: race the SSH cwd probe against a 1.5s timeout so a
slow/wedged connection can't block clone/split tab creation; fall back to
lastCwd on timeout/failure
- clone factory: only set pendingInitialCwd for inject-eligible remotes
(ssh/undefined, non-mosh/et), not telnet/serial/mosh/et where it would be
dead, never-cleared state
- AppView: pass a stable useCallback for the new-local-terminal icon instead
of an inline arrow that defeated the TopTabs memo comparison
Reconnect can replace browse connection ids before save completes, so close
handlers that only matched sessionId skipped dirty editors and tore down their
writer. Remap as soon as reconnect is detected and match owners by sftpTabId.
Co-authored-by: Cursor <cursoragent@cursor.com>
- captureInheritedCwd: probe live SSH cwd before stale lastCwd snapshot (Codex P2)
- new-local-terminal icon routes through handleCreateLocalTerminal so sessions get a ConnectionLog + output capture (Codex P2)
- use single-quote import in AppHandlers (Copilot)
Reconnect during save changes the browse connection id before remapSessionId
runs, so session-id-only ownership briefly reports no owned editor and parks
the channel mid-write. Match promoted editors by sftpTabId as well.
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-resolve the pinned pane after ensureRemoteSftpId so editorTabSave
remaps session ownership to the regenerated connection id.
Co-authored-by: Cursor <cursoragent@cursor.com>
Track sftpTabId on editor tabs, remap sessionId after reconnect, and
subscribe SFTP side panels to editor presence only so typing does not
rerender every mounted panel.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop hostId-based pane fallback so non-owning SFTP writers keep throwing
"connection is no longer available" and editorSftpBridge can dispatch to the
correct owner when multiple useSftpState instances are mounted.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep browse sessions alive when promoting the built-in editor to a tab, and
reconnect the SFTP session on save when the browse mapping is missing.
Closes#2576
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(terminal): suggest Network Device Mode via a session-header tip
When a connected host is classified as a network device (auto-detected
vendor banner/os-release, or a manual distro-icon override) but Network
Device Mode is off, show a non-blocking one-line tip at the top of the
session. The user can enable the mode (persisted to the host) or dismiss
it; we only suggest once per host.
Driven by the host's effective distro so it also previews without real
hardware: set a host's device icon to a network vendor and reconnect.
- domain: shouldSuggestNetworkDeviceMode (exact vendor match, once-per-host)
- application/state: networkDeviceModeSuggestion persistence boundary
- TerminalView: header tip + reserve top offset so it never covers output
- i18n: terminal.networkDevice.tip.* for en/zh-CN/zh-TW/ru
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): gate network device mode tip to plain SSH sessions
Codex review: the suggestion tip used the host's effective distro (incl.
manual icon override), so a Mosh/ET/serial/telnet session with a
network-vendor classification would still offer it and persist a
host-level deviceType:'network'. The Host Details toggle is exposed only
for plain SSH hosts, so this could set a hidden mode that surprises later
SSH reconnects. Add an effectiveProtocol gate to shouldSuggestNetworkDeviceMode
and pass effectiveTerminalProtocol from TerminalView. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): make network device tip gate effective and popup/multi-pane safe
Addresses Codex re-review on the tip:
- The effectiveProtocol gate was dead: ctx never carried
effectiveTerminalProtocol, so it was always undefined (unrestricted).
Derive it from the host via resolveEffectiveTerminalProtocol instead.
- Suppress the tip when host updates are unavailable (detached popup has no
onUpdateHost) so Enable can't silently no-op; plumb canUpdateHost through ctx.
- Sync dismiss/enable across panes/windows for the same host via an in-process
subscription in the suggestion persistence module. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): make network device tip once-per-host, ephemeral- and multi-window-safe
Addresses Codex re-review round 3 on the tip:
- Exclude ephemeral deep-link/quick-connect hosts (isSavedVaultHost): they are
not in the persisted vault array, so Enable would silently no-op even though
canUpdateHost is true.
- Track first display separately from user interaction: record "shown" silently
on first display so simply closing the session no longer re-nags on every
reconnect, while the showing instance keeps the tip until the user acts.
- Propagate resolve/dismiss across separate BrowserWindows (#/session-window)
via a native storage-event listener that diffs newly handled host ids.
(#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): don't touch localStorage at import; keep network tip clear of speed dial
Addresses Codex re-review round 4:
- P1: networkDeviceModeSuggestion read localStorage at module scope
(knownHandledIds initializer), which threw ReferenceError under the Node
test runner and broke `npm test`. Initialize it lazily inside the existing
browser-only window guard instead.
- P2: when the host info bar is hidden and search is closed the full-width tip
strip overlapped the compact speed-dial toggle (top-right) and swallowed its
clicks. Reserve right-side clearance for the toggle via a pure
resolveNetworkDeviceTipRightInset helper (with unit test).
(#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): drop network tip once the host is classified elsewhere
Addresses Codex re-review round 5: while the tip is latched open, enabling
Network Device Mode from Host Details or a synced pane updates the host to
deviceType:'network', but the eligibility effect's early return kept the tip
(and its now-redundant Enable action) visible. Clear the latch whenever
host.deviceType becomes 'network'. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): persist only the deviceType override, not the effective host
Addresses Codex re-review round 5 (P1): the tip's Enable spread the *effective*
session host (group defaults / proxy profile already materialized) into
onUpdateHost, so mergeTerminalHostUpdate froze every inherited field
(credentials, appearance, proxyConfig) as host-level overrides and later
group/profile changes stopped propagating. Send a sparse
{ id, deviceType: 'network' } update instead. (#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(terminal): move network device tip lifecycle into a state hook; per-host keys
Addresses Codex re-review round 6:
- P1 (architecture): TerminalView no longer imports the persistence API or owns
the handled-state subscription/eligibility/writes. Extract
useNetworkDeviceModeSuggestion under application/state; the component consumes
{ visible, enable, dismiss } and keeps only view glue (sparse host update +
toast), per AGENTS.md's layer boundaries.
- P2 (cross-window clobber): persist one key per host instead of a single shared
array. localStorage has no atomic read-modify-write, so concurrent appends
from two windows could drop a marker; independent keys never clobber, and the
storage event recovers the host id directly from the key.
(#2367)
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Main-process progress IPC was ~10 Hz and each tick remapped the full
transfer list plus published the global center, pegging the main window
renderer at 100%+ CPU during large copies. Raise IPC throttle to 250ms/1MB
and coalesce progress-only React/store patches to one paint per frame.
Restart resume and bulk dedicated opens were still calling openSftp, which
silently borrowed a parked/shared terminal transport. That reattached
checkpoint resume to a lease-bound conn instead of an independent dial.
Dedicated transfer sessions now pass reuseTransport:false so they always
open a fresh vault SSH connection.
Remove ensureTransportShape upgrade of bare { count, conn } descriptors.
Borrow requires a registry transport from createConnectionRef/createTransport.
Update tests to construct real transports instead of hand-rolled count objects.
acquireConnectionRef / borrowTransport now ensure leases Map and registry
fields exist on pre-registry { count, conn } descriptors so older tests and
in-memory sessions do not throw on transport.leases.has.
Password-only automatic login now digests the password so vault rotation
invalidates parked reuse, while multi-material auto keeps publicKey-based
matching so key-first transfer opens still find the shared transport.
Auth material fingerprints are method-scoped so key-first / password-retry
SFTP opens still match a parked multi-material transport. Password rotation
invalidates password auth; key/cert content rotation invalidates those methods.
Digest password/privateKey/certificate/passphrase/identity paths into the
auth fingerprint so key or password rotation under the same keyId invalidates
parked transports. Secrets are not retained on the transport registry — only
the digest is indexed.
Bind-failure cleanup must remove close/error handlers while conn is still
attached; releasing first cleared conn and left stale listeners on the
shared transport after local bind errors.
Add resolveSftpTransferSourceSessionId that walks all reusable terminal
sessions instead of the picker one-per-hostId list, so transfers can still
borrow a live transport when another tab for the same hostId has a different
endpoint.
Dedicated dials that registered a transport lease were leaving the lease
held after local/dynamic server errors. Always release managed leases so
authenticated connections are not orphaned until quit.
Record remote bind target before forwardIn so cancel-during-listen always
unforwards (or discards). Shared PF transport errors discard instead of park;
identical idle TTL re-sends no longer reset idle deadlines.
Remote unforward callback errors now discard the shared transport like
timeouts, so a half-open remote listen cannot be idle-parked. Shared
port-forward reuse attaches close/error lifecycle before bind so mid-listen
transport drops cannot leave zombie local listeners.
Shell and parked-shell lookup now use endpointAllowsReuse(..., "shell") so
disabling ForwardAgent cannot reattach to a warm conn that still exposes the
local agent. SFTP/port-forward keep asymmetric channel reuse so they can still
borrow a ForwardAgent-enabled terminal transport.
Store agentForwarding on the session/transport endpoint and apply an
asymmetric reuse check: opens that need ForwardAgent reject parked conns
that never enabled it, while SFTP/port-forward can still borrow a
ForwardAgent-enabled terminal transport.
Transfer pool source sessions now require hostname/user/port parity so
multi-tab or overridden endpoints do not borrow the wrong shared transport.
Remote forward cancel-during-forwardIn and stop paths share one timed
unforward helper that discards the transport when the server listen stalls.
Avoid shell/sftp lease id collisions across reconnect generations, and
discard the shared transport when remote unforward cannot be confirmed
within the stop timeout.
If the user stops a shared local/dynamic/remote forward while listen or
forwardIn is still pending, close the listener and resolve cancelled
instead of re-registering the tunnel as active.
Track accepted forward sockets per tunnel and destroy them on stop, end
already-dead transports instead of parking them, and fingerprint
command-proxy routes so edits invalidate warm reuse.
Catch transport registration failures inside async bind callbacks so a
dead shared socket cannot crash the main process, and fingerprint jump
hops by both vault id and effective endpoint/proxy.
Include proxy/jump profile identity in transport keys, normalize loopback
remote-forward addresses, detach shared listeners on cancel, and avoid
MFA-retrying dedicated dials for local bind failures.
Attach error/close lifecycle to reused port-forward transports, match
wildcard remote binds by expected listen address, and end unusable
transports whose sockets are destroyed during lookup.
Scope transport keys by vault hostId so different profiles cannot share
an authenticated connection. Fix createConnectionRef to preserve hostId.
Route remote-forward accepts by destPort/bind address, await unforwardIn
before success, return stopAllPortForwards as a promise, and update
async cancel tests.
Introduce a main-process SSH transport registry (borrow/return leases,
ControlPersist-style idle park) shared by shell, SFTP, transfer, and
port-forward. Transfer pool becomes channel concurrency only; remove the
separate transfer-pool idle TTL UI in favor of a single SSH keep-alive
setting. Register dedicated SFTP dials, reuse parked transports, and fix
TTL 0 (park until quit), jump-host endpoint keys, and remote unforwardIn.
* fix(terminal): suppress autocomplete popup on alternate screen (#2530)
Codex CLI's TUI (and other full-screen apps like vim/htop/less) render
their own input UI on the alternate screen buffer. Netcatty's autocomplete
only gated on its shell-prompt heuristic, which codex's composer line
matches — so typing "/" inside codex popped up Netcatty's path/command
list on top of codex's slash-command menu.
Bail out of fetchSuggestions (the single query path for both popup and
ghost text) when term.buffer.active is the alternate buffer. Reuses the
existing isTerminalAlternateScreenActive helper already used by hibernate.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(terminal): keep simple alt-screen autocomplete ban (#2530)
Drop the bloated TUI-vs-multiplexer heuristics from the codex auto-fix loop.
Disable autocomplete unconditionally on the alternate screen buffer — same
simple tradeoff as other hosts; tmux/screen attach has no Netcatty completions.
* fix(terminal): drop alt-screen completions after async lookup too
Same unconditional policy as the entry gate: if the buffer switched to
alternate while getCompletions was in flight, discard results so popup/ghost
cannot paint over a TUI. #2530
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(ai): route auto-detected network devices to raw PTY exec (#2367)
AI exec paths (Catty, worker, MCP) decide whether to send commands raw
(no shell wrapping) based on meta.deviceType === "network". That field
only reflected the manual Network Device Mode toggle (host.deviceType),
so auto-detected switches (Huawei VRP, Cisco IOS, …) still went through
the POSIX shell wrapper. Their vendor CLIs reject the wrapped command,
the start marker never arrives, and the call stalls until the startup
timeout.
Report deviceType: "network" when the detected distro/vendor classifies
as a network device too, mirroring the terminal's own gating. This also
gives the AI system prompt vendor-CLI guidance before the user manually
flips the toggle. Mosh/ET sessions stay suppressed since they require a
shell-backed PTY.
Extract the pure buildAITerminalSessionInfo helper into its own module
for testability and add unit coverage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(ai): guard network-device classification against substring false positives
Address Codex review: replace non-ASCII ellipsis with ASCII per the
ASCII-only convention, and add a regression test proving classification
is exact-match against the vendor id list (so a custom distro that merely
embeds "cisco"/"huawei" is not treated as a network device).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* perf(vault): virtualize host pickers for large inventories
Virtualize Select Host, Quick Switcher, SFTP host picker, and workspace
dialogs so thousands of hosts only mount a viewport window. Shared scroll
and keyboard clamp helpers keep selection and filter-while-scrolled paths
stable under virtualization.
* fix: address Codex review on PR #2553
* fix(vault): plugin row heights and Select Host keyboard types
Give Quick Switcher plugin command/view rows a taller virtual slot so
two-line chrome does not overflow into the next absolute row. Narrow
Select Host navigable rows to group|host so Enter keyboard activation
typechecks cleanly.
* fix(vault): finish host-picker review feedback
Clip and truncate fixed-height Quick Switcher / workspace rows so long
labels never overlap the next virtual slot. Wire Select Host listbox
aria-activedescendant to stable option ids for keyboard AT feedback.
* fix: address Codex review on PR #2553
* fix: address Codex review on PR #2553
* fix: address Codex review on PR #2553
* fix: address Codex review on PR #2553
* fix(vault): clear remaining host-picker codex findings
Declare multi-select listboxes, drop empty SFTP Hosts headers when only
connected rows remain, size the SFTP list to content under a 360px cap,
and give Quick Switcher / workspace pickers a definite max scroll height
so large inventories remain reachable.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Packaged Netcatty launched from Finder often has process.cwd() === "/", so
merging .cursor/mcp.json failed silently and netcatty-remote-hosts never
mounted. Point CLI turns at Netcatty temp and fail loudly on merge errors.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: switch .gitignore to allowlist layout
Default-deny root paths so a mistaken git add cannot pick up secrets,
agent state, or build artifacts. Re-include only known source paths and
keep the existing classical denylist for nested outputs.
Closes#2534
* fix: address Codex review on PR #2554
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* feat(snippets): open quick add/edit as a centered modal
Replace the scripts side-panel AsidePanel drawer with a Dialog so
snippet create/edit no longer slides over the terminal workspace, while
keeping the same form fields and shortkey capture behavior.
* fix: address Codex review on PR #2552
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
* feat(vault): optimize large imports and vault rendering (#2526)
* fix(vault): address import review findings
* fix(vault): preserve filtered selection and import consistency
* fix(vault): complete import review fixes
* fix(vault): make import cancellation concurrency-safe
* fix(vault): preserve concurrent edits during import rollback
* fix(vault): handle concurrent saves and reset import target
* fix(vault): drop empty source groups after targeted import
* fix(vault): preserve sidebar accessibility check
* fix(vault): correct group counts and selection semantics
* fix(vault): preserve imports across concurrent saves
* fix(vault): preserve concurrent group changes during import
* fix(vault): close concurrent import edge cases
* fix(vault): retry rollback and enable tree keyboard selection
* fix(vault): retain concurrent sources and visible selections
* fix(vault): merge imported sources with persisted state
* fix(vault): serialize duplicate managed imports
* fix(vault): keep imports non-blocking and grouped views responsive
* fix(vault): preserve concurrent managed sessions
* fix(vault): preserve concurrent groups and keyboard access
* fix(vault): serialize cross-window import commits
* fix(vault): make import commits atomic
* fix(vault): surface import rollback failures
* fix(vault): recover import transactions safely
* fix(vault): commit imported hosts atomically
* fix(vault): preserve unfinished import recovery
* fix(vault): wait for edits and preserve keyboard focus
* fix(vault): protect group settings and lock fallback
* fix(vault): keep virtual focus aligned
* fix(vault): complete keyboard navigation semantics
* fix(vault): finish keyboard navigation
* fix(vault): close large-vault concurrency gaps
* fix(vault): finish large-vault interaction safety
* fix(vault): harden filtered large-vault flows
* fix(vault): keep managed deletion atomic
* fix(vault): coordinate managed file deletion
* fix(vault): close cross-window managed races
* fix(vault): refresh persisted managed state
* fix(vault): preserve distinct imported sessions
* fix(vault): keep failed progress accurate
* fix(vault): keep search tree collapsible
* fix(vault): close final review gaps
* fix(vault): reveal filtered tree matches
* fix(vault): close final review edge cases
* fix(vault): preserve concurrent import changes
* fix(vault): serialize all vault saves under one cross-window lock
Hold the shared import lock for ordinary host/group/key writes and the
full import commit path so check-then-write can no longer interleave
across windows. Make the lock re-entrant in-window to avoid nested
deadlocks, and announce large tree item positions for screen readers.
* fix(vault): keep concurrent saves serialized and fix tree ARIA siblings
Stop treating same-window concurrent work as nested lock re-entry; nested
helpers skip re-acquire only when the outer critical section already owns
the lock. Announce treeitem position among true siblings at each level.
* fix(vault): require explicit lock handle for nested saves
Independent concurrent vault writes always queue on the shared lock.
Nested import/deletion helpers continue only when they pass the active
lock handle from their outer critical section.
* fix(vault): avoid lock deadlocks with encrypt-then-write saves
Encrypt outside the shared lock and only acquire it for the final write so
importers waiting on pending work cannot deadlock on lock acquisition.
Retry plugin commits outside the lock when concurrent edits intervene, and
rebase imports onto in-memory host edits when disk is still stale.
* fix(vault): release import lock before concurrent-edit retries
Drain encrypt work under the lock and full disk writes only outside it.
When an import or group deletion is superseded by a concurrent edit, release
the shared lock so that edit can finish, then retry. Functional group updates
now base on the latest in-memory snapshot while a write is still pending.
* fix(vault): tidy import dialog destination and SecureCRT prompts
Move import destination settings behind a bottom-left control with its own
step, and keep SecureCRT guidance in a dedicated chooser instead of inside
the format tile.
* test(vault): fix import dialog layout guard test under ESM
* fix(vault): simplify import destination footer control
Replace the heavy two-line destination card with a single quiet text link
that shows the current placement choice.
* fix(vault): simplify import destination settings page
Drop the nested card and duplicate headings. Show only the three placement
choices, finish immediately for preserve, and keep Done only when a group
name is needed.
* fix(vault): simplify SecureCRT chooser and iconify destination options
Remove redundant SecureCRT prompt copy while keeping the two source cards
with icons. Add small folder icons to the import-location choices so that
page is less plain.
* fix(vault): use SecureCRT logo in import chooser header
* fix(vault): drop border around SecureCRT import logo
* fix(vault): clarify import location footer with icon and label
Show a folder icon plus "Import location · current choice" so the control
is recognizable without becoming a heavy card again.
* fix(vault): remove border from import progress status icon
* fix(terminal): keep Enter reconnect working after disconnect
Disconnected sessions advertised "Press Enter to reconnect" but often
swallowed the key when focus left xterm (body blur or dialog controls),
and an open terminal search bar globally hid the hint and disabled the
shortcut. Give the disconnected overlay a focus sink + local Enter
handler, and stop treating search-open as a global reconnect gate while
still leaving real buttons/inputs alone.
Closes#2544Closes#2546
* fix(terminal): stop disconnected dialog focus theft
Only claim Enter-reconnect focus when it is already lost or still inside
this session tree, and restore xterm focus when the overlay unmounts.
Avoid stealing focus from other panes/side panels on disconnect.
* fix(terminal): restore dialog focus only when overlay leaves
Split claim/restore effects so showLogs/error rerenders no longer
yank focus off dialog buttons onto xterm mid-interaction.
* fix(terminal): restore Enter-reconnect focus in popup terminals
Popup Terminal windows lack data-session-id ancestors. Walk up from
the dialog to the local xterm root so claim/restore still work there.
* fix: address Codex review on PR #2550
* fix: address Codex review on PR #2550
* fix: address Codex review on PR #2550
* fix: address Codex review on PR #2550
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
- Remove sdkRequestRuntimes entries on chat session teardown to prevent
stale runtime references accumulating across sessions.
- Cancel pending elications (resolve with deny) when the owning chat
session closes, so elicitations never hang indefinitely.
- Use crypto.randomUUID() as fallback elicitation id when the SDK does
not provide one, avoiding collisions on empty-string keys.
- Surface interrupt failures via console.debug for easier diagnostics
without changing best-effort stop behavior.
Behavior preserved:
- Session key fingerprinting (config-based reuse) unchanged.
- Stop/interrupt flow: signal-first, then queryRef.interrupt().
- Permission mode routing (auto/confirm/observer) unchanged.
- Stream handler event protocol and dedup logic unchanged.
- Upgrade @tencent-ai/agent-sdk to 0.3.230 (elicitation support)
- Add CodebuddyElicitationCard with schema-driven form fields and
accept/decline/cancel actions; wire full event chain from driver
through shared elicitation store to ChatMessageList
- Pass agents/thinking/effort to V2 SessionOptions instead of forcing
query() fallback; include them in the session fingerprint
- Close stale V2 session before query() fallback to prevent context
divergence when resuming the same persisted conversation
- Forward codebuddyOptions from renderer config through IPC to the
main-process driver context
- Add i18n strings (en/ru/zh-CN/zh-TW) and test coverage
- Fix test process hang: stale-session test no longer triggers real SDK
import which spawned a live CodeBuddy backend and prevented exit
- Fix plugin management API: remove non-existent removePlugin, correct
enablePlugin/disablePlugin second param to marketplace string
- Wire elicitation handler in production path via session manager's
pending map; expose elicitation-response IPC to renderer
- Skip V2 Session when query-only options are configured (maxBudgetUsd,
sandbox, fallbackModel, enableFileCheckpointing, agents, outputFormat,
effort, thinking) so settings are not silently ignored
- Expose plugin/mcp-status/account-info IPC channels in preload + types
- Add renderer handling for hook, elicitation-create/complete events
OpenSSH keeps the first HostName value. Setting HostName to the jump
alias under the forced -F session config blocked later Include of
~/.ssh/config from resolving HostName aliases, and could diverge from
the vault snapshot built via ssh -G against the real config.
Drop the process-lifetime ssh -G cache so HostName/HostKeyAlias and
known_hosts path edits are observed on the next connect. Keep a
per-prepare Map only so target and jump discovery in one session can
share probes.
Reset Host context with Match all so Included user/system ssh_config
applies to every hop, not only the trailing jump Host stanza. Prepend
the ssh -F wrapper onto the session PATH from options.env so custom
ProxyCommand helpers remain available.
-F replaces both the per-user and system-wide ssh_config. Append
Include directives for the real ~/.ssh/config and system ssh_config
after session Host blocks so HostName aliases and other user settings
still apply while session ProxyJump/vault overrides keep first-match
priority.
Avoid removing the current target's system known_hosts entry when the
vault only contains unrelated pins, so strict ET stats probes keep
working for already-trusted hosts.
When rewriting vault pins under a resolved HostName, retain
[host]:port encoding for non-default ports so OpenSSH lookup finds the
vault key. HostKeyAlias continues to use the bare default-port form.
OpenSSH ignores $HOME for user config. When ET generates a private
config for ProxyJump and jump host-key policy, put a PATH-fronted ssh
wrapper that always passes -F so interactive ET and follow-up probes
both honor the vault-authoritative Host stanzas.
When the connection target is an SSH-config alias, OpenSSH records and
looks up host keys under the resolved HostName. Rewrite the vault pin
and filter selectors accordingly so accept-new cannot ignore the vault.
OpenSSH ignores $HOME for user config on POSIX, so pass -F to
execOnEtSession so ProxyJump host-key policy is actually loaded. Cache
real ssh -G probes briefly to limit main-process blocking.
Disable KnownHostsCommand when enforcing vault authority so dynamic trust
cannot override vault pins. Rewrite multi-host known_hosts lines instead
of dropping them so unpinned hop patterns remain trusted.
ET --ssh-option applies only to the destination hop. Keep vault-authoritative
UserKnownHostsFile and StrictHostKeyChecking in the ProxyJump Host stanza so
the separate jump OpenSSH process still enforces key verification.
Apply vault-authoritative known_hosts through --ssh-option so OpenSSH
enforces it even when a temp-HOME Host config is not read. Reject
fingerprint-only publicKey tokens when building vault pins.
Write vault pins under the effective HostKeyAlias when present, filter
system entries for both alias and hostname, and invoke ssh -G with -p/-l
so %p/%r known_hosts path tokens expand correctly.
Build separate authoritative known_hosts files for target and jump hops
using each hop's hostname for ssh -G discovery. Reuse the same vault
authority builder for requireTrustedHost exec probes so system pins
cannot override vault keys on stats/shell follow-ups.
Apply vault-authoritative known_hosts only on hops the vault pins, so
unpinned ET jump/target hops keep the persistent user known_hosts.
Also merge ssh -G UserKnownHostsFile paths and preserve spaced path
names when discovering configured trust files.
Discover administrator-configured global known_hosts paths via ssh -G
when building the vault-authoritative snapshot, so replacing
GlobalKnownHostsFile does not drop custom admin pins or revocations.
Evaluate OpenSSH positive/negative host patterns when filtering system
known_hosts so vault-covered hosts do not drop jump-host pins that use
negation. For ET follow-up ssh, do not prepend accept-new when the
session already set StrictHostKeyChecking (including verifyHostKeys=no).
Only switch to a session known_hosts snapshot when the vault pins the
active target or jump host, so ET accept-new still persists first-seen
keys otherwise. Filter OpenSSH wildcard patterns covering vault hosts
and keep @revoked markers so admin revocations remain authoritative.
OpenSSH accepts a match from any known_hosts source, so a rotated system
pin could override a vault pin. Build an authoritative snapshot that
keeps vault keys exclusive for vault-covered hosts, force Mosh
StrictHostKeyChecking=ask, and quote trust-file paths that contain
whitespace.
Prefer waking the terminal runtime so executor encoding (including
bracketed-paste mode checks) is used, instead of inventing paste markers
on a blind backend fallback path.
Preserve OpenSSH default global known_hosts when injecting vault pins by
merging both into a single GlobalKnownHostsFile. When verifyHostKeys is
off, neutralize UserKnownHostsFile and GlobalKnownHostsFile with an empty
file so StrictHostKeyChecking=no cannot still block password auth against
a stale pin.
When multi-tab fan-out falls back to a direct backend write (hibernated
pane), wrap multi-line paste/noAutoRun payloads so newlines do not submit
as Enter.
Mosh and Eternal Terminal bootstrap via system OpenSSH, so Netcatty vault
known_hosts were ignored and a key rotated after a prior SSH connect could
still be accepted. Inject usable vault entries as GlobalKnownHostsFile and
honor verifyHostKeys=false so key-change interception matches SSH trust.
Closes#2501
Skip terminal focus steal during multi-tab snippet fan-out and restore the
original pane afterward. Trap Tab focus inside the quick-add drawer and
restore the opener element on close.
Restore modal dialog contract for quick-add (role/data-state/escape),
harden package path validation, skip sensitive sessions on multi-tab
snippet sends, and show a single run-on-all-tabs action for plain snippets.
Address #2502: bind shortkeys when quick-adding snippets, add package
creation via a split + menu, convert quick-add to a right-side drawer,
and let code snippets run on all workspace tabs from the context menu.
Capture tabId/connectionId at paste time and resolve external folder/entry
uploads against that pane after awaits so mid-transfer tab switches cannot
retarget remaining multi-folder items to another host. Prefer the pinned
pane host when reconnecting and reconnect by tab id without clobbering the
active tab's lastConnectedHost.
PR #2509's TransferRuntime unify accidentally regressed per-task external
upload cancel (#2495), clipboard confirm close-before-upload (#2478), and
main SftpView interactive browse sessions (#2498). Restore those behaviors
on top of the process-level control plane, keep Pausing copy, and gate
broadcastGlobalTransferEvent so bare Node unit tests do not download Electron.
When Observer → Confirm/Auto → Observer, drop the inactive ask/agent
in-memory session so switch-back cannot resume a stale thread and skip
the intervening conversation history.
Update Settings copy to use `cursor-agent login` and note that a saved API
key is retained while CLI login mode is active.
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-apply the stored key explicitly when toggling CLI login and API Key so
mode switches cannot drop a previously saved credential.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stop falling back to bare `agent` on PATH so other CLIs (for example Grok)
cannot be mistaken for Cursor during auth probes, model presets, or turns.
Co-authored-by: Cursor <cursoragent@cursor.com>
Skip duplicate stream-json assistant flushes that include model_call_id, and
isolate Cursor CLI resume sessions by ask vs agent so switching Observer to
Confirm/Auto starts a writable turn instead of keeping Ask.
Only automation bot PRs re-check source-issue pending comments before
mark-ready, so maintainer Fixes-only PRs are not stuck draft. Write
WebSearch/WebFetch denials after the isolated research pass so classify
and follow-up research can still call those tools.
Pause and resume now go through one process-level control plane that survives
panel/terminal close: TransferRuntime + soft latches/epochs/walk registry,
soft-then-hard resume, dual-epoch-safe progress, and no-owner soft control.
Load @xterm/addon-image in the shared xterm runtime so remote programs can
draw real raster images (kitten icat, chafa --format kitty, image previews
in file managers and TUIs) instead of ANSI approximations.
- domain/terminalInlineImages.ts owns the limits and turns TerminalSettings
into addon options; disabled settings resolve to null so the addon is not
loaded at all and the terminal keeps advertising no graphics capability.
- The addon is loaded right after term.open, before the WebGL renderer, so it
patches IRenderService.setRenderer first and its canvas layers survive every
DOM <-> WebGL swap (deferred WebGL, soft-hide suspend, context-loss rebuild).
It is disposed before term.dispose so bitmaps never outlive their terminal.
- Hibernate restores a tab from a text-only snapshot, which cannot carry image
data. XTermRuntime.hasInlineImages reads the live image cache, and Terminal
degrades such a session to soft-hide: hibernateRuntime takes the soft-hide
branch, canFinishHibernate re-checks after every async step, and an eviction
request leaves the session soft-hidden. The block lifts by itself once the
cache is emptied by a terminal reset or FIFO eviction.
- Settings > Terminal > Inline Images exposes the master switch, per-protocol
switches (Kitty / SIXEL / iTerm IIP) and the memory limits, translated for
en, zh-CN, zh-TW and ru. Protocol switches cloud-sync; the memory limits stay
local like the other per-device renderer tuning.
Co-authored-by: Wine Fox <fox@ling.plus>
Issue titles and bodies are auto-validated; agents reading only AGENTS.md or
CLAUDE.md were missing the [Bug]/[Feature]/[Other] prefix and template
requirements. Point all three guides at the shared .github templates.
Closes#2492
When scanning the system known_hosts file or importing from a file,
exclude well-known public service hostnames (github.com, gitlab.com,
bitbucket.org, Azure DevOps) from being added to the Known Hosts vault.
These entries represent third-party services rather than user-managed
hosts and are not meaningful in an SSH host management context.
- Add PUBLIC_SERVICE_HOSTNAMES exclusion list
- Apply filter in both system scan and file import paths
- Show a toast notification when entries are filtered
- Add locale strings for filtered count (en, zh-CN, zh-TW, ru)
Make folder pause latch-first with barriers and a child pause watchdog so
workers stop claiming files after soft-drain; soft-resume live walks instead
of full rewalks. Surface path conflicts with toasts, keep pause progress
visible, and show a resume spinner. Restore verified shell rm -rf with SFTP
confirmation for fast directory delete, and prefer reconnect over wiping the
pane to the host picker when listing fails on some hosts.
Do not paint transferring when every backend resume misses after restart.
Refuse starting or resuming a second job with the same source and target
while another is still active, matching FileZilla-style single-writer
destination handling.
Track UploadController instances per transfer task so queue cancel of one
external upload does not abort another concurrent paste/drop upload.
Scope pending conflict prompts to the owning controller as well.
SFTP queue pause/resume/cancel now call sftpTransferCenterStore (same as the
global transfer center), harden action-button pointer handling, retry
"cannot be paused yet", and keep progress updates from undoing a paused row.
Paint paused optimistically, stop counting paused rows as "active", freeze
visible progress while paused, and soft-drain concurrent pause in ~50ms
without long writeStream waits on the contiguous-range path.
* fix(sftp): close clipboard upload dialog before transfer
Clear the confirmation request before awaiting the upload so the modal
overlay no longer blocks side-panel and standalone SFTP during large
clipboard transfers (issue #2478). Keep the existing background transfer
path and post-upload refresh; guard double-clicks and skip refresh on
transfer failure.
* fix(sftp): scope clipboard confirm guard to request identity
Only block double-clicks on the same confirmation request so a later paste
can still Upload while an earlier background transfer is in flight.
Prefer contiguous bridge checkpointBytes over high-water transferred for
resume offsets, read remote modifyTime for meta pause identity, clear
deferredSparseTruncate on failed pause unpause, and forward fingerprint
fields through drag-drop upload progress.
Wait for in-flight concurrent ranges on resume with a deadline and cancel
checks so stalled SFTP requests cannot hang Resume forever. Propagate
sourceFingerprint through external-upload progress updates.
Embed self-hosted light/dark star history charts after api.star-history.com
stopped serving public embeds, and add a weekly workflow that refreshes them
via RELEASE_TOKEN.
Confirm pause only after a cheap size+mtime source identity is recorded
and published for transfer-center persistence. Wait for in-flight
concurrent ranges before sparse-tail truncation on resume, and refuse
truncate after cancel during that wait.
Pause felt stuck because the panel hid the pausing state, and the
backend waited for every in-flight range plus a full source fingerprint
hash. Soft-drain concurrent ranges, skip blocking fingerprint work, and
show clear pausing feedback in the panel and transfer center.
Raise UPLOAD_TRANSFER_CONCURRENCY from 32 to 64 so the default in-flight
window is ~2MiB, matching Electerm transfer.js / ssh2-style defaults while
keeping the safe 32KiB chunk size.
Remove the unused uploadFileResumableFast alias and the always-null
activeSftpUploads.stream field from the buffer-upload cancel path after
serial WriteStream crawl was fail-closed for #2449.
On promoteLocalTransfer failure after publish, only unlink/restore when the
target still matches the published ready identity. Concurrent post-publish
replacements leave the concurrent file and backup intact.
Do not publish a staged local download over a concurrent file that appears
at the target path after the original was moved to backup. Leave the
concurrent target in place and retain the backup for recovery.
Always content-hash remote destinations before staged promotion (even when
ino/fileId is stable), pass AbortSignal through destination hashing so cancel
closes read streams, and revalidate local download backups before publish.
* fix(sftp): raise upload WRITE fanout for higher throughput
Increase UPLOAD_TRANSFER_CONCURRENCY from 8 to 32 so resumable and
fastPut uploads keep ~1MB in flight (32 × 32KB). Keeps the safe 32KB
packet size from #2022 while closing most of the gap with ssh2/Electerm
defaults on multi-ms RTT paths (#2449).
* fix(sftp): never fall straight into serial WriteStream uploads
Serial ssh2 WriteStream/put is RTT-bound (one 32KB WRITE at a time) and
is the usual cause of sub-MB/s speeds, not the fanout setting alone.
- Prefer concurrent WRITE ranges on an isolated channel, then fastPut,
then concurrent ranges on the shared browse channel; serial stream is
last resort only.
- Do not swallow post-upload size/content verification into fallback.
- uploadLocal and memory-buffer progress uploads use fastPut with the
configured fanout instead of serial put.
- Add a regression test that sudo/shared sessions stay pipelined.
* fix(sftp): fail closed instead of serial WriteStream fallback
Align with OpenSSH/Electerm/WinSCP: default SFTP uploads stay on
pipelined WRITE strategies only. When concurrent isolated, fastPut,
and concurrent-shared all fail, throw the last error — do not crawl via
serial createWriteStream/put (RTT-bound, #2449).
uploadLocal and writeSftpBinaryWithProgress require fastPut; no silent
serial put path. Tests assert createWriteStream is never invoked on
forced concurrent failure.
* fix(sftp): restore pipelined upload on session-backed clients
Session-backed SFTP clients (openForSession / terminal reuse) are not
ssh2-sftp-client instances and lacked client.fastPut after serial put
was removed. Expose fastPut via the raw channel and route uploadLocal /
writeSftpBinaryWithProgress through pipelinedUploadLocalFile so
session-backed paths keep concurrency fanout.
* fix(sftp): make pipelined fastPut abortable and throttle progress
Address codex review on the upload fanout PR:
- Prefer a disposable SFTP channel for cancelable uploads so
cancelSftpUpload / AbortSignal can end the transfer immediately.
- Honor abort signals for session-backed fastPut instead of ignoring
them after the call starts.
- Restore 100ms / 1MB progress throttling for binary-with-progress
fastPut step callbacks.
* fix(sftp): wait for shared-channel fastPut to settle on abort
When abort uses a non-disposable browse/sudo SFTP channel, do not reject
until fastPut's callback fires so temp/staged cleanup cannot race
in-flight WRITEs. Disposable channels still end immediately.
* fix(sftp): harden fastPut abort, channel errors, and pause ads
Codex re-review fixes:
- Attach SFTP channel error listeners during fastPut to avoid unhandled
EventEmitter crashes on disposable channels.
- Wait for fastPut callback (with short force timeout) after disposable
channel abort so temp files are not unlinked while still open.
- Mark pause unsupported during transferBridge fastPut fallback; restore
pause ads for concurrent-shared.
- Create AbortController before staging ArrayBuffer to disk and pass
signal to writeFile.
* fix(sftp): settle fastPut after errors and verify fastPut content
Codex P2 follow-ups:
- Channel errors during fastPut wait for the callback or a short force
timeout before settling (same pattern as cancel) so temp files are not
unlinked while still open.
- transferBridge uploadViaFastPut uses the same settle discipline so
fallback strategies do not overlap a still-running isolated fastPut.
- Resumable fastPut fallback captures and rechecks a local content
fingerprint before promoting success.
* fix(sftp): reset fastPut checkpoint and verify staged size
Codex re-review:
- After isolated fastPut fails, reset checkpointBytes so concurrent-shared
does not resume past non-contiguous progress holes.
- Rethrow noTransferFallback/sourceChanged after fastPut fingerprint check.
- uploadLocal verifies staged remote size against local before rename.
* fix(sftp): avoid hang on cancelled close and unsafe fastPut checkpoints
- Skip/timeout remote CLOSE after disposable channel abort/failure so
concurrent upload finally does not hang waiting on a dead channel.
- fastPut progress updates UI without advancing durable checkpointBytes
(non-contiguous chunk progress is not crash-safe resume state).
* fix(sftp): address remaining remote Codex review notes
- Fall back to the shared browse SFTP channel when opening a disposable
upload channel fails (MaxSessions / subsystem refused), unless aborted.
- Force-settle concurrent range uploads after cancel if in-flight WRITEs
stall on a non-disposable shared channel (2s grace).
* fix(sftp): bound shared-channel fastPut cancel with force-settle
Shared browse/sudo channels cannot call sftp.end() on abort without
killing the session, but a stalled fastPut callback must still settle.
Schedule the same 2s force-finish used for disposable channels, and
cover it with a stall test that asserts the shared channel is not ended.
* fix(sftp): force-settle shared-channel fastPut on channel error
Shared browse/sudo channels cannot be ended on error, but a stalled
fastPut callback after an error event still must not hang forever.
Schedule the same 2s force-finish for shared-channel errors without
calling sftp.end(), and cover it with a stall regression test.
* fix(sftp): stage binary uploads and cleanup shared-channel cancel
writeSftpBinaryWithProgress now uploads to a remote .part path and
renames into place, so a stalled shared-channel fastPut cannot keep
mutating the final destination after cancel returns. Shared-channel
force-settle also best-effort unlinks the in-progress remote target
without ending the browse/sudo session.
* fix(sftp): harden staged upload replace semantics
Default to stage+rename for cancel-safe finals, but:
- write symlinks in-place so the link node is not replaced
- restore previous mode bits after rename overwrite
- fall back to in-place when creating a .part is permission-denied
Covers mode restore, symlink, and parent-dir permission regressions.
* fix(sftp): encode after staging names; never unlink finals
- Build stage/backup names from the logical path, then encode (GB18030-safe)
- Existing files/symlinks write in-place to preserve inode/metadata
- Shared-channel force-settle only unlinks .netcatty-upload-*.part paths
* fix(sftp): keep AbortSignal aborted in dispose cancel handler
When cancel replaces transferControl.abort for a disposable channel,
also abort the AbortController so staged rename/promotion cannot
complete after the UI has already cancelled the upload.
* fix(sftp): stage regular overwrites; throttle fastPut progress
Regular files always stage+rename (cancel cannot mutate finals on shared
channels) and restore mode after promotion. Symlinks stay in-place.
Clip stage/backup basenames to NAME_MAX and stop forcing every fastPut
step progress event through IPC.
* fix(sftp): tighten staging plan and permission fallback
- Without native lstat, existing paths write in-place to avoid clobbering links
- Permission fallback uses SFTP/error codes only (not message substrings)
- Cover size-mismatch paths whose names contain words like "access"
* fix: address Codex review on PR #2452
* fix(sftp): write SCP uploads through existing symlinks in-place
SCP stage+rename was replacing the symlink node itself. Detect symlink
destinations first and upload through them so the link target is updated.
* fix(sftp): drain shared writes before cleanup
* fix(sftp): track generated upload stages explicitly
* fix(sftp): preserve upload recovery paths
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
- Format recovery dispatches triage before clearing invalid-format
- Shared CJK-friendly title rules; codex label terminal state machine
- Research steps in classify; agent TITLE + PR body for bot PRs
- Single @codex review via CODEX_REQUEST_GITHUB_TOKEN (binaricat)
Use TRIAGE_GITHUB_TOKEN for Codex review loop jobs. Treat convert-to-draft
and mark-ready permission errors, and closed PRs, as warnings so @codex
still runs instead of failing the whole automation job.
Public issue comments no longer show a generated-by line. Grounding stays
in code_findings/reasoning only so reply can use UI language without file
paths, symbols, or dense parentheses. Classify prompt and rewrite templates
match that tone.
Use the bot PAT for classify prepare/apply and implement needs-human
comments so issue activity shows netcatty-bot instead of github-actions.
Treat netcatty-bot as an own actor for automation PR loops.
* fix(ci): stop Cursor issue triage crashing on Search pagination
Octokit normalizes Search API pages so response.data is already the items
array. Mapping response.data.items returned undefined and then crashed on
candidate.user during daily-limit admission — so every valid new issue
failed before Cursor classify could run.
* fix(ci): classify already-available issues with how-to and auto-close
Add an already_available triage category for requests the product already
ships: after code-grounded inspection, reply with the concrete UI entry
path, then close as completed. Low confidence falls back to other instead
of closing. Keeps the Search pagination fix so new issues can classify.
* fix SFTP transfer throughput regression
* fix: address Codex review on PR #2429
* fix resumable transfer review findings
* fix remaining resumable transfer review findings
* fix: address Codex review on PR #2429
* fix(sftp): harden resumable range fallback and channel cleanup
Truncate staged sparse tails to the contiguous checkpoint before stream
fallback or pause, always release isolated download channels on cancel and
failed fast paths, and stabilize concurrency unit tests.
* fix(sftp): abort range transfers on isolated channel errors
Attach a temporary SFTP error listener during concurrent range copies so
channel resets reject cleanly instead of crashing main, and force-publish
the contiguous checkpoint when pausing.
* fix(sftp): never truncate S2S temp source on upload fallback
stagedLocalPath during SFTP→SFTP upload is the fully-downloaded temp
source, not a partial target. Only truncate partial download/local
targets and remote .part staging files.
* fix(sftp): truncate direct-download sparse tails on range fallback
Direct SFTP→local resumable transfers keep resumeStage "direct" and never
copied targetType onto the transfer object, so prepareStreamFallback skipped
truncating the staged .part after out-of-order range failure. A later stream
pause could then stat the oversized sparse file and advance past holes.
Treat any staged local path outside the S2S upload phase as a partial target,
persist target metadata on the transfer, and fix the sparse-tail test to assert
against the real staged path.
* fix(sftp): always end isolated upload channel on open failures
uploadFileResumableFast assumed callers only saw throws after the isolated
channel was already ended, but a failed local fs.open left the channel open
while the catch path cleared fastSftp without disposing it. End the channel
in a single finally covering local open, remote open, and range transfer.
* test(sftp): stabilize local-open channel cleanup regression
Make the missing-source upload case delete the file only after the isolated
channel opens, and keep stream-fallback errors handled so the suite asserts
channel disposal without unhandled stream ENOENTs.
* fix resumable transfer fallback safety
* fix(sftp): allow cancel while resumable OPEN is in flight
Install transfer.abort before awaiting local/remote handles so cancel ends
the isolated channel if the server stalls SFTP OPEN. Covers upload and
download fast paths, with a regression test for stalled upload OPEN.
* fix(sftp): detect same-size source rewrites via content samples
Metadata-only checks can miss in-place rewrites when mtime/ctime do not
advance. Sample SHA-256 digests through the open local handle before range
upload and re-check after, and do the same path-level check for stream
fallback. Harden the regression test by freezing utimes after rewrite.
---------
Co-authored-by: netcatty-cursor-bot <41898282+github-actions[bot]@users.noreply.github.com>
Classification must open real source files first. The prompt mandates
search→read→classify, JSON now requires code_paths/code_findings, and
normalizeClassification rejects ungrounded replies so agents cannot answer
from issue text alone.
Cursor's --sandbox enabled fails on ubuntu-latest due to AppArmor, so classify
and implement jobs never ran. Use --sandbox disabled and keep secret isolation
via token stripping, frozen helpers, and post-run scans.
Isolate publish onto fresh runners, harden Codex loop head scoping,
protect fork bootstrapping, and polish classification replies and
mark-ready error handling after local codex review.
Route checked out main for trusted helpers, but scripts/cursor-automation.cjs
only exists on this PR until merge, so require() failed with MODULE_NOT_FOUND.
Fetch the PR head helper when the default branch does not have it yet.
The SFTP transfer center trigger was missing the shared
--top-tabs-muted color used by adjacent utility buttons, so it
rendered as pure foreground white.
Tighten re-triage, Codex single-flight signals, protected-path checks on
commit ranges, trusted helper loading from RUNNER_TEMP, and failure
handling so own/bot loops stay safe and forks only re-@codex.
Add missing Terminal snapshot-effect deps, drop unused cursorAuthMode
deps in SettingsAITab, and remove dead test imports/state so dev lint
starts clean.
Drop Cursor CLI review for third-party/fork PRs. Initial Codex review
stays with the existing connector; after contributor synchronize we only
comment @codex review (deduped per head SHA).
Wire GitHub Actions to classify and implement issues via Cursor CLI,
open draft PRs, request @codex review for own/bot PRs, fix findings,
and only post read-only review comments on third-party PRs.
Cancel wind-down can leave child transferIds latched when they are not in
activeTransfers; clearPendingTransferCancel before reusing those ids so
startStreamTransfer does not abort immediately. Regression test models the
sticky latch.
Soft-unpause only live streams; directory dedicated pause winds down then
startFresh. Resume replace-mode under stagedTargetPath and promote on full
success. Refuse pre-stream pause, force R2R progress without corrupting stage
offsets, and cover the directory pause-resume path with a regression test.
Urgent interrupt bypasses xterm's native scrollOnUserInput. Treat Ctrl+C
as scroll-on-input so interrupting a scrolled-up stream (e.g. tail logs)
returns the viewport to the bottom when that setting is enabled.
Keep browse sessions independent of pool transfers, complete dedicated resume
for files/folders after restart, roll back partial pause safely, clamp crash
checkpoints to durable .part size, and open upload targets by connecting the
host when no terminal tab is active.
Seed erase-scrollback with open sync-block state from prior chunks so a
split H/2J redraw that returns to the live bottom does not append CSI 3 J
and wipe history (Codex review on #2415).
Keep cursor-home and strip only erase-display while reading scrollback,
and ignore 1-row sticky lag so agent TUI frames cannot append under the
previous frame (#2291).
* feat(ai): support Cursor CLI login for subscription quota
Allow the managed Cursor agent to authenticate via local `agent` CLI
login as an alternative to CURSOR_API_KEY, so Catty can use subscription
Auto quota. Keep API key and CLI login mutually exclusive, isolate
resume sessions by auth mode, and expose selectable CLI models with
auto as the default.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ai): harden Cursor CLI login path after review
Address multi-agent review findings: soft-cancel abort without late
stream errors, prefer cursor-agent over ambiguous agent binaries,
mode-aware availability without wiping API keys or sticky-disabling,
and concurrent MCP merge refcount restore. Add unit coverage for the
new contracts.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(terminal): batch hidden log output
* fix(terminal): preserve ordered hidden output
* fix(terminal): bound hidden output overhead
* fix(terminal): respect custom tab stops in timestamps
* fix(terminal): preserve terminal parser state
* fix(terminal): preserve hidden output boundaries
* fix(terminal): retain batching with prompt formatting
* fix(terminal): preserve prompt boundaries through transforms
* fix(terminal): handle unbatched prompt output
* fix(terminal): preserve batched prompt and time boundaries
* fix(terminal): retain background timestamp boundaries
* fix(terminal): preserve visible timestamp boundaries
* fix(terminal): separate shell output from tui frames
* fix(terminal): separate output after tui exit
* fix(terminal): preserve prompts across output slices
* fix(terminal): flush output before context reads
* fix(terminal): time-slice timestamp marker cleanup
* fix(terminal): preserve prompt columns after bare LF
* fix(terminal): preserve background write yielding
* fix(terminal): retain yields across timestamp boundaries
* perf(terminal): scan output boundaries once
* fix(terminal): track prompt cursor movement
* fix(terminal): resume prompt scan after resets
* fix(terminal): preserve stateful prompt ordering
* fix(terminal): drain close output before capture
* fix(terminal): measure control-only prompt prefixes
* fix(terminal): preserve prompt control boundaries
* fix(terminal): preserve prompt mode toggles
* fix(terminal): bound hidden batches and unfocus policy
Always drain write-coalescer size caps even when scheduled frames are gated,
keep unfocused-but-visible panes on throttled batching instead of per-chunk
force flush, compute prompt starts on post-transform display data, and model
full-width wrap so prompt-on-newline does not insert a blank line.
* perf(terminal): skip off-path timestamps and share alt-screen probes
When host line timestamps are disabled, write plain bytes without the
segmenter/marker hot path. Reuse one alt-screen CSI probe per coalesced
chunk for frame split and schedule mode, and reuse a single full-batch
visual-row measure on the single-segment batched timestamp path.
* fix(terminal): read line-timestamp enable from live hostRef
Session data handlers close over boot-time ctx.host, so toolbar/vault
toggles of showLineTimestamps were ignored. Prefer hostRef.current for
the marker hot-path gate and keep hostRef synced on every Terminal render.
* perf(terminal): per-second timestamp ledger while gutter is off
When line timestamps are hidden, keep a lightweight one-stamp-per-second
ledger without xterm markers. Opening the gutter materializes recent
ledger entries into markers for paint; while visible, keep per-line
markers for accuracy.
* perf(terminal): separate timestamp record from gutter paint
Always record a one-stamp-per-second ledger on the write path with no
xterm markers. Gutter rendering maps the ledger to visible rows, so open
and closed modes share the same cheap record path.
* fix(terminal): fill gutter gaps with previous second stamp
When painting line timestamps from the per-second ledger, blank rows
between stamps inherit the preceding stamp's label so multi-line output
in the same second is fully labeled.
* fix(terminal): stop empty gutter paint and false scrollback rebase
Only rebase ledger anchors when the buffer is actually full and baseY
grows from top trim — not when baseY rises while the buffer is still
growing (which deleted MOTD/login stamps on scroll). Cap gutter paint at
the last content line so empty viewport rows past the cursor stay blank.
* fix(terminal): soft-wrap stamp placement and sparse reflow anchors
Estimate ledger lines with visual-row measurement so narrow terminals
do not pin the next second on a wrap continuation. After each write,
attach at most one xterm marker per stamped second so reflow and
scrollback trim keep anchors honest without per-line marker storms.
* fix(terminal): ignore alt-screen rows when placing timestamp anchors
Stamp line estimates froze incorrectly through vim/less alt-screen
output, so sparse reflow markers landed far below the restored normal
buffer. Skip visual-row advancement while DEC alt-screen is active,
key one-stamp-per-second from the batch arrival date, and only attach
anchors still present in the ledger after capacity trim.
* fix(terminal): seed stamp lines from normal buffer when already on alt
When a write begins on the alternate screen, active cursor is the TUI
row — freezing that through 1049 leave mis-pinned sparse anchors and
dropped them past normal bufferLength. Seed and restore stamp estimates
from buffer.normal, and cover multi-write already-on-alt leave+prompt.
* fix(terminal): keep same-chunk pre-enter line on alt leave
Leave CSI must not always re-read pre-write buffer.normal: when enter
happened in the same walk, the frozen estimate already includes normal
advances before the TUI. Only restore buffer.normal when the write
started already on the alternate screen.
* fix(terminal): pure alt stamp transitions that never rewind lines
Stamp estimates stay in normal-buffer coordinates: seed once (active or
buffer.normal when already on alt), freeze while alt is active, and on
leave only clear altActive — never re-read the live term mid-walk.
Export applyAltScreenAction with a table-driven unit test covering
spurious leave, plus write-path cases for already-on-alt, same-chunk
pre-enter advance, and leave-without-enter.
Parent resume clears sticky per-child cancelledTasksRef entries so directory
re-walk can retry cancelled files. Route clearPendingCancel to the terminal
worker (where skipAdmission latches live), re-check cancel on live resume,
and clean per-child stage artifacts on parent cancel.
Re-check cancel inside setTransfers so late cancels are not overwritten,
keep partial progress on cancelled directory downloads, and clear the
pre-start cancel latch on intentional resume/retry.
downloadToLocal previously treated cancelled child counts as partial
failure and overwrote the parent to failed. Cancel now wins, matching
processTransfer, with a regression test for the final-status helper.
Keep replace/staging on directory adopt, skip byte size validation for
directory soft-resume, and treat per-child cancel as cancelled without
aborting sibling workers or overwriting status as failed.
Reassign parent+children together on adopt, stop spurious cancelTransfer
after failed dedicated reconnect, and clear pending-cancel when a queued
job is cancelled so same-id resume is not permanently poisoned.
When a directory transfer is re-adopted after restart, pull orphaned child
rows into the new owner so completed checkpoints are honored. Show badge
count/attention for paused and interrupted work after restore.
Server-to-server dedicated soft-fail rehomes via adopt instead of a no-op
resume, Resume/Pause All skip child rows, and orphaned failed tasks remain
controllable with a clean Retry from byte zero.
Server-to-server soft-fail from dedicated resume rehomes ownership so a
live panel can resume; pause during dedicated reconnect no longer cancels
(so quit confirmation does not destroy in-flight resume).
Pause on dedicated reconnect cancels the open, resolveConflict never
resurrects cancelled rows after async duplicate naming, and background
agent restart admits before opening session-backed SFTP handles.
Guard processTransfer against cancel after conflict stats, and make batch
Stop cancel scheduler jobs, release pause waiters, sync refs, and clean
artifacts like single-task cancel.
When the user stops a conflict batch, cancel every non-terminal sibling
(including attention conflict rows) so they do not remain stuck without
resolve UI or resume.
No-op pause on conflict rows, keep transfer/conflict refs in sync on cancel,
eagerly clear conflictsRef on resolve to stop double processTransfer, and
surface adoption failures when resolving orphan conflicts.
Keep conflictsRef updated during adopt so global conflict resolution applies
immediately, re-check cancel after adopt, gate replace/merge like the panel
dialog, and ignore dedicated progress while paused/cancelled.
Drop panel-local rows reassigned to dedicated-resume, seed conflicts on
adopt without auto-resume, cancel directory children on orphan cancel,
gate dedicated resume on !conflict, and hide invalid global merge actions.
Exclude conflict-pending transfers from dedicated resume and Resume All so
users cannot overwrite targets without resolving Replace/Skip/Duplicate.
Also skip Pause All on dedicated reconnect spinner rows.
Reassign store ownership to dedicated-resume before reconnect work so
panel publishOwner cannot clobber progress, and open vault SFTP sessions
only after the renderer admission pool grants a slot.
Directory cancel now stops all non-terminal children on the renderer
scheduler, and transfer workers re-check cancel before acquiring pool
leases so cancelled bulk jobs do not open dedicated sessions.
Remember cancels before startTransferNow registers the id, cancel the
renderer scheduler on orphan cancel, avoid pause completion resurrecting
cancelled rows, forward skipAdmission for temp downloads, and persist
progress checkpoints on background events.
Share dedicated resume with the renderer admission pool, preserve queued
pause checkpoints, refuse to resurrect cancelled agent rows, and complete
dedicated resumes that finish after a reconnect-spinner pause.
Settle directory streams on cancel return values, detach idle pool slots
before close, make force hard-close lease-aware, honor skipAdmission on
the worker IPC path, fix MCP non-worker admission, and block dedicated
resume from resurrecting cancelled/paused rows.
Keep soft-close sticky across re-acquire, re-check before hard-close,
holder-aware pool discard, admit-then-lease for uploads, directory/cancel
resume edge cases, MCP skipAdmission, and vault-based reconnect identity.
Add dedicated transfer connections (1–2 per host) so bulk uploads/downloads
no longer block browse sessions. Also cover session leases, resume after
restart, auto-reconnect, skipAdmission, and transfer-center UX fixes for #2396.
* Improve keychain identity layout
* Preserve key search results with identities
* Show keychain search empty state
* Show all matching keychain results
* Restore full keychain key view
- Add computeOptionsFingerprint to detect option changes between turns;
close stale session and create fresh one when model/env/mode/etc change
- Store resolved binPath in sdkRequestRuntimes so steer builds the same
session key as runTurn (fixes steer returning 'unsupported')
- Register abort listener that calls session.interrupt() immediately,
so Stop reaches the CLI even while parked in stream().next()
- Update tests for { session, fingerprint } map structure (35 passing)
* fix(sftp): follow terminal cwd on macOS/BSD remotes and first open
The "follow terminal directory" probe read the interactive shell's cwd
only via /proc, which does not exist on macOS/BSD remotes, so
getSessionPwd fell back to the home directory (or failed) and the SFTP
panel never tracked the terminal. Add an lsof fallback (read_shell_cwd)
so the probe works on non-Linux remotes; Linux keeps using /proc.
Also fix the first open: while the panel is closed the per-command cwd
probe does not run, so activeTerminalCwd can be stale (login home) even
after the terminal cd-ed elsewhere. On that stale value the follow sync
saw currentPath === terminalCwd and did nothing, leaving the panel at
home. Force one fresh backend probe when the panel first becomes visible
for a connected remote, and re-sync on reopen.
Fixes#2335
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* fix(sftp): keep first-open follow from snapping back to stale cwd
Addresses PR review: the first-open resync navigated to the freshly
probed terminal cwd but left the stale `activeTerminalCwd` cache (login
home) in place. Because the one-shot ref was already set, a later
dependency change that re-runs the normal follow sync (e.g. closing an
editor toggles hasActiveWork) would navigate the pane back to that stale
home. Mark the stale cached cwd as already handled before navigating so
the normal sync no-ops until the live cwd catches up;
invalidateInFlightFollowSync clears the guard once activeTerminalCwd
actually changes, letting follow resume.
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* fix(sftp): re-check follow eligibility before first-open navigation
Addresses PR review: the first-open resync awaited a fresh terminal-cwd
probe but only re-validated the connection afterward. If the panel was
hidden or interactive work started (editor/dialog) while the probe was
in flight, it still navigated, moving the pane while the follow feature's
own visible/work guards say it should be paused.
Add a live followStillEligible() check (generation, follow enabled/
allowed, visible, no active work, same connection) that gates both the
post-probe continuation and the navigateTo call via shouldApply, matching
the normal follow path. Reset the one-shot guard when bailing mid-probe
so the resync can retry once follow is eligible again.
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* test(host-details): align auth row assertion with unified row height
The HostDetailsSection row was unified to `flex h-12 ... px-3` (#2378)
but HostDetailsConnectionSections.test still expected the old
`min-h-12 ... px-3 py-2` class, failing lint-and-test on main. Update the
regex to match the shipped markup.
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
* fix(sftp): harden terminal cwd resync
---------
Co-authored-by: Cursor Agent <cursor-agent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(ui): open AI silent sessions in an attach terminal popup
Tray jumps for hidden MCP sessions attach the existing terminal popup
to the live PTY instead of activating a tab-less main-window surface.
Output is rebound to the popup and restored when the popup closes.
* fix(terminal): register rebindOutput handlers under worker mode
Worker-enabled builds returned early from terminalBridge.registerHandlers
without registering rebind/restore IPC, so the first AI silent-session
attach popup always failed with "No handler registered". Register those
handlers in both paths and rebind display routing via terminalWorkerManager.
* fix(terminal): harden AI attach popup for worker mode lifecycle
Address codex review P1s: resume flow control before restoring output,
route exit/events to the rebound renderer, and replay home scrollback
via a snapshot request before rebinding display output.
* fix(terminal): restore attach routing on popup close and dual-notify exit
Hardening for AI observe popups: restore output from BrowserWindow closed
lifecycle (not only React cleanup), keep attach home for exit events so
main/tray lifecycle updates, and resume paused backend flow on attach.
* fix(terminal): resume flow on attach restore and close snapshot gaps
Resume backend output when attach popups are force-closed, fan out
lifecycle exit events to home+display in non-worker mode, and pause
during snapshot→rebind so live bytes are not dropped from the popup.
* fix(terminal): harden attach restore and worker lifecycle edges
Prefer live display for ZMODEM dialogs after rebind, dual-notify worker
crash exits to attach-home, and avoid clearing attach-home when restore
targets a dead renderer.
* fix(terminal): sync observe-popup state home and fan out SSH exits
Push popup serializations home before restore, fall back to hibernate
snapshots, settle briefly after pause for worker handoff, and dual-notify
SSH exits to attach-home owners in non-worker mode.
* fix(terminal): fan out exits for mosh/telnet/et attach owners
Use shared fanout for in-process protocol exits so attach-home and
display renderers both receive netcatty:exit under non-worker mode.
* fix(terminal): restore attach only onto main app windows
Avoid routing silent-session output to settings/prewarm windows when the
original home renderer is gone; fall back only to registered main windows.
* fix(terminal): harden silent session popup handoff
* fix(terminal): close observed popup with session
* fix(terminal): keep observe popup from reconnecting
* fix(terminal): preserve cleared observe snapshots
* fix(terminal): route transfer prompts to observer
* fix(terminal): keep context after observe restore
* feat: fold top-bar utilities into a control panel
Collapse External MCP, window opacity, theme, and cloud sync into one top-bar control panel so the right rail only keeps AI, controls, and settings.
* fix: keep the cloud sync panel and append quick controls
Restore the original detailed cloud sync popover as the host surface, and place External MCP, window opacity, and theme controls in a footer section beneath it.
* fix: split quick controls from the cloud sync panel
Restore the original cloud sync popover and move External MCP, window opacity, and theme into a dedicated quick-controls entry so the two surfaces stay separate.
* fix: simplify quick controls panel chrome
Drop the explanatory subtitle and card-heavy rows so the panel reads as a compact control list.
* fix: separate appearance controls from External MCP
Group window opacity and theme under one appearance section, with a divider from the External MCP switch.
* fix: compact window opacity controls in the quick panel
Replace the tall slider-plus-presets block with a single-row preset selector to free vertical space.
* fix: make window opacity controls expandable
Keep the opacity row compact by default and reveal the slider plus presets only when the row is expanded.
* fix: put appearance controls above External MCP
Reorder the quick controls panel so window opacity and theme sit first, with External MCP below the divider.
* fix: harden quick controls opacity matching and drop dead copy
Compare opacity presets by percent to avoid float mismatch, and remove the unused quick-controls description strings.
* fix: clean quick controls accessibility and dead top-bar opacity code
Move Switch accessible names onto the focusable control, restore popover autofocus, delete the unused window opacity button, and drop obsolete top-bar theme copy.
* fix: tighten quick controls peer-window and switch labeling
Assert showExternalMcpToggle is forwarded into the quick panel, and name the External MCP switch from its visible label only.
* fix: add icon to quick controls panel title
Prefix the panel header with the same sliders icon used by the top-bar entry.
* fix: remove icon from quick controls panel title
Keep the header as plain text after the title icon felt unnecessary.
* feat: add external MCP top bar toggle
* fix: polish external MCP toggle accessibility
* fix: harden External MCP top-bar toggle ownership and status sync
Hide the quick toggle in peer session windows, poll runtime status at the same cadence as settings, and drop an unrelated zh-CN copy change from this branch.
* fix: prevent External MCP top-bar poll from clearing startup state
Wait for main-window startup reconcile before runtime status can turn the shared switch off, and keep peer windows from owning the main-process enable path.
* fix: await External MCP startup enable before top-bar status polling
Mark startup ready only after the async enable/disable reconcile settles so persistent mode is not cleared by a still-disabled runtime.
* fix: scope External MCP startup gate and preserve enable intent
Only main App shells wait on startup reconcile, keep waiters single-flight, re-read storage after await gaps, and avoid clearing always-on preference on start errors.
* fix: ignore stale External MCP startup enable after concurrent toggles
Bump an enable generation on top-bar changes and skip outdated startup apply work so boot reconcile cannot overwrite a newer user toggle.
* test: fix External MCP toggle lint errors in unit coverage
Remove a duplicate import and use globalThis.localStorage in tests so eslint no-restricted-globals passes.
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(sftp): preserve active transfers when panel closes
* fix(sftp): preserve completed transfer history after panel close
* fix(sftp): bound retained transfer history lifetime
* fix(sftp): close retained panel cleanup races
* fix(sftp): protect transfer history during panel reopen
Jump hosts reuse the same SSH bridge authentication flow as the target host, but the renderer did not serialize the per-host requiresMfa preference into jumpHosts. A bastion configured to prefer keyboard-interactive was therefore still authenticated with the default password-first order when used in a host chain.
Forward requiresMfa from each resolved chain host into the NetcattyJumpHost payload and extend the startSSH serialization test to cover the flag alongside existing jump host proxy and timeout settings.
The terminal search bar left the previous match decorations on screen
after the input was cleared. Two causes:
- TerminalSearchBar only called onSearch when the term was non-empty, so
clearing the field never notified the search hook.
- handleSearch's empty-term branch returned early without clearing
decorations or resetting the stored term.
Now clearing the input fires onSearch(''), and the empty-term branch
resets searchTermRef and calls clearDecorations() so highlights vanish
immediately and a subsequent find-next won't reuse the stale term.
Problem: some SSH servers require the login password and the MFA challenge through keyboard-interactive. They reject the plain SSH password authentication method even when the password is correct, which makes Netcatty report password failures and can close the session before a usable shell opens.
Fix: add an explicit per-host Prefer interactive auth setting, reuse the existing requiresMfa host flag, forward it from the renderer to the native SSH bridge, and place keyboard-interactive before password only when that setting is enabled and a password is available. Hosts that do not enable the setting keep the existing auth ordering.
Tests: add a bridge regression test for the MFA auth order and extend the terminal starter test to assert the host option is forwarded.
MCP host_open unconditionally surfaces and focuses the main window,
which interrupts whatever the user is doing when an AI agent connects
to a host in the background. Add a toggle in Settings -> AI -> External
MCP (defaults to on, preserving existing behavior) so users can opt out.
* feat(settings): support local CJK fonts
* refactor: move CJK font rules to domain
* fix(settings): improve CJK font preview safety
* fix(settings): refresh local font previews
* fix(settings): improve font picker keyboard behavior
* fix(settings): handle unavailable font detection
* fix(settings): cover font picker edge cases
* fix(settings): quote custom font families safely
The onTerminalOutput handler had two near-identical branches (with/without an
external onTerminalOutput callback), each inlining the same
/password|passphrase|口令/i test that runs on every output chunk. Collapse them
into a single handler and hoist the pattern to a module-level constant so it is
compiled once and not duplicated. Behavior is unchanged.
* fix: keep non-hibernated tabs active in background
* fix: scope background work to hibernation policy
* fix: bind background monitoring to its tab
* fix: measure hidden local workspaces
* fix(ui): replace native confirms in system manager panels
Windows native confirm dialogs steal focus after process/tmux/docker
destructive actions. Use SystemPanelConfirmDialog so the side panel
stays usable after confirm/cancel.
Fixes#2242
* fix(ui): clear system manager confirms on session switch
Drop pending process/docker confirms when sessionId changes so a
dialog opened for one host cannot execute against another.
* fix(ui): clear docker image action busy on session switch
Reset actionBusy when sessionId changes so a previous host's
in-flight remove/prune cannot leave the new session's confirm
dialog disabled. Guard stale completions with a generation counter.
Covers password-only, dual KI (TOTP / secondary password), password+KI
partial (mock model A), and password-full-skip-MFA gap (mock model C).
Live cases run against the lab host when SSH_MFA_LIVE=1.
Always try password before keyboard-interactive for the first factor. After
any partial success, prefer keyboard-interactive when the server still allows
it so multi-factor/EDR second factors work without a per-host switch. Remove
the requiresMfa UI and "remember MFA" checkbox; keep secondary-prompt
protections and hostId-aware password save.
Default stays password-first for ordinary hosts. Hosts with requiresMfa
prefer keyboard-interactive so EDR secondary factors are not skipped.
Secondary prompts can suggest enabling MFA mode for that host.
Reject recovered-trailing-garbage parses during upload verification, pad
stale fixed temp files before MOVE, and re-check the destination so a
successful upload cannot leave non-whitespace junk on non-truncating
servers.
Fail closed when the existing vault length cannot be read, re-read and
re-pad after upload to heal concurrent stale-length races, and reuse a
single .tmp path so cleanup failures cannot pile up full vault copies.
When MOVE is unavailable, fall back to in-place PUT padded to the previous
byte length with spaces. Non-truncating servers then leave only JSON-legal
whitespace, the canonical vault never disappears mid-replace, and concurrent
clients cannot observe an empty remote (#2223).
Avoid empty remotes when MOVE falls back to DELETE+PUT: do not in-place
PUT after a failed delete, and retain the temp recovery copy if the final
PUT fails after the old vault was removed (#2223).
Only fall back to DELETE+PUT after a successful temp write, and abort if
destination delete fails so a non-truncating in-place PUT cannot corrupt
or wipe the previous vault (#2223).
Some WebDAV servers overwrite existing files without truncating when the
new body is shorter, leaving leftover bytes that break JSON.parse on the
next download.
Upload now uses temp PUT + MOVE (DELETE + PUT fallback), and download
recovers trailing garbage when V8 reports a position after valid JSON.
Home/scp probes during Auto fallback or forced SCP can hang forever if
the remote accepts exec but never closes. Abort after 15s so the file
panel cannot stay stuck connecting.
Store host sftpFileProtocol on SSH sessions so openSftpForSession used by
Catty/MCP/clipboard respects SFTP-only or forced SCP without payload.
Destroy the local read stream when the peer ACK loses the upload race.
Auto/utf-8 first-open used lossy UTF-8 on non-UTF-8 $HOME bytes, so
Chinese homes opened as mojibake and failed to list. Strict-decode with
gb18030 fallback and promote session encoding for subsequent shell ops.
When writeAll fails after waitForAck is armed, stream close would reject
the ACK promise unhandled. Centralize write+ACK in writeAndWaitAck and
guard Promise.all losers in uploadFile.
Listen for local write-stream errors before opening the SCP channel, pass
encoding/signal into download metadata stat, and restore remote mode after
external-editor SCP uploads.
Track active SCP exec streams per client and close them when the handle is
released, so agent Stop/timeout cancels transfers without killing the terminal SSH.
Detect GB18030 basenames under auto encoding for SCP listings, pass encoding
into source stat for transfers, and allow POSIX backslash in SCP basenames.
Await stream and final ACK together, keep SCP remote-to-remote paths as
strings, decode source control-line names with session encoding, and cancel
SCP homeDir via abort signal.
Reject failed local writers during download, always use fresh local file size
for SCP headers, pass encoding into parent mkdir, and require `scp` on the
remote before advertising SCP-mode connectivity.
Arm final ACK before trailing NUL, pass encodings through transferBridge,
harden base64 listing fallback, encode control-line basenames, and fail open
when SCP shell probe cannot run.
Do not end terminal-reused SSH sockets when closing SCP panes, and pass the
session filename encoding through SCP read/write/stat/rename/delete/chmod paths.
Tear down underlying SSH clients on SCP close, pass AbortSignal into homeDir
probes, and use session filename encoding when quoting remote paths for shell/SCP.
Avoid invalid do; in list for-loops, preserve setuid/setgid/sticky when
parsing ls modes, and clear sftpSudo when the host file protocol is set to SCP.
Stage SCP AI downloads/uploads before replacing destinations, use metadata
for remote list sizes, include file protocol in reconnect cache keys, refuse
Auto→SCP fallback when sudo elevation was requested, and apply download
backpressure. Update reuse-credentials contract tests.
Some hosts (e.g. NAS/Synology root) expose SSH but not the SFTP subsystem.
Add hybrid SCP mode: shell-backed list/manage and OpenSSH scp -t/-f transfers
behind the existing SFTP UI, transfer queue, and sftp.* AI/CLI/MCP tools.
Hosts can choose Auto (SFTP then SCP), SFTP only, or force SCP. Include unit
tests and optional env-gated real OpenSSH integration tests.
Same-endpoint session rebind was replacing the pane in place and closing the
SFTP session mid-transfer. Force a new tab when pending/transferring tasks
still reference the current connection id.
Same-endpoint rebind on terminal focus change was replacing the active SFTP
connection in place, which invalidates promoted editor saves keyed by the old
connection id. Force a new tab when editors still reference the current one.
Do not reuse a healthy same-endpoint SFTP tab across terminal session focus
changes — proxy/jump can differ while host keys match. Capture the visible
path first and reconnect in place with remembered initialPath + new
sourceSessionId so the directory stays sticky without stacking tabs.
closeSession clears preload ready listeners for the session id. A React
effect alone can lose a race with xterm-driven startMosh on retry; arm
the UI ready subscription synchronously after cleanup and again right
before startMosh.
Do not advance the last source session id until auto-connect can actually
rebind, so closing an editor/dialog still switches SFTP to the focused
terminal session.
Session switches can share a saved host key while live credentials/endpoints
differ, so always rebind to the focused session. Restore the last browsed path
on that reconnect so multi-terminal use no longer drops back to $HOME.
A always-truthy backend wrapper made the no-ready-event fallback dead
code and left scripts waiting forever on older bridges. Surface the
method only when the preload API actually implements it.
Use the connect-time connection cache key for skip/reuse and path memory so
same-hostId endpoint switches are not mis-attributed. Only restore previous
path on reconnect when the target host matches the prior connection.
Do not cancel ready/startup work when disposeExitRef is cleared for
hibernate detach. Dispose the pre-start ready subscription when
startMoshSession rejects before attach so retries do not leak listeners.
When a menu cannot fit above or below the trigger, stop flipping it
off-screen. Prefer the side with more space and cap max-height so
overflow scrolls instead of clipping trailing actions.
Require the active tab's mapped connection key to match the focused host
before skipping auto-connect, and re-key last-browsed paths from the live
connection so host-picker switches do not poison another host's memory.
Subscribe to mosh ready before startMoshSession and drain a ready that
fires during the await after attach. Default moshSessionReadyListeners
in createPreloadApi so lightweight test contexts do not throw on close.
Fix DropdownContent side=top so the menu sits fully above the trigger
instead of covering it. Match QuickConnect continue/save menu styling
to TerminalAuthDialog (open below, same z-index).
Fixes#2194
Closing follow mode still dropped back to $HOME when switching terminals or
after SFTP reconnect. Reuse a healthy same-host tab and restore the last
browsed path on rebind/reconnect.
Fixes#2193
closeSession clears preload mosh ready listeners for the session id.
Re-register on every transition into connecting so retry/auto-reconnect
do not leave scripts stuck unready or replaying into the handshake PTY.
Codex review P2s:
- Dismiss the connection overlay on first handshake output so password/OTP
prompts remain reachable; only defer startup commands and connect/pending
scripts until mosh-client ready.
- Dispose the ready listener via disposeExitRef and closeSession so cancel
does not leak preload callbacks when exit is suppressed.
Do not treat shell verbs after git-status markers as directory chrome, and
only strip multi-word path prefixes for privilege commands or clear path
sigils so `git status` and `My Project ls` stay correct (#2191).
Mosh connects through an ephemeral SSH handshake PTY before swapping to
mosh-client. Marking connected and firing startup commands too early
sent snippet/script input into that handshake PTY, so run-on-connect
scripts and host startup commands never reached the real shell (#2199).
Tag handshake output with moshHandshake meta, emit mosh ready after the
client swap, and defer connected status + scheduleStartupCommand until
then.
Accept resolved trailing privilege commands in shouldRecordShellHistory so
themed multi-word dirs still arm. Scan no-space prompts without a cache,
and do not peel echo/ls prefixes as directory chrome (#2191).
When password-prompt assist is open, Enter is hijacked to paste the
session host password. Clipboard paste did not clear that pending state,
so after pasting a different password (e.g. nested SSH via jump host)
Enter could append the wrong secret and fail sudo (#2198).
Dismiss the assist on paste/typed content input so Enter submits what
the user actually provided.
Never promote painted same-token tails over a non-empty keystroke buffer
(g + git status stays g); only prefer the full line when history yields a
privilege command the buffer does not. Accept unicode/punctuated themed
directory names before su/sudo, and keep double-space glyph first words.
Prefer trailing su/sudo/doas when the themed prefix looks like a path
(including multi-word dirs). Avoid over-peeling double-space glyph prompts
and do not treat a trailing-padded cwd token named su as a command (#2191).
Do not absorb post-cursor paint when it looks like a space-prefixed
suggestion (git + status). Prefer the keystroke buffer for single-word
incomplete echo (su→sudo), while still using the live line when history
shortens a multi-word typed buffer (#2191).
When typed text is only a trailing argv of a recalled sudo/su command,
prefer the full live line. Apply lastPromptText before peel so multi-word
directory themes resolve the real command first (#2191).
Peel path chrome on prefixed themed prompts (⚡ ➜ ~ su -). Prefer live
when history shortens a typed command (sudo→su), and prefer the typed
buffer when live still ends with chrome+command (#2191).
Reject decoration-only cached remainders and single cwd tokens on prefixed
themed prompts. When an aligned buffer is only a mid-line prefix of the
full recalled command, prefer the expanded live line (#2191).
Use lastPromptText when boundary detection fails for prompts without a
trailing space, and expand submitted input across wrapped rows so mid-line
Enter still sees the full history-recalled command (#2191).
Skip non-executable desktop CLI candidates, keep Claude on the macOS
app-bundle path, and surface desktop-resolved absolute paths in the
External MCP copyable setup command without breaking PATH installs.
Allow glyph-only one-word su/sudo/doas history to arm password assist,
limit path decoration filtering to bare themed prompts, and expand past
the cursor so mid-line Enter still resolves the full recalled command.
Empty Enter on ➜/git-status decorations must not record cwd tokens or
status glyphs as commands, which would pollute history and false-arm
password assist (#2191).
Validate cached prompt remainders against the live detector split so a
partial cache cannot yield git-status chrome as the command. Accept
multiword userInput on complete Powerline prompts, and allow history
recording when themed chrome reconciles away from the command.
Prefer the live line when ↑/Ctrl+R replace a typed prefix, while still
keeping the keystroke buffer for direct-send and echo lag. Peel themed
prompt decorations via reconcile when lastPromptText is missing or stale
after cd so su/sudo still arm (#2191).
- Strip themed prompt chrome via lastPromptText before falling back to
live userInput; reuse getCommandToRecordOnEnter so decoration pollution
is never armed or recorded (#806/#2191).
- Soft-dismiss password assist on all printable keys except plain Ctrl+C
so AltGr/Option characters still switch to manual entry.
Shell history (↑) redraws the line remotely without updating commandBuffer,
so Enter never armed su/sudo assist. Resolve the submitted command from the
live prompt line when the keystroke buffer is empty.
Ctrl+C was soft-dismissing the picker (same path as typing a password),
leaving dismissedWhileArmed and blocking the next bare Password: line.
Hard-abort on interrupt and ignore Ctrl/Meta/Alt keys for soft-dismiss.
* fix(ai): stop OpenCode process spawn storms when unused (#2184)
OpenCode model catalog loads were spawning full servers for discovered
agents, skipping main-process cache, and not aborting on timeout—so
idle Netcatty could accumulate many opencode processes.
Only load catalogs for the selected agent, cache/coalesce list-models,
abort and tear down on timeout, and share a short-lived list-models
server pool per binary.
* fix(ai): address OpenCode list-models review findings
Restore the main-process env immediately after spawning OpenCode so an
idle list-models server cannot leave PATH/OPENCODE_BIN on a temp shim.
Key model caches and the list-models pool by catalog-affecting env so
profiles that share a binary do not receive each other's catalogs.
* fix(ai): tighten OpenCode model-cache invalidation
Include profile-affecting env in the renderer runtime model cache key so
OpenCode config switches do not reuse a stale catalog. Skip caching empty
list-models results in the main process so transient OpenCode failures can
recover before TTL expiry.
* fix(terminal): prevent osc7_cwd errors after su
OSC 7 setup installed a bare osc7_cwd PROMPT_COMMAND entry. After
non-login su, that string could be inherited without the function
definition, printing "osc7_cwd: command not found".
Use a guarded prompt hook, unexport PROMPT_COMMAND, and upgrade
existing v1 rc snippets in place when setup is re-run.
* fix(terminal): safe OSC7 rc upgrade when markers are incomplete
Do not rewrite/truncate shell rc files when the Netcatty OSC 7 block is
missing an end marker or markers are unbalanced. Append a complete v2
snippet instead so setup still installs tracking without dropping user
config after an open start marker.
* fix(terminal): harden OSC7 rc upgrade safety
Address Codex review findings for OSC 7 setup upgrades:
- Never truncate incomplete/unbalanced marker regions; append v2 only
- Detect a complete v2 block for idempotent recovery
- Resolve full symlink chains; atomic same-dir replace with mode preserve
- Build stripped+v2 content in one temp write so read-only configs cannot
lose the legacy block without receiving the replacement
* fix(terminal): replace OSC7 v1 blocks in place
Upgrade complete legacy OSC 7 blocks by substituting the marked region
at its original location instead of stripping and appending at EOF. This
keeps surrounding control flow (if/fi wrappers) valid and still writes
the full result atomically with mode preservation.
* fix(terminal): match OSC7 markers as whole lines only
Use trimmed whole-line equality for start/end markers so echo statements
that embed the marker text are not treated as block boundaries during
legacy upgrades.
* fix(terminal): preserve ownership on OSC7 rc atomic replace
When upgrading a legacy block via temp+mv, restore the original
uid:gid on the temp file before replacing so a root-run setup against
another user's HOME cannot leave a root-owned bashrc.
* fix(terminal): harden OSC7 PROMPT_COMMAND array and chown
Process every PROMPT_COMMAND array element when installing the guarded
hook so leftover osc7_cwd entries cannot duplicate OSC 7. Abort the
atomic rc replace when ownership cannot be restored, leaving the
original file untouched.
* fix(terminal): fix exported array PROMPT_COMMAND handling
Detect array PROMPT_COMMAND via the -a attribute even when combined with
-x, and stop force-unexporting PROMPT_COMMAND now that the guarded hook
is safe to inherit across su.
* test(terminal): update OSC7 assertions for export-preserving hook
* fix(terminal): fall back when OSC7 upgrade temp dir is not writable
If same-directory mktemp fails (writable file in a non-writable managed
home directory), leave the original config intact and fall back to the
append path instead of aborting setup with set -e.
* feat(ui): customizable dense toolbars with show/collapse/hide
Add a shared toolbar layout model so terminal, SFTP, and side-panel
action strips can right-click customize placement, overflow collapsed
items into the more menu, and persist order plus placement.
* fix(ui): harden toolbar layout placement after review
Address review findings: availability-aware reachability, skip
unavailable reorder neighbors, Popover overflow for nested SFTP menus,
path chrome outside customize context menu, and side-panel hide close.
* fix(ui): only close side panel when hide placement sticks
Return the next layout from setPlacement so hide-of-last-reachable
does not tear down the panel, keep an active hidden tab chip visible,
and rebuild terminal shown order with highlight collapse coercion.
* fix(ui): narrow SFTP overflow spill and close ⋮ after leaf actions
Respect user show/collapse/hide, then temporarily move non-pinned shown
actions into ⋮ when the SFTP toolbar is narrow. Close overflow after
leaf clicks while keeping nested bookmark/encoding panels usable.
* feat(terminal): configurable su/sudo password prompt assist (#2156)
Extend the existing confirm-to-fill sudo assist to arm on su as well, and
add a Terminal setting for off / quick Enter fill / WindTerm-like credential
picker. Picker candidates come from the host password plus keychain password
identities; secrets are never shown in the list or sent without confirmation.
* fix(terminal): tighten password prompt assist after codex review
- Hint mode only uses the session host password, never an arbitrary
keychain identity as a silent Enter target.
- Build autofill candidates from the effective session host so group-
inherited passwords participate.
- Clear a stale credential picker when the session disconnects.
* fix(terminal): more password-assist polish from codex review
- Do not swallow arrow keys while the quick-fill hint is pending; only the
multi-credential picker consumes them.
- Clear the picker overlay on hibernate and wake so it cannot outlive the
autofill controller.
- Show the resolved auth username on the host candidate row (identityId /
group identity), not a stale host.username after password dedupe.
* fix(terminal): keep password picker select callback stable
Avoid forcing TerminalView memo invalidation on every Terminal render by
using a stable useCallback for credential selection.
* fix(terminal): do not offer full keychain on unarmed sudo prompts
Picker mode only lists all password identities after a real su/sudo
command was armed. Unarmed explicit [sudo] lines still get host-password
quick-fill only, blocking remote prompt phishing of other credentials.
* fix(terminal): fix picker mode switch and armed picker test
Arm before inspecting the picker payload, and when switching settings
mid-prompt recompute whether the command arm is still valid so picker
mode can open the full list.
* fix(terminal): reopen password picker after a wrong credential
Keep the su/sudo arm active after confirm-to-fill so a rejected password
re-prompt can offer the full credential list again instead of falling
back to host-only unarmed assist.
* fix(terminal): only re-assist real auth retries after fill
Keep a short post-fill arm for wrong-password re-prompts, but require an
explicit [sudo] line or authentication-failure text before reopening
assist. Prevents sending the host password into child prompts after a
successful sudo (e.g. sudo mysql -p).
* fix(terminal): scope sudo assist away from child password prompts
Track whether the armed command was sudo or su. su still accepts bare
Password: lines; sudo only assists on [sudo] tags or user-scoped prompts
(password for … / 输入密码), not generic Enter password when sudo is cached.
* fix(terminal): tighten when the full credential picker may open
Exclude child-program prompts (Enter password, Password for user …).
Full keychain picker only for explicit [sudo] lines or armed su Password:
prompts; Kylin-style sudo bare prompts still get host-password hint only.
* fix(terminal): limit full credential picker to armed su only
Multi-identity picker is the WindTerm-style su workflow (#2156). Sudo
keeps host-password quick-fill only, so a cached sudo child cannot open
the full keychain list via a forged or nested password prompt.
* fix(terminal): derive password assist candidates in Terminal + mode gate
- Standalone Terminal popups without TerminalLayer now resolve candidates
from host/keys/identities so su picker mode still works.
- When switching to picker mid-prompt, keep the su-only full-list gate
(do not open multi-identity list for an armed sudo prompt).
* fix(terminal): reject SSH-style prompts for su assist and drop dupe keys
- su bare matching only accepts short Password:/密码 lines, not user@host
SSH password prompts after passwordless su -c.
- Remove duplicate sudoAutofillCandidates keys that failed eslint.
* fix(terminal): cursor-anchor password picker and soft-dismiss Esc
Position the su credential picker with the same cursor-relative layout
helpers as autocomplete instead of fixed bottom-center. Esc only closes
the UI while keeping the arm so Esc/arrows can reopen the list on the
same Password: prompt.
Selected list borders were eating into content and looking like extra
left padding. Use inset primary ring (no layout shift) and reduce list
row horizontal padding from px-3 to px-2.
Unselected rows keep hover-only chrome with no extra border/bg.
Selected list/tree add a primary border; grid only recolors the
existing soft-card border to the accent color.
Selection was easy to miss because list mode reused hover glass fill.
Use full primary border, light primary wash, and ring so focus reads
clearly in grid, list, and tree.
Include hostClickBehavior in VaultView memo equality so the settings
toggle applies immediately, clear focus when the mode changes, and
apply select-then-expand to tree group rows with focus highlighting.
Add a Settings > Appearance vault option that defaults to single-click
connect, but can switch to select-then-click-again to connect. Grid mode
shows an accent ring on focus; list/tree reuse the hover fill. Groups
follow the same pattern when the option is enabled.
Store probed --version on ExternalAgentConfig from settings resolve-cli
and discovery enable, and prefer that when gating GPT-5.6 so custom
paths not on PATH still unlock Sol/Terra/Luna when compatible.
Hide minCliVersion presets when CLI version is unknown so pre-discovery
cannot default to Sol on old installs. Require exact path matches for
filesystem command configs so PATH discovery cannot gate a different
manual Codex binary.
Netcatty is BYO-CLI: upgrading codex-sdk does not replace the user's
binary. Hide Sol/Terra/Luna when discovery reports Codex CLI < 0.144.0
so old installs do not auto-default to an unsupported model.
Address PR #2170 Codex review: stop auto-selecting the last thinking
level (which defaulted Sol to ultra), add defaultThinkingLevel from the
official catalog, and keep gpt-5.2 selectable so stored model IDs do not
silently fall back to Sol.
Surface GPT-5.6 Sol/Terra/Luna in the curated Codex model picker and
bump @openai/codex-sdk to 0.144.3 so the bundled CLI meets the official
minimal client version for those models. Align reasoning efforts
(max/ultra) with the Codex catalog.
Fixes#2169
Always run filter, erase-scrollback, and paste prepare/cleanup so long
multi-line pastes still clear residual cells under large-output pressure.
Bulk-plain only skips prompt cosmetics when the post-paste stream is plain.
Decide bulk-plain shortcut after the stateful filter so retained CSI
prefixes still get erase/prompt transforms. Yield inside flood-merged
steps on hop count or WRITE_QUEUE_TURN_BUDGET_MS wall time.
Always run stateful filterTerminalSessionData before bulk-plain shortcuts so
split CSI continuations are not emitted as text. Cap synchronous recursion
inside flood-merged write steps to avoid Maximum call stack size exceeded.
Do not skip forcePromptNewLine preparation when bulk-plain degrade is active.
Bulk highlight catch-up only waits on time-bounded largeOutput, not sticky
longLine, so a single long line cannot leave a permanent 50ms poll.
Split timestamp skip from general bulk degrade. Full-scrollback multi-line
output (docker ps) no longer drops gutter markers; only high-rate floods
(>=64KB/100ms) or long lines suppress registerMarker storms.
Under large-output pressure, plain batches skip paste/prompt/filter prep and
go straight to xterm (bytes unchanged). Keyword highlighting no longer
schedules per-write scans during bulk — one quiet catch-up after pressure
drops. Sizable bulk queue items also yield after the write callback so paint
and input can interleave, matching Tabby FlowControl intent.
Lower the high-rate threshold to 16KB/100ms and treat multi-line writes on
a near-full scrollback as large-output immediately so second dumps skip
timestamp/highlight work. Extend the quiet window while saturated so
prompt gaps do not reopen the expensive path between seq runs.
Cap plain xterm writes (including short-line dumps like seq) to ~128KB and
yield after each intermediate slice so the UI can paint and accept input.
Under large-output pressure use the flood coalesce cap and rAF scheduling
instead of microtask packing. Leave SSH flow high-water at 1MB for #1961.
- abortTerminalWriteCoalescer always clears alt-screen schedule state so a
direct oversized enter-alt probe cannot stick after Ctrl-C drops the batch
- flood merge no longer forces a timer after every shard; inter-step yields
honor per-step yieldAfter (drain-budget pacing from writeLargeTerminalBatch)
Call resolveScheduleMode before early-returning when pending exceeds the
byte cap while auto-flush is gated (hidden pane). Without this, enter-alt
CSI in oversized hidden batches never latched rAF for follow-up repaints
after reveal.
Abort drops enter-alt CSI that never reaches xterm, so reset schedule
latches; treat ESC c (RIS) as leaving alternate so normal output returns
to the microtask path.
Drop enter-pending once the alternate buffer is seen or after an unobserved
exit (RIS), and reprocess ESC/C1 bytes that abort an incomplete private CSI.
Replace raw 16-char tail retention with a phase/params parser that handles
7-bit and 8-bit CSI in encounter order, long split sequences, and oversized
batches that bypass the coalescer frame arm.
xterm parses enter-alt-screen asynchronously, so buffer.active can stay
normal after ESC[?1049h is written. Keep frame scheduling until the
alternate buffer is observed (or leave-alt is seen).
xterm accepts \x9b?1049h as well as ESC[?1049h; force rAF for both forms
so the first TUI repaint is not microtask-scheduled while still on the
normal buffer.
Keep incomplete private CSI tails across coalescer flushes without
quadratic pending joins, and measure bulk-output rate with a true rolling
sample window so high-rate bursts are not missed at window boundaries.
Avoid control-character regex for alt-screen detection, join pending PTY
chunks so split CSI still upgrades to rAF, and reset timestamp segmenter
state when marker work is skipped under output pressure.
Restore debounced keyword refresh while panes are hidden so decorations
still apply after reveal, and force rAF scheduling when output enters the
alternate screen (including the first burst before buffer.active flips).
Reduce stutter from tiny write shards and vsync batching while keeping flow
control, TUI rAF coalescing, and interrupt safety intact. Bulk dumps use larger
shards, fewer yields, microtask scheduling on the normal screen, and degraded
timestamp/highlight side work under output pressure.
#2106 rewired the workspace toolbar X to detach the pane into a
standalone tab, duplicating the existing detach control. Restore X to
closeSession so it closes the pane again; detach stays on the arrow
button.
Expose Settings > Terminal > Normalize terminal text on copy (default on).
When off, all copy paths use raw xterm getSelection(); when on, strip
display padding and join soft wraps as before.
- Compact disposed timestamp entries on every write so clears cannot leave
a huge dead store while the gutter is off.
- Select bulk markers by measured visual rowOffset window rather than
timestamp count, so soft-wrapped floods do not pin out-of-buffer markers.
Drop ambiguous mid-token alnum continuation for padded wraps. Keep strong
continuations (path punctuation, hyphen at split, CJK) and treat multi-space
padding after a finished token as a word boundary.
Queue dropped/duplicate markers and dispose them with a per-pass budget
instead of abandoning forever or mass-disposing in one frame. Keeps
rewrite/scroll-region sessions from leaking unbounded xterm markers
while avoiding O(n²) freezes on flood output.
Deduping rewritten marker.line entries no longer calls dispose() for each
older duplicate. Mass dispose is O(n) per marker in xterm and can stall
rewrite-heavy output; abandoned markers leave with scrollback trim.
DECSTBM regions recycle rows without trimming normal scrollback. Bulk
row-offset measurement would invent history and prune still-valid labels,
so fall back to segmented marker registration when margins are active.
Collapse multi-space TUI padding between ordinary words to a single space so
Pi-style wrapped prose stays readable. Keep strong token continuations (CJK,
path punctuation, URL query) tight, and only treat URL mid-token alnum joins
as tight when padding is multi-space (not a single separator after a URL).
Codex review follow-up:
- Stop wiping prior timestamp entries when a bulk batch hits capacity;
DECSTBM regions can recycle rows without trimming scrollback.
- Throttle onScroll only under time-bounded largeOutput pressure so
sticky longLine state cannot lag user scrolling after output stops.
Codex review follow-up:
- Only register the last capacity timestamps in a bulk flood batch, and drop
excess store entries without per-marker dispose (xterm dispose is O(n)).
- Size capacity as scrollback + viewport with headroom above the unlimited
scrollback cap so oldest still-visible rows keep labels.
- Do not treat sentence punctuation as path continuations
- Scope URL/path detection to the trailing token only
- Cover Hangul and CJK punctuation on both sides of the wrap
Codex review follow-up:
- Treat xterm onScroll as throttled under large-output pressure so
output-driven scroll events cannot bypass the gutter paint budget.
- Collapse duplicate marker.line entries to the latest label before
applying the timestamp capacity cap.
- Prefer the latest marker label when cursor reposition rewrites an earlier
line (entries are not always sorted by marker.line).
- Advance the flood throttle clock on every render attempt so same-second
labels still rate-limit viewport rescans during sustained output.
- Prefer getTrimmedLength() columns when deciding full-row vs partial selection
- Apply token-continuation checks before single-space word-boundary handling
- Detect URL/path context so mid-token wraps do not gain spaces
- Do not invent spaces for URL/path/CJK token continuations across padding
- Keep a single space only for clear word-boundary soft wraps
- Preserve explicitly selected trailing spaces on partial last rows and column selections
Keep always-on timestamp semantics, but cut flood-path work:
- Merge validate+measure into one pass and fast-path printable ASCII
CR/LF floods (seq-style) without escape/unicode scans.
- Binary-search sorted marker entries for the viewport window.
- Reuse gutter row DOM nodes and throttle paints under large-output
pressure while keeping scroll/resize immediate.
- Collapse soft-wrap padding to a single word separator (no CJK space insert)
- Intercept the native copy event so Edit>Copy uses the same normalization
Always-on per-line timestamp markers used an O(n) filter on every
scrollback dispose, which made seq 1 500000 freeze the UI and back-pressure
SSH flow control. Keep always-on history for gutter expand, but compact
disposed entries lazily, cap capacity to scrollback, intern same-second
labels, and skip listener notify when the gutter is closed.
xterm getSelection() turns every physical buffer row into a hard newline, so
soft-wrapped TUI output (e.g. Pi agent responses) pastes with broken wraps and
display padding. Rebuild clipboard text from buffer coordinates: trim row
padding, join isWrapped rows, keep real hard breaks.
Use the helper on copy-on-select, context/hotkey copy, paste-selection, and
add-selection-to-AI paths.
Fixes#2162
- Keep unlocked encrypted keys eligible after passphrase retry even when
the target is password-only (jump-chain retry path).
- Fire onAuthAttempt from the simple ordered auth handler so jump/SFTP
progress logs still update for password-only sessions.
Direct terminal sessions still tried ~/.ssh default keys after a saved
password failed, while jump-host and SFTP paths (buildAuthHandler) only
tried password. That made a wrong/stale host password look fine on direct
login but fail ProxyJump and SFTP until credentials were re-saved (e.g.
via Keychain).
Align startSession with #266 password-only policy and pin buildAuthHandler
behavior with regression tests.
Fixes#2079
Only suppress KI password auto-fill when the password method already
succeeded as a prior factor (EDR step-up). publickey/agent partialSuccess
still allows filling the saved host password into a following Password:
challenge (AuthenticationMethods publickey,keyboard-interactive).
Do not suppress saved-password prefill for multi-prompt keyboard-interactive
forms just because the challenge name mentions Duo or two-factor. Prefill
still targets only the password slot; skipAutoFill covers true second factors.
autoFilledOnce still blocks re-prefilling a stale login password into the
retry modal, but no longer hides the save checkbox. Saving is suppressed
only after partialSuccess or for an explicit single secondary/OTP field.
createOrderedStringAuthHandler no longer permanently consumes methods
that are merely absent from the current methodsLeft list. After a
publickey partialSuccess the server can re-request password even if it
was skipped earlier when only publickey was advertised.
allowSavePassword only turns off for post-first-factor rounds or a single
secondary/OTP field. A combined Password: + Verification code: challenge
still offers saving the password slot (Codex P3 on #2151).
Pass allowSavePassword=false for post-partialSuccess / EDR / multi-round
keyboard-interactive challenges so the modal cannot overwrite the host
login password with a secondary secret (Codex P2 on #2151).
Expand secondary-password regex to allow words between "secondary" and
"password", and scan keyboard-interactive name/instructions so Chinese
"二次认证密码" banners still block auto-fill when only the English field
label is in the prompt text.
Multi-round keyboard-interactive exchanges may not report partialSuccess
between prompts. After the first auto-fill, later password-looking rounds
open an empty modal so an EDR secondary secret is not pre-filled.
Track partialSuccess on simple/agent ordered auth handlers, omit saved
password from second-factor modal payloads, and align modal prefill
deny-list with the backend MFA vocabulary.
Do not auto-fill keyboard-interactive challenges that look like
secondary/EDR passwords, and skip saved-password auto-fill after a
first-factor partialSuccess so step-up prompts always surface the modal.
When the host info bar is hidden, the script execution banner used a
fixed top:34 under empty space and full right edge under the circular
toggle. Raise it and reserve a right gutter so it sits beside the dial
instead of being pushed under it.
Replace the dense radial-dot “chessboard” grip in the hidden-host-info
speed-dial with a lucide GripVertical control sized like the other
toolbar icons, so drag remains available without looking like a grid
button.
When the host info bar is hidden, the circular action toggle is an
overlay. Reserving a right inset for it pushed the xterm scrollbar
inward and left a dead strip beside the button. Keep the normal body
inset only so the dial floats on top of the terminal.
Match toggle and action pill at h-7 with the same border/shadow treatment,
swap the dense dots for ChevronsLeft / X, and pad the expand clip box so
the soft downward shadow is no longer sliced by overflow-hidden.
.terminal-topbar sets container-type:inline-size, which size-contains the
inline axis so the spring-out tray stayed width 0 and no controls appeared.
Expand with a 0fr→1fr grid instead, drop that class from the tray, shrink
the circular toggle, and use three horizontal dots.
Replace the cramped top-right strip with a circular toggle. On click, the
full action toolbar springs out to the left as a rounded pill (scale +
slide + fade), so every control is visible instead of stacking on the
dots button.
When the host info bar is hidden, the action strip used to open at the
same top-right corner as the dots toggle. That stacked the first control
(host keyword highlight) and its tooltip on the toggle, so it looked like
only “关键词高亮” appeared. Open the full toolbar as a bordered panel
under the toggle instead.
When the host info bar is hidden, the action cluster sits at the top-right
edge. Opening host keyword highlighting with side=top clipped the panel
and left the tooltip stacked on the trigger. Open both below the toolbar,
align the panel to the end of the cluster, and suppress the tooltip while
the panel is open.
Raise the packaging floor past 0.1.5 so Netcatty pulls host-before-ack
Confirm, Pending-continue, and related mosh-client fixes. Local resources
fetch to moshcatty-0.1.6 (binaries remain gitignored).
Raise the packaging floor from 0.1.4 to 0.1.5 so Netcatty bundles the
stock-aligned Diff-path speculative echo fix for high-latency typing.
Handshake diagnostics already landed via #2128.
When SSH exits without a parsed MOSH CONNECT line, users only saw the
mosh-server banner and "[Mosh session closed (code 0)]", which looked
like a successful session that immediately died (#2121 residual
connect failures).
Print an explicit handshake-failure hint and put a matching error on
the exit payload. Document that speculative local echo lives in
MoshCatty 0.1.5+ (prediction is not implemented in the renderer).
Workspace toolbar X was calling closeSession and destroying the pane
instead of returning it as its own tab. Route that control through
detach when available, harden close/detach against stale session state,
and record the workspace id in tabOrder when drag-to-split creates a
workspace so later detach can put tabs back correctly.
#2096 gated split layout on viewMode === "split", but drag-to-split
workspaces from createWorkspaceFromSessions leave viewMode undefined
(default tiled split). Visible panes then fell back to full-size stacking
and looked like a broken split. Treat any non-focus workspace as split
geometry, matching pre-2096 isSplitViewVisible behavior.
Refresh AppImage SRI hashes for the re-published v1.1.62 release assets
(including the SFTP opaque-background fix). Release workflow cannot push
directly because main requires a pull request.
After continuous terminal rendering (#2096), inactive terminal panes stay
visible under other surfaces. SFTP had no solid background, so terminal
content bled through when switching from a terminal tab. Use bg-background
so the layer is opaque and still follows the active theme.
Mirror the published AppImage SRI hashes from the v1.1.62 release so Nix packaging tracks the new version. The release workflow could not push this update directly because main requires changes through a pull request.
When the terminal side panel is docked on the left, default top tooltips
for the tab rail (SFTP, scripts, etc.) rendered under the traffic lights.
Match the host-tree toolbar and force side="bottom" (#2095).
* feat(mosh): default bundled client to MoshCatty releases
Wire Netcatty packaging and npm run dev to pull pure Rust mosh-client
binaries from binaricat/MoshCatty (moshcatty-* tags), document the cutover,
and mark the old Cygwin Windows fetch path as legacy-only.
* fix(mosh): accept pure MoshCatty packages (exe-only, no dlls/terminfo)
Align fetch/packaging with MoshCatty pure-Rust releases: require only
mosh-client[.exe], treat Cygwin dll bags and terminfo as optional legacy
layout. Document moshcatty-0.1.1 (ConPTY Ctrl+C + static MSVC CRT).
* refactor(mosh): drop Cygwin legacy; MoshCatty-only pipeline
Remove FluentTerminal/mosh-bin fallbacks, build-mosh C++ CI, and
dll/terminfo runtime helpers. Fetch accepts only moshcatty-* pure
binaries; packaging ships a single mosh-client per platform.
* docs: align ET checklist and et README with MoshCatty-only mosh
Update mosh template references after removing Cygwin build pipeline and
legacy fallbacks. Point ET packaging notes at fetch-mosh / MoshCatty
instead of deleted build-mosh-binaries workflow.
* docs(mosh): require MoshCatty 0.1.2+ for Linux glibc floors
Codex review vs main: defaulting to MoshCatty Linux binaries built on
ubuntu-latest required GLIBC 2.34, above Netcatty's package floors
(x64 2.28 / arm64 2.31). Document the floors, prefer moshcatty-0.1.2+,
and drop trailing whitespace in the design note. Upstream MoshCatty
release CI now builds in almalinux:8 / debian:bullseye with assert.
* fix(mosh): reject MoshCatty releases below 0.1.2
Codex re-review: docs alone still allowed MOSH_BIN_RELEASE=moshcatty-0.1.0/0.1.1,
which produce packages that fail to start on Netcatty's Linux glibc floors.
Validate the minimum tag in resolve and fetch, skip pre-0.1.2 when picking
latest, and cover the reject path in unit tests.
* fix(mosh): pin MoshCatty owner and reject floor prereleases
Codex review vs main (P2):
- Always default MOSH_BIN owner/repo to binaricat/MoshCatty; do not
inherit fork owner from GITHUB_REPOSITORY in resolve or fetch.
- Treat moshcatty-X.Y.Z-rcN as below final X.Y.Z so pins like
moshcatty-0.1.2-rc1 cannot bypass the glibc floor.
* fix(mosh): accept build metadata in MoshCatty release tags
TAG_RE rejected '+' so moshcatty-0.1.2+build.1 failed validation even
though the version parser and isAtLeastMinRelease accepted it. Align
the regex and require a successful semver parse.
* Fix system-to-dark theme race
* Cover peer windows in theme race fix
* Exercise production theme sync path
* fix(settings): announce all changed appearance fields over IPC
Theme sync only broadcast STORAGE_KEY_THEME, so receivers that hit the
stale-storage race for UI theme or accent kept the old value. Emit a
keyed notify for each appearance field that actually changed so the
reducer race guard covers light/dark UI theme ids and accent state too.
* fix(settings): keep current appearance fields across keyed IPC
Sequential notifies for one multi-field appearance change were
re-reading non-announced keys from still-stale storage, so a later
UI-theme IPC could revert an earlier theme update. Preserve in-memory
current values for non-matching keys, compose IPC applies via a ref,
and cover the multi-notify sequence in tests.
Build Linux x64 native modules in an AlmaLinux 8 environment with a newer toolchain while keeping the RHEL 8 compatibility target. Stabilize RPM and pacman packaging in that container, and harden bundled mosh/ET binary builds against transient dependency download failures.
After the 1MB high-water mark fix, bulk dumps like `tail -2000f` still slowed
down because pause/flood mode shrank the coalescer to 8KB batches. That made
the renderer drain the 1MB backlog slowly, so each pause/resume RTT stayed
long and total time regressed toward ~10s on WAN links.
Keep the full bulk coalesce size while flow is paused so the backlog drains
fast enough to resume the SSH channel quickly; only use the flood cap when the
write queue is flooded but flow is still open. Also raise flood coalesce to
128KB and per-turn queue drain to 256KB so plain-text floods do not shard into
tiny setTimeout yields.
Show the active write permission mode as a dedicated status row, and move the
lengthy how-to / security copy into a header help tooltip so the card stays
scannable. Also fix usage copy that incorrectly said the toggle was "below".
Agents could list vault hosts and run commands on open sessions, but could not
open a host themselves. Add vault.host.open (MCP: host_open) that opens a
terminal tab via the vault agent bridge, merges the new session into MCP scope,
and documents the flow in the Catty system prompt.
Also redesign External MCP client configuration into segmented tabs with
shared copyable code blocks so setup matches the settings design system.
* fix(ai): probe remote login shell before PTY wrappers (#1854)
SSH/Telnet sessions never set shellKind, so AI exec defaulted to the
posix wrapper and broke fish login shells with "Unsupported use of '='".
Probe the remote login shell once via a silent SSH exec channel before
AI/MCP PTY execution, cache session.shellKind, and leave et/mosh
shellKind unset so they can take the same path. Fish wrapper coverage
includes a real-fish binary regression test.
* fix(ai): harden remote shell kind probing
* fix(ai): keep PowerShell prompt override after posix login probe
Do not pin probed posix login shells onto session.shellKind. Unset still
defaults to posix wrapping, while leaving the kind open preserves the
live PS ...> prompt override for bash/zsh login shells that entered
pwsh interactively (Codex P2 on #2061 / issue #841 path).
* fix(ai): make remote shellKind probe cancellable before PTY write
Register a pending activePtyExecs marker for the pre-exec shell probe so
Stop during the probe window latches cancellation and does not still type
the command after the probe resolves (Codex P2 on #2061). Wire through
Catty, MCP, and worker foreground exec paths.
* fix(ai): release session lock if job shell probe rejects
terminal_start reserves the session before ensureSessionShellKind; if that
promise rejects, the previous .then-only path never released the lock.
* fix(ai): cancel pending terminal_start during shellKind probe
Register background job entries before the remote shell probe so chat
cancel can latch and abort job start before startPtyJob types into the
PTY (MCP + worker paths; Codex P2 on #2061).
* fix(ai): do not pin login shell as active; default to posix_sh
Login-shell probe no longer sets session.shellKind for fish/posix — only
powershell/cmd are pinned. Unset shellKind resolves to posix_sh (sh -c
inner POSIX wrapper), which both fish and bash interactive shells can
parse, fixing #1854 without assuming login shell === active shell
(Codex P2 on #2061).
* fix(ai): use login shell as soft hint, not sh -c / dash
Drop posix_sh default that ran commands under /bin/sh (often dash) and
broke bash/zsh source, [[ ]], and functions. Keep fish/posix probe as
session._loginShellKind soft hint for resolveEffectiveShellKind so fish
login still gets the fish wrapper and bash/zsh keep the native posix
wrapper, while live PS prompts can still override.
* test(ai): cover shell probe cancellation gaps
* fix: build Linux x64 packages on Debian Buster for glibc 2.28
Rebuild node-pty (and serialport) inside debian:buster so x64 releases
load on RHEL 8 / CentOS 8 / UOS / Deepin instead of requiring glibc 2.35
from ubuntu-22.04. Mirrors the arm64 containerized build pattern (#2062).
* fix: run Buster build-deps step with bash
Container jobs default to /bin/sh; dash does not support [[ so the
archive.debian.org rewrite would be skipped and apt-get update would fail.
* fix: disable apt Valid-Until for archived Buster mirrors
archive.debian.org security metadata for Buster is expired; without
Acquire::Check-Valid-Until false, apt-get update fails in the container.
* fix: install Python 3.11 for node-gyp on Buster x64 builds
Buster ships Python 3.7; node-gyp 12 uses walrus syntax and fails during
npm ci when rebuilding node-pty. Keep the glibc 2.28 container and supply
a modern interpreter via actions/setup-python.
* fix: install Python 3.11 via uv inside Buster x64 builds
actions/setup-python is not reliable on glibc 2.28 containers. Install a
manylinux CPython with uv so node-gyp 12 can rebuild node-pty during npm ci.
Keep only the confirmed product fixes for 奇安信-style auto-login:
1. Seed startup screen from viewport or live buffer so on-screen menus
stay waitable (never mark the whole buffer consumed at script start).
2. sendLine emits body then CR separately, matching real keyboard input.
3. After sendLine, consume only pre-send buffer length so peer prompts
that arrive between body and Enter remain matchable for the next step.
Same-host Connected preference depends on session order; ignoring
reorder left the picker bound to a stale sourceSessionId.
Co-authored-by: Cursor <cursoragent@cursor.com>
Connected picker names a specific live session; renderer username/port
can lag the authenticated bridge endpoint, so reuseOnly must not reject
on endpoint mismatch before attaching to that session.
Co-authored-by: Cursor <cursoragent@cursor.com>
When session.port is undefined, use SSH default 22 instead of a vault
host.port that may have been edited after the terminal connected.
Co-authored-by: Cursor <cursoragent@cursor.com>
Host-ready only refreshes an already-issued external token so cold-start enable cannot write a short-lived discovery token before resolveHostCredentials rotates it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Endpoint-only Connected picker attempts must not fall through to a
fresh SSH dial. Also correct zh-CN/zh-TW Connected section labels.
Co-authored-by: Cursor <cursoragent@cursor.com>
Ignore Catty/CLI authToken from onBridgeHostReady and re-assert the cancel flag after scoped metadata cleanup so soft-revoke stays intact.
Co-authored-by: Cursor <cursoragent@cursor.com>
Try endpoint-only openSftp with sourceSessionId before getHostCredentials
so Connected picker rows work when saved password/key is missing or
undecryptable on this device.
Co-authored-by: Cursor <cursoragent@cursor.com>
Issue a rotatable external-only TCP token on enable, soft-revoke it on disable without killing long-lived stdio sockets, force external sockets onto the reserved chat scope, and only seed empty external metadata when the scope key was never written.
Co-authored-by: Cursor <cursoragent@cursor.com>
Reject disabled/in-flight scope resurrection, move session sync to App mount, cancel the external chat session on stop, and broadcast approval cards to main + settings windows.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only hide a vault host when its endpoint matches the live Connected
entry, so post-connect host edits still offer the new saved target.
Co-authored-by: Cursor <cursoragent@cursor.com>
Skip peer/settings windows, listen for cross-window enable, only mark payloads sent after success, and mount the MCP approval IPC bridge at app startup before TerminalLayer lazy-loads.
Co-authored-by: Cursor <cursoragent@cursor.com>
Sudo SFTP never reuses a terminal shell connection; advertising those
hosts as Live would fall back to a fresh auth while hiding the normal row.
Co-authored-by: Cursor <cursoragent@cursor.com>
Vault host edits after connect must not break sourceSessionId reuse;
overlay hostname/username/port from the terminal session onto the picker host.
Co-authored-by: Cursor <cursoragent@cursor.com>
Serialize enable/disable, cancel in-flight external work on stop, refresh discovery env for live permission mode, sync all terminals from TerminalLayer, host confirm approvals globally, and treat Claude local/project scope as upgradeable.
Co-authored-by: Cursor <cursoragent@cursor.com>
Skip rebroadcast when HTTP proxy settings are unchanged across windows, and force rejectUnauthorized onto the tunneled target TLS hop when allowInsecure is set.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep app-wide external hosts when a Catty sidebar push arrives, and re-read discovery permissionMode so long-lived stdio clients pick up Safety changes without restart.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the vault-host mosh/et filter so plain SSH terminals opened against
a mosh/et-default host (e.g. ssh:// deep links) still appear as reusable.
Co-authored-by: Cursor <cursoragent@cursor.com>
In terminal-worker mode mirror renderer session payloads into
__external_mcp__ and seed from other chat scopes when the live map is empty.
Co-authored-by: Cursor <cursoragent@cursor.com>
Lets Windows users (and others) route Google Drive / OneDrive / GitHub sync and AI provider traffic through system or custom proxies without touching SSH ProxyJump settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
In terminal-worker mode the main-process sessions map can be empty; avoid
overwriting renderer-pushed __external_mcp__ metadata with an empty sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
Merge prior scoped host metadata when syncing live sessions, and parse
Claude Environment: KEY=VALUE blocks so configured entries are not
misclassified as missing discovery env.
Co-authored-by: Cursor <cursoragent@cursor.com>
Do not fall back to the expected launcher path when the existing Grok
entry carries args; that masked conflicts as configured.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep disabled same-name entries visible to classifiers so one-click setup
can mcp remove them before writing the discovery-env launcher config.
Co-authored-by: Cursor <cursoragent@cursor.com>
Exclude connecting and Mosh/ET sessions so Connected rows always map
to a live ssh2 shell that SFTP can reuse without a fresh login.
Co-authored-by: Cursor <cursoragent@cursor.com>
Parse Scope/Args from `claude mcp get`, remove the blocking scope before
re-adding with discovery env, and treat non-empty Args as a conflict.
Co-authored-by: Cursor <cursoragent@cursor.com>
When multiple live terminals share a hostId, pick the later same-status
session so Connected picker reuse targets the newest endpoint match.
Co-authored-by: Cursor <cursoragent@cursor.com>
Treat launcher-only Codex/Claude/Grok entries as not_configured so one-click
setup can remove and re-add them with NETCATTY_EXTERNAL_MCP_DISCOVERY_FILE.
Co-authored-by: Cursor <cursoragent@cursor.com>
Always show reserved-scope approval cards in Catty, stop rewriting
discovery after startup errors, and fail closed when an explicit discovery
env path is missing instead of falling back to another profile.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop unsupported `-s` from `claude mcp get`, treat only starting/running as
enabled for RPC gates, and reject launcher commands that carry extra args.
Co-authored-by: Cursor <cursoragent@cursor.com>
Disconnect marked external sockets on disable, await in-flight stop before
re-enable, write Claude MCP entries to user scope, and stop spawning client
CLIs on the quiet status poll.
Co-authored-by: Cursor <cursoragent@cursor.com>
Expose the existing catalog MCP as a Settings-managed local bridge with
discovery lifecycle, one-click client setup, and reserved external scope
so third-party agents can use Netcatty without a sidebar chat session.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use sftpPickerSessionsEqual in SftpView and SftpSidePanel memo
comparisons so OSC title updates do not force full SFTP re-renders.
Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid re-rendering mounted side panels on OSC title churn by comparing
id/hostId/protocol/status for the Connected picker, and ignore local
.omo run-continuation state.
Co-authored-by: Cursor <cursoragent@cursor.com>
Include sessions in the side-panel stable context equality check so the
Connected host list updates when terminal sessions connect or change.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep WindowOpacityButton in sync with Settings so 96–99% stays
selectable from the top-tabs control as well.
Co-authored-by: Cursor <cursoragent@cursor.com>
The appearance slider previously stepped by 5%, so 96–99% was unreachable
even though the clamp already allows any value in [0.5, 1]. Fixes#2019.
Co-authored-by: Cursor <cursoragent@cursor.com>
Let users pick currently connected SSH sessions from the SFTP host
picker and reuse the existing connection, including ephemeral deep-link
hosts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Local Terminal used a per-session hostId, so autocomplete history never
accumulated, and the store never imported ~/.zsh_history. Stabilize the
hostId and seed from local histfiles so prefix suggestions match Ghostty.
Closes#2037
Co-authored-by: Cursor <cursoragent@cursor.com>
Do not trim password values when validating auth forms or attaching
credentials to SSH connect options, so a single-space password works
(issue #2036).
Co-authored-by: Cursor <cursoragent@cursor.com>
Closing the hibernate setting must restore already soft-hidden or
hibernated tabs immediately, instead of waiting until the user selects
them again.
Co-authored-by: Cursor <cursoragent@cursor.com>
Solo tab switches only flip isVisible. Skipping that prop in Terminal's
memo left isVisibleRef stale, so hidden panes kept the visible write path
and #1995/#1996 tab-return recovery never ran (#1985).
Co-authored-by: Cursor <cursoragent@cursor.com>
Detect complete password prompts on the last line independently of the
whole-chunk promptCandidateBytes cap, so a large stale prefix plus a
sudo/password prompt is not dropped during Ctrl+C drain.
Co-authored-by: Cursor <cursoragent@cursor.com>
Attackers switched to hash-named zips with soft bait, so drop the
scoring heuristics and treat any zip/dangerous attachment from
non-maintainers as spam.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: let script waits match the visible startup screen
Auto-login scripts that wait for bastion menus already on screen were
timing out because startup snapshot sync marked the whole buffer as
consumed. Replace the script buffer with the current viewport so waits
can match visible text while still ignoring scrolled-off scrollback.
Fixes#1960
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: keep startup viewport waitable across sync races
Address Codex review on #2035: merge the visible snapshot with any
output that arrives during the snapshot IPC (e.g. bastion BEL), and seed
the whole viewport as fresh so waitFor* can match headers beyond the
512-byte tail slack on long menus.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: do not seed buffer-fallback scrollback as visible screen
When the renderer misses the snapshot request, requestScreenSnapshot
returns the full script buffer, not the viewport. Mark that path and
keep the consumed baseline so scrolled-off keywords cannot rematch.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: preserve live output during fallback snapshot sync
Baseline only the pre-sync buffer length on empty/fallback snapshots so
bytes that arrive while waiting for the snapshot stay matchable, matching
the viewport path's trailingFresh behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: keep waitForPrompt scoped to the live tail after seeding
Seeded full-viewport freshness is for waitFor/waitForText/waitForRegex
only. waitForAny still uses the 512-byte tail window so an older prompt
still visible above long-running output cannot satisfy waitForPrompt.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: advance scan offset when consuming a preserved startup prompt
waitForPrompt could resolve a seeded live-tail prompt without consuming
the viewport above it, so a later waitForText rematched older visible
lines. Advance scanOffset through the preserved prompt end.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: consume the full seeded viewport after startup prompt
When waitForPrompt resolves a preserved live-tail prompt, advance
scanOffset through the entire seeded snapshot — not just the prompt —
so later waitForText cannot rematch older visible text after the prompt.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: keep sync-race trailingFresh outside seeded viewport
seededLength must end at the visible snapshot so waitForPrompt does not
consume bytes that arrived during the snapshot IPC. Those trailingFresh
bytes stay matchable for later waitForText/waitForRegex.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: dedupe trailingFresh against blank-padded viewport snapshots
Full-viewport snapshots often end with blank rows after live content, so
a plain endsWith check missed sync-race duplicates. Compare after
stripping trailing blank lines before appending trailingFresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: let generic waitForAny match the full seeded viewport
Tail-only freshness is for waitForPrompt (allowPreservedTailMatch).
Generic waitForAny must still see menu labels near the top of a long
seeded startup screen, matching waitForText/waitForRegex.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: trim partial overlap from sync-race trailingFresh
When the viewport snapshot already contains a prefix of trailingFresh,
append only the non-overlapping suffix so waitForText cannot rematch a
phantom duplicate of the overlapped text.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: match partial trailingFresh overlap against full viewport
Use the unstripped viewport when trimming sync-race prefixes so an
overlapped trailing newline is not left behind as a phantom blank line.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: keep waitForPrompt from rematching short seeded prompts
After live output clears preservedTailMatch, currentTailFreshBoundary
must start at seededLength so a short startup prompt cannot satisfy
waitForPrompt through the normal 512-byte window.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: invalidate preserved prompts once scanOffset passes them
If waitForText/waitForRegex already consumed output after a preserved
startup prompt, clear that prompt via advanceScanOffset so waitForPrompt
cannot reuse it. markOutputConsumedThrough still keeps the one-shot
startup prompt for waitForPrompt.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: trim blank-padded overlap without dropping duplicate sync markers
Use syncStartText to keep genuinely new duplicate suffixes during snapshot
sync, while still trimming blank-padded and partial overlaps from trailingFresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: treat scrollback-backed stale viewports as pre-sync
When syncStart has scrollback above the visible suffix, a stale snapshot can
still equal that suffix without matching syncStart exactly — keep trailingFresh
so duplicate markers printed during the IPC round-trip are not trimmed away.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: invalidate startup seed after automated script input
sendLine/send must not leave the seeded viewport or preserved prompt
matchable, or common send-then-wait scripts can complete against pre-command
output. Keep sync-race trailingFresh beyond seededLength still waitable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: keep long sync-race output waitable after startup prompt
After waitForPrompt consumes the preserved prompt, re-seed remaining
trailingFresh so markers ahead of a >512-byte burst stay matchable. Also
consume all buffered bytes on automated input, including pre-input trailingFresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
When multiple restore controls are preserved, peel complete leading
restore sequences that are a suffix of preserved from the password
prefix so droppedBytes stay accurate and restores are not re-emitted.
Co-authored-by: Cursor <cursoragent@cursor.com>
Hold incomplete CSI with password prefixes, but drop standalone SGR
fragments so they cannot leak into a later shell prompt. Restore/OSC
prefixes remain holdable alone.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only strip a full preserved restore prefix from password holds, and when
discarding a mid-CSI held prefix also consume the CSI final byte so
shell prompts are not corrupted.
Co-authored-by: Cursor <cursoragent@cursor.com>
When a drain chunk preserves alternate-screen restore and also holds a
password prefix on the same line, strip the overlapping restore from
pending so droppedBytes stay accurate and restore is not re-emitted.
Co-authored-by: Cursor <cursoragent@cursor.com>
Styled prompts can split mid-CSI (Pass + ESC[ / 0mword:). Hold both
the password-prefix body and the trailing control prefix so the prompt
still completes across chunks during Ctrl+C drain.
Co-authored-by: Cursor <cursoragent@cursor.com>
Include the new resume reason in TerminalInterruptDisplayFilterReason
so typed returns from the one-chunk password path type-check.
Co-authored-by: Cursor <cursoragent@cursor.com>
Password prompts often emit nothing further until the user types, so
dropping a complete prompt while waiting for promptQuietMs leaves a
blank terminal. Resume immediately on complete password candidates.
Co-authored-by: Cursor <cursoragent@cursor.com>
Match password-prefix candidates after stripAnsi so SGR-styled chunks
like ESC[31mPass still hold across chunks, while keeping raw pending
bytes for display when the prompt completes.
Co-authored-by: Cursor <cursoragent@cursor.com>
Classify keyword bodies without punctuation/spaces so full-width
colon targets do not collapse minLen to 1 and hold lone "p"/"pa".
Co-authored-by: Cursor <cursoragent@cursor.com>
A held Pass prefix followed by \nPassword: is a fresh flood line, not a
same-line completion. Discard the prefix and keep quiet-gap so stale
prompts cannot bypass drain via the held-prefix resume path.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only bypass promptQuietMs when a held password-prefix chunk completes.
Fresh Password:/Enter password: lines in the flood wait for the quiet
gap like shell prompts, so canceled prompts do not resume drain early.
Co-authored-by: Cursor <cursoragent@cursor.com>
Detect trailing password-keyword prefixes at word boundaries so split
prompts like "alice@host's pass"+"word:" or "用户 的密"+"码" survive
Ctrl+C drain instead of being dropped mid-keyword.
Co-authored-by: Cursor <cursoragent@cursor.com>
Align complete-password-prompt detection with terminalSudoAutofill so
prompts like "用户 的密码" resume Ctrl+C drain instead of being dropped.
Co-authored-by: Cursor <cursoragent@cursor.com>
Limit held prefixes to true prompt-prefix forms so ordinary lines like
"Password authentication failed" are drained, and include discarded held
prefix bytes in returned droppedBytes so renderer flow ack stays accurate.
Co-authored-by: Cursor <cursoragent@cursor.com>
A held "Pass" that never completes as a password prompt must be dropped
before the generic shell-prompt matcher runs, otherwise "Pass$ " leaks
into the terminal after Ctrl+C drain.
Co-authored-by: Cursor <cursoragent@cursor.com>
Buffer probable password-prompt prefixes (e.g. "Pass") across chunks so a
split "Password:" / "[sudo] password…" is not dropped before it completes,
and resume immediately once the held prefix forms a complete prompt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Settings can probe ~/.nvmd/bin/codex.cmd, but the SDK path needs the real
native binary under versions/<ver>/. Without that override, packaged builds
fall back to optional platform deps we deliberately exclude.
Fixes#2027
Co-authored-by: Cursor <cursoragent@cursor.com>
Opening SFTP (or other side panels) can shrink the terminal in pixels
without changing cols/rows, so term.onResize never clears the atlas and
WebGL keeps painting stale glyphs as black squares (#2013).
Co-authored-by: Cursor <cursoragent@cursor.com>
Interrupt output gates only resumed on shell-style prompts, so a password
prompt arriving in the quiet window was dropped as stale flood. Treat
sudo/password prompts as resume points on both main and renderer gates.
Co-authored-by: Cursor <cursoragent@cursor.com>
Version opacity updates and skip rebroadcasting incoming IPC/storage
echoes so a fast drag in Settings no longer fights delayed main-window
values (#2018).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): prevent corrupt uploads from oversized fastPut chunks
Restore ssh2's 32KB default chunk size, verify remote size after upload,
and treat stream finish (not close) as success so truncated zip/tar.gz
transfers fail loudly instead of looking complete (#2022).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(sftp): wait for stream close after finish on upload fallback
Address Codex review: resolving on finish alone can miss close-time
server errors while the remote handle is still open. Require finish then
close, and fail on premature close.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
SSH key passphrase prompts previously checked "Remember this passphrase" by default; make it opt-in for safer defaults (#2024).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: make Windows Mosh handshake survive ConPTY and Cygwin terminfo
Windows ConPTY can inject CSI into MOSH CONNECT lines and exit without a
trailing newline, so the sniffer never swapped to mosh-client. Also convert
bundled TERMINFO paths to /cygdrive/... because Cygwin ncurses splits on ':'.
Fixes#2025
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: defer unterminated MOSH CONNECT parse until EOF
Avoid accepting a 22-char key prefix mid-stream before optional "=="
padding arrives; recover unterminated CONNECT only on SSH PTY flush.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Clarify Netcatty MCP environment context so external agents that read resources can see the terminal execution tools, and cover the MCP resource path with a test.
* Add telnet URL deep link support
* Fix telnet deep link review issues
* Keep ssh deep links independent from telnet registration
* Address Codex telnet deep link feedback
* Support JumpServer jms:// client launch links
Register Netcatty as an optional jms:// protocol handler (opt-in,
default off to avoid conflicting with the official JumpServer client).
SSH/SFTP connection payloads decode to an ephemeral host that connects
to the JumpServer gateway with the one-time connection token, without
saving anything to the vault.
Fixes#1958
* Mark jms deep link hosts as ephemeral for session restore exclusion
* Open the SFTP side panel for JumpServer sftp launch links
jms:// payloads with protocol "sftp" previously landed in a plain
shell. Mark the ephemeral host with an autoOpenSftpPanel hint that the
session factory forwards as autoOpenSidePanel, and let TerminalLayer
open the SFTP side panel for that session once it connects.
* Support JumpServer telnet deep links
* Keep JumpServer telnet links on SSH gateway
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Support password in ssh deep links with ephemeral connect
ssh://user:password@host URLs now connect directly using the URL
credentials without saving the host to the vault, covering bastion
one-time password flows. Password-less links keep the existing
match-or-prefill behavior.
Fixes#1890
Co-authored-by: Cursor <cursoragent@cursor.com>
* Keep ephemeral deep-link hosts out of vault update paths
Terminal-layer host updates (e.g. the SFTP follow-CWD toggle) map over
the merged terminal host list, which could persist an ephemeral host
and its plaintext password into the vault. Route terminal-layer host
updates through a splitter that applies ephemeral entries to in-memory
state only and forwards just the vault hosts to updateHosts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Exclude ephemeral deep-link sessions from session restore
Sessions backed by an in-memory ephemeral host cannot survive a
relaunch (the one-time password only lives in memory), so persisting
them would leak the ephemeral host metadata into restore storage and
restore a tab that can never reconnect. Mark ephemeral hosts and their
sessions, and filter them out of the session-restore payload.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Treat ephemeral hosts as unsaved in appearance classification
The terminal theme/font panel and pane appearance resolution decided
"saved vault host" via hostMap membership. Ephemeral deep-link hosts
are now in that map, so theme/font changes were routed to the
persisted-host updater and silently dropped. Classify via
isSavedVaultHost (ephemeral flag aware) so those changes fall back to
the session/global setting path as before.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reuse saved host settings for password deep links and keep ephemeral marker on clones
Password deep links now run saved-host matching first: a uniquely
matched vault host contributes its non-credential settings (proxy,
jump chain, charset, group, ...) to the ephemeral host while the URL
credentials override vault identities and key references. Session
clones (split/copy/copy-to-new-window) now carry the ephemeralHost
marker so cloned tabs stay excluded from session restore.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Block group credential inheritance on password deep-link ephemeral hosts
buildSshDeepLinkEphemeralHostFromSaved now takes the group-resolved
effective host and clears group/identityFilePaths in addition to
identity references, so a later resolveEffectiveHost pass cannot
re-inherit group default credentials (identity, key, password) over
the one-time URL password. Non-credential group defaults (proxy,
jump chain, charset, ...) are preserved via the effective host.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Route ephemeral-host font zoom through the per-session font-size path
Ctrl+zoom on a non-workspace ephemeral deep-link session hit the
saved-host update path, where the vault updater silently dropped the
change (ephemeral ids are not in the persisted hosts array). Zoom and
the theme panel font-size slider/reset now store the size on the
session (rendered via applySessionFontSizeToHost) for ephemeral
hosts, matching the workspace-pane behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix ephemeral session font zoom routing
* Prevent ephemeral deep-link sudo autofill
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): detach stale keychain identity when saving retry credentials
When a host references a Keychain identity (host.identityId) with wrong
credentials, re-entering credentials via the auth-retry dialog's
"Continue & Save" saved them onto the host but kept the identity
reference. On reconnect, resolveHostAuth prefers identity credentials
over host fields, so the stale keychain credentials kept failing.
Clear identityId when the user explicitly saves new credentials so the
saved username/password (or key) actually takes effect.
Fixes#1956
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): keep saved retry credentials over group-inherited identity
Writing identityId: undefined is treated as "unset" by applyGroupDefaults,
so a group-level identity would be re-inherited on reconnect and shadow the
credentials saved from the auth-retry dialog. Store an empty string instead
and register identityId in EMPTY_STRING_OVERRIDES_GROUP_DEFAULT so the
explicit "no identity" override sticks. All consumers check identityId
truthiness, so "" behaves as "no identity".
Addresses Codex review on #1970.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Re-inject tool call names on OpenAI-compatible stream desync
Some OpenAI-compatible gateways emit tool_call chunks the AI SDK
rejects or ignores (non-standard `type` values, tool calls outside
choices[0]). The chunk normalizer had already recorded the tool call
id, so later nameless continuation chunks were forwarded with an
injected id and the SDK threw "Expected 'function.name' to be a
string." on what it saw as a brand-new tool call.
Remember each tool call's function name when its id is assigned and
re-inject it (plus type "function") into continuation chunks so every
forwarded chunk is self-describing, and coerce non-standard tool_call
type strings to "function" so the naming chunk itself is not dropped
by schema validation.
Fixes#1954.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Preserve argument prefixes from SDK-invisible tool call chunks
Codex review on #1966 flagged that when the chunk establishing a tool
call is invisible to the AI SDK (which only reads choices[0]), any
argument fragment it carried was lost: the recovery path re-injected
the name but forwarded only the suffix arguments, producing truncated
tool input.
Track the full concatenation of argument fragments per tool call key
and which keys have been forwarded at choices array position 0. The
first chunk forwarded where the SDK can see it now carries the full
accumulated arguments; already-visible tool calls keep streaming
incremental fragments as before.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(terminal): raise SSH flow high-water mark to stop WAN pause ping-pong (#1961)
Bulk terminal output over SSH (e.g. `tail -2000f big.log`) was far slower
than other clients on high-latency links. The renderer's output flow control
pauses the source stream once its unacked backlog crosses FLOW_HIGH_WATER_MARK
(previously 100KB). For SSH the source is the ssh2 channel, so pause() stops
the remote until resume() plus a full round-trip.
ssh2's own channel window is 2MB (WINDOW_THRESHOLD 1MB), so a 100KB watermark
made Netcatty pause/resume dozens of times during a multi-MB dump — a 4MB dump
triggered ~61 pause cycles, each costing ~1 RTT. At 300ms RTT that turned a
~2s dump into ~21s.
Raise FLOW_HIGH_WATER_MARK to 1MB so it sits near the ssh2 window; the same
4MB dump now settles in ~4 pause cycles (~2-3s at 300ms RTT) while still
bounding renderer memory. FLOW_LOW_WATER_MARK stays at 8KB to keep interactive
acks responsive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(terminal): align deferred-ack test with 1MB flow high-water mark
The deferred IPC ack batch threshold (XTERM_WRITE_CALLBACK_BATCH_BYTES)
now sits far below FLOW_HIGH_WATER_MARK, so deferral alone can no longer
accumulate up to the watermark. Assert the batch-flush invariant instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(zmodem): keep transfer listeners across same-session reconnects
The preload netcatty:exit handler dropped the per-session zmodem
listener sets, but auth-failure retries and auto-reconnects reuse the
same sessionId without remounting the terminal, so useZmodemTransfer
never re-subscribed and rz/sz progress + overwrite dialogs silently
stopped appearing (#1955). Move listener cleanup to explicit
closeSession and prune empty sets on subscriber dispose instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(zmodem): gate event delivery instead of dropping listeners on close
Codex review caught that reconnects also call closeSession with the
terminal still mounted (startReconnect -> cleanupSession), so clearing
listener maps there reintroduced the bug for manual/auto reconnects.
Keep listener sets owned by renderer subscribers and gate zmodem event
delivery on closedTerminalDataSessions, which is cleared when a session
restarts with the same id.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
if [[ ! "$tag" =~ ^mosh-bin-[A-Za-z0-9._-]+$ ]]; then
echo "Invalid mosh binary release tag: $tag" >&2
exit 1
fi
printf 'name=%s\n' "$tag" >> "$GITHUB_OUTPUT"
- name:Create / update release
env:
GH_TOKEN:${{ secrets.MOSH_BIN_RELEASE_TOKEN }}
RELEASE_REPO:${{ inputs.release_repo }}
RELEASE_TAG:${{ steps.tag.outputs.name }}
run:|
set -euo pipefail
if [[ -z "${GH_TOKEN:-}" ]]; then
echo "::error::MOSH_BIN_RELEASE_TOKEN is required to publish into ${RELEASE_REPO}."
exit 1
fi
{
printf '%s\n' 'Pre-built `mosh-client` binaries consumed by `scripts/fetch-mosh-binaries.cjs` during `npm run pack`.'
printf 'Linux/macOS artifacts are built from `mobile-shell/mosh` upstream ref `%s`.\n' "${MOSH_REF}"
printf '%s\n\n' 'Windows x64 is the SHA256-pinned Netcatty runtime bundle, with the FluentTerminal standalone `mosh-client.exe` kept as a fallback asset.'
'Title must start with `[Bug]` or `[Feature]` followed by a short summary (at least 8 characters after the prefix). Legacy app links using `Bug: ...` are also accepted. Example: `[Bug] SFTP upload fails on Windows`'
);
}
if (body.length < 120) {
errors.push(
'Body is too short. Please use the Bug Report or Feature Request template and fill in all required fields.'
git commit -m "chore: update star history charts [skip ci]"
# Auth comes from actions/checkout (RELEASE_TOKEN), not GH_TOKEN above.
git push -u origin "HEAD:${branch}" --force
existing="$(
gh pr list \
--head "$branch" \
--base "$default_branch" \
--state open \
--json number \
--jq '.[0].number // empty'
)"
if [[ -n "$existing" ]]; then
echo "Reusing open PR #${existing}"
pr_number="$existing"
else
pr_url="$(
gh pr create \
--base "$default_branch" \
--head "$branch" \
--title "chore: update star history charts" \
--body "Automated weekly refresh of the static star history SVG charts embedded in the README. Generated by the Star History workflow via mystarhistory."
)"
echo "Created ${pr_url}"
pr_number="${pr_url##*/}"
fi
# required_approving_review_count is 0; merge immediately when possible.
if gh pr merge "$pr_number" --squash --delete-branch; then
echo "Merged PR #${pr_number}"
else
echo "Could not auto-merge PR #${pr_number}; left open for manual merge."
External SDK turns normalize file changes, web searches, plan updates, recoverable warnings, and token usage into the shared event protocol. These activity and usage records are stored on assistant messages so the compact activity view is restored with chat history.
**Stop** always goes through `stopAgentTurn()` (UI, `/stop`, MCP). Do not add parallel abort paths in hooks.
### Codex App Server (experimental)
Codex can opt into a persistent `codex app-server --stdio` runtime under
`electron/bridges/aiBridge/codexAppServer/`; the existing TypeScript SDK remains
the default. The main process owns JSONL RPC correlation, thread/turn routing,
native approvals, `request_user_input`, model discovery, and process cleanup.
- Session identities include the Codex runtime; SDK and App Server threads must
never resume across runtimes.
- Observer maps to `read-only + never`, Confirm to `read-only + on-request`, and
Auto to `danger-full-access + never`.
- App Server native “allow for session” decisions are session-scoped Codex
grants and must not become persistent Netcatty permission grants.
-`turn/completed` is the terminal lifecycle event. Retryable `error`
notifications are warnings; process exit is fatal.
- Regenerate the committed protocol contract with
`npm run generate:codex-app-server-schema` after upgrading Codex, and verify it
with `npm run check:codex-app-server-schema`.
### AI SDK v7 (Catty path)
Catty sidebar turns use **Vercel AI SDK 7** via `streamText` in `turnDrivers/cattyStreamProcessor.ts`. Key conventions:
@@ -84,6 +106,31 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
**Harness domain (`catalog/harness.cjs`):** Catty-only surface (`surfaces.catty.toolName`). Registered in the capability catalog but executed locally in `capabilityTools.executeLocalCattyCapability` (not MCP/CLI). `harness.web.search` is omitted when web search is not configured.
## Plugin host runtime (internal preview)
The phase-2 plugin host lives under `electron/plugins/` and is disabled unless
`NETCATTY_PLUGIN_DEV=1` is present at application launch. Public wire and package
types still come only from `packages/plugin-contract/schema/`; do not add a
second private RPC shape when extending the host.
-`PackageStore` validates an immutable `.ncpkg` snapshot, extracts only into
private staging, and atomically publishes an installed version.
-`PluginManager` serializes install, enable/disable, restart and uninstall
mutations. Do not bypass it from renderer IPC.
- Ordinary plugins run in a sandboxed, offline `BrowserWindow` session and can
reach only their runtime-scoped `netcatty-plugin://` authority.
- Node-only plugins run in a dedicated `utilityProcess`; they remain an advanced
development-only path until permission and distribution phases land.
-`PluginRpcRouter` owns correlation, cancellation, deadlines, stream credit and
protocol-failure containment. Runtime identity is assigned by the host and is
never accepted from request parameters.
- App quit goes through `runPluginShutdown()` after the dirty-editor guard; do
not add another independent quit interception path.
Run `npm run test:plugin-runtime` for main-process boundaries and
`npm run test:plugin-runtime:electron` for real BrowserWindow/utilityProcess
smoke coverage. Packaged-resource changes must also pass `npm run pack:dir`.
## Extending the System
1) **New domain logic**: Add pure functions/types under `domain/`; avoid side effects.
2) **New stateful behavior**: Wrap it in a hook under `application/state/`; keep external I/O behind adapters.
@@ -95,6 +142,14 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
- **Temporary files**: All temporary files (e.g., SFTP downloaded files for external editing) must be written to Netcatty's dedicated temp directory via `tempDirBridge.getTempFilePath(fileName)`. Do not write directly to `os.tmpdir()`. This ensures proper cleanup and user visibility in Settings > System.
## Terminal Side Panel Splits
-`domain/sidePanelLayout.ts` owns the pure pane/split tree operations, including focus, unique tools, close collapse, resize, and the pane limit.
-`application/state/useTerminalSidePanelLayoutState.ts` owns per-terminal layouts and keeps the legacy focused-tool map compatible with external open paths.
-`TerminalLayerSidePanelSection.tsx` renders one shared toolbar plus the nested pane chrome. Each pane content host must remain `overflow-hidden` and layout-contained.
- Mounted tool panels use a stable portal node that moves between their pane host and the hidden parking host. Never fall back to an `absolute inset-0` side-panel root when a pane host is not ready.
- Closing the whole side panel clears that terminal's split tree. Switching terminal tabs must not reuse another tab's layout.
## Testing & Safety
- Favor unit tests for domain helpers (e.g., `workspace.ts`, `host.ts`) and hook-level tests for application state.
- When changing storage keys or schema, provide migration or backward-compat handling.
@@ -106,6 +161,16 @@ Placement rules (`resolveAgentKinds` in `toolSurfaces.cjs`):
- Avoid direct network/fetch in components; add a service/adaptor first.
- Maintain ASCII-only unless required by existing file content.
## Reporting Issues & PRs
Issues opened without the required format are **auto-closed** by the issue-format bot. Agents that file issues via `gh` or the API must still follow these rules:
- **Title:** start with `[Bug]`, `[Feature]`, or `[Other]`, then a short summary (≥ 4 characters after the prefix). Example: `[Bug] SFTP upload fails on Windows`.
- **Body:** use the templates under [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/) (Bug Report or Feature Request) and fill every required field. Blank issues are disabled.
Full contributor guidance (setup, commits, PR process): [CONTRIBUTING.md](./CONTRIBUTING.md).
## Review Boundaries
- Treat `electron/cli/*`, `netcatty-tool-cli`, the CLI discovery file, and the local TCP bridge as internal Netcatty integration surfaces unless a task explicitly says otherwise.
- Do not review those surfaces as public APIs by default, and do not assume they must support third-party callers, manual launches, or non-Netcatty agents.
@@ -127,6 +127,7 @@ If you regularly work with a fleet of servers, Netcatty is built for speed and f
### 🖥️ Terminal Workspaces
- **Split panes** — horizontal and vertical splits for multi-tasking
- **Session management** — run multiple connections side-by-side
- **Inline images** — render Kitty graphics, SIXEL and iTerm inline images from remote programs
### 📁 SFTP + Built-in Editor
- **File workflows** — drag & drop uploads/downloads
@@ -206,6 +207,18 @@ Download the latest release for your platform from [GitHub Releases](https://git
Or browse all releases at [GitHub Releases](https://github.com/binaricat/Netcatty/releases).
### Code signing and privacy
Netcatty is applying to the SignPath Foundation open-source program. Once
approved, covered Windows release artifacts will use **Free code signing provided by SignPath.io, certificate by SignPath Foundation**.
See the [code signing policy](CODE_SIGNING_POLICY.md) and
[privacy policy](PRIVACY.md). Windows releases may remain unsigned until the
application and integration are complete.
> **Windows portable data:** Exit Netcatty, then create a folder named `data` beside `Netcatty.exe` (zip build) or beside the portable launcher. Netcatty will store its profile there on the next launch. Saved passwords and private keys remain protected by the Windows user account that created them, so they must be re-entered after moving the folder to another computer or Windows account.
> **Open folders in Netcatty on Windows:** The installed version adds **Open in Netcatty** to Explorer's folder context menu and folder-background context menu. It opens a local terminal in that folder. On Windows 11, choose **Show more options** first. Turn the menu off or on in **Settings → System → Windows Explorer**. ZIP and portable versions do not add this menu by default.
> **macOS Users:** Current releases are expected to be code-signed and notarized. If Gatekeeper still warns, make sure you downloaded the latest official build from GitHub Releases.
### Nix / NixOS
@@ -308,11 +321,11 @@ This project is licensed under the **GPL-3.0 License** - see the [LICENSE](LICEN
'ai.codex.description':'Connect OpenAI Codex. Sign in with ChatGPT here, or enable an OpenAI-compatible provider API key and custom endpoint in Settings.',
'ai.codex.appServer.description':'Use the persistent Codex protocol for native approvals, sandbox controls, live models, and mid-turn questions. SDK remains the default.',
'ai.codex.appServer.checking':'Checking App Server support…',
'ai.codex.appServer.available':'App Server is available for this Codex CLI.',
'ai.codex.appServer.modelCatalogWarning':'The live Codex model catalog is unavailable. Using the built-in model list.',
'ai.codex.appServer.approval.allowSession':'Allow for session',
'ai.codex.appServer.userInput.title':'Codex needs your input',
'ai.codex.appServer.userInput.description':'Answer these questions to continue the current turn.',
'ai.codex.appServer.userInput.other':'Enter another answer',
'ai.codex.appServer.userInput.autoResolve':'Codex will continue automatically if no answer is provided in time.',
'ai.codebuddy.envVars.hint':'One KEY=VALUE per line, passed to the CodeBuddy agent. Set CODEBUDDY_API_KEY or CODEBUDDY_AUTH_TOKEN here for authentication. Stored locally in plaintext.',
// AI Grok Build (in-app managed agent — distinct from External MCP client install)
'ai.grok.title':'Grok Build',
'ai.grok.description':"xAI's Grok Build coding agent CLI. Install the Grok CLI, sign in with `grok login` or set XAI_API_KEY, then select it as an external agent.",
'ai.grok.detecting':'Detecting...',
'ai.grok.detected':'Detected',
'ai.grok.notFound':'Not found',
'ai.grok.path':'Path:',
'ai.grok.notFoundHint':'Could not find grok in PATH. Install Grok Build CLI or specify the executable path below.',
'Talk to Grok over Agent Client Protocol (grok agent stdio). Injects Netcatty MCP on session/new. Turn off to use the original headless streaming-json CLI path.',
'ai.grok.runtime.streamingJson.hint':
'Using headless streaming-json (grok -p --output-format streaming-json). Project .grok/config.toml is used for MCP injection.',
// AI Default Agent
'ai.defaultAgent':'Default Agent',
'ai.defaultAgent.description':'Agent to use when starting a new AI session',
'ai.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',
// External MCP (productized catalog MCP for Codex / Claude Code / Cursor)
'ai.externalMcp.title':'External MCP',
'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.mode':'Availability mode',
'ai.externalMcp.mode.temporary':'Temporary',
'ai.externalMcp.mode.persistent':'Always on',
'ai.externalMcp.mode.description':'Temporary mode auto-disables after idle timeout. Always-on restores External MCP when Netcatty starts.',
'ai.externalMcp.idleTimeout':'Idle timeout',
'ai.externalMcp.idleTimeout.description':'In temporary mode, disable External MCP after this many minutes with no MCP operations.',
'ai.externalMcp.idleTimeout.minutes':'min',
'ai.externalMcp.focusOnHostOpen':'Focus window on host_open',
'ai.externalMcp.focusOnHostOpen.description':'When an MCP client opens a host, bring the main window to the foreground. Turn off to keep working without interruption.',
'ai.externalMcp.silentSessions.description':'Sessions opened by AI stay out of your tab bar and are not restored after restart. View them anytime from the tray panel.',
'ai.externalMcp.sessionIdleTimeout.description':'Automatically close sessions opened by an AI after this many minutes without terminal or file activity.',
'ai.externalMcp.usage.title':'How to use',
'ai.externalMcp.usage.keepRunning':'1. Turn on External MCP and keep Netcatty running.',
'ai.externalMcp.usage.localhost':'2. Clients connect via the local launcher (127.0.0.1 only). Discovery is removed when you disable the switch.',
'ai.externalMcp.usage.permissions':'3. Write operations follow Settings → AI → Safety (observer / confirm / auto) and the command blocklist.',
'ai.externalMcp.usage.capabilities':'4. Full catalog tools are available: terminal, SFTP, Vault, and port forwarding. Secrets (passwords / private keys) are never returned.',
'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.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.clientConfiguration.description':'Pick a client to one-click install, or copy CLI / config snippets.',
'ai.externalMcp.client.codex':'Codex',
'ai.externalMcp.client.claude':'Claude Code',
'ai.externalMcp.client.grok':'Grok',
'ai.externalMcp.client.cursor':'Cursor',
'ai.externalMcp.cliCommand':'CLI command',
'ai.externalMcp.configSnippet':'Config snippet',
'ai.externalMcp.addToCodex':'Add to Codex',
'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.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.status.codexNotFound':'Codex not found',
'ai.externalMcp.status.claudeNotFound':'Claude Code not found',
'ai.externalMcp.status.grokNotFound':'Grok not found',
'ai.externalMcp.status.conflict':'Conflict',
'ai.userSkills.title':'User Skills',
'ai.userSkills.description':'Open the Netcatty skills folder to add your own skill directories. Netcatty scans these skills automatically and injects only lightweight indexes unless a skill clearly matches the current request.',
'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.networkProxy.description':'HTTP(S) proxy for cloud sync and AI providers. This is separate from SSH ProxyJump / ProxyCommand profiles.',
'settings.system.networkProxy.mode':'Proxy mode',
'settings.system.networkProxy.mode.system':'Use system proxy',
'settings.system.networkProxy.mode.direct':'Direct (no proxy)',
'settings.vault.showRecentHostsDesc':'Display a section of recently connected hosts at the top of the vault',
'settings.vault.selectBeforeConnect':'Select host before connecting',
'settings.vault.selectBeforeConnectDesc':'Click once to select a host (accent highlight in grid view), click again to connect. Groups work the same way. Default remains single-click connect.',
'settings.vault.showOnlyUngroupedHostsInRoot':'Only show ungrouped hosts at root',
'settings.vault.showOnlyUngroupedHostsInRootDesc':'When enabled, the root host list only shows hosts without a group. Open a group from the sidebar to see grouped hosts.',
'settings.vault.showSftpTab':'Show SFTP tab',
'settings.vault.showSftpTabDesc':'Display the standalone SFTP view in the top tab bar. When hidden, use the in-session SFTP side panel instead.',
'settings.vault.showHostTreeSidebar':'Show host list sidebar',
'settings.vault.showHostTreeSidebarDesc':'Display the host list sidebar and its top-bar toggle on terminal and editor tabs.',
'settings.vault.autoImportSystemKnownHosts':'Auto-import system known_hosts',
'settings.vault.autoImportSystemKnownHostsDesc':'When Vault loads, silently import entries from the OpenSSH known_hosts file. Turn this off to keep Netcatty known hosts separate; Scan System still imports on demand.',
'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.
###Howtoselect
Useattributeselectors:
\`\`\`css
[data-section="terminal-side-panel"]{
border: 2pxsolid#00c851!important;
border-radius: 6px!important;
}
\`\`\`
Somerowsandpanesalsosupportstatehookssuchas\`[data-selected="true"]\` or \`[data-focused="true"]\`.
BecausemanystylescomefromTailwindutilities,youoftenneed\`!important\` to override them.
###Toptabs
-\`top-tabs\`
-\`top-tabs-toolbar-actions\` — Right-side menu and utility toolbar
-\`top-tabs-host-tree-toggle\`
-\`top-tabs-quick-switcher-toggle\`
###Vault
-\`vault-view\`
-\`vault-sidebar\`
-\`vault-main\`
-\`vault-hosts-header\`
-\`vault-host-list\`
###Terminalworkspace
-\`terminal-workspace\`
-\`terminal-toolbar\` — Terminal action toolbar
-\`terminal-toolbar-menu\` — Terminal toolbar overflow and submenu
-\`terminal-workspace-sidebar\` — Focus-mode terminal list
-\`terminal-host-tree-sidebar\`
-\`terminal-host-tree-sidebar-content\`
-\`terminal-host-tree-sidebar-row\`
-\`terminal-split-pane\`
-\`terminal-split-resizer\`
###Terminalsidepanel
-\`terminal-side-panel\` — SFTP / Scripts / Theme / AI panel (only present while open)
'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',
'settings.terminal.behavior.copyOnSelect':'Copy on select',
'settings.terminal.behavior.copyOnSelect.desc':'Automatically copy selected text. In tmux/vim with mouse mode, hold Option on macOS or Shift on Windows/Linux to select',
'settings.terminal.behavior.normalizeTextOnCopy':'Normalize terminal text on copy',
'When copying from the terminal, strip display-only trailing padding and join soft-wrapped rows so pasted text matches logical output. Turn off to copy the raw screen selection (including TUI padding and visual wraps).',
'When the clipboard holds an image, pasting in a remote session uploads it via SFTP to .netcatty-paste-images/ under the remote working directory and inserts the remote path instead of pasting text.',
'settings.terminal.hostInfoBar.show':'Show host information bar',
'settings.terminal.hostInfoBar.show.desc':'Show the host address and server information above the terminal. Action buttons remain available when hidden.',
'settings.terminal.hostInfoBar.titleMode':'Host information title',
'settings.terminal.hostInfoBar.titleMode.desc':'Choose whether the bar shows the connection address (user@host) or the vault host label.',
'settings.terminal.inlineImages.enabled.desc':'Draw real images sent by remote programs (kitten icat, chafa, image previews in file managers and TUIs). Changes take effect on new terminal sessions.',
'settings.terminal.inlineImages.maxMegapixels.desc':'Largest single image that will be decoded; bigger images are discarded without touching the terminal buffer (1–64 MP, 16 MP is 4096 × 4096).',
'settings.terminal.inlineImages.hibernateNote':'Tabs that have drawn an inline image keep their renderer instead of hibernating, because hibernation restores text only.',
'cloudSync.convergent.desc':'Uses an encrypted CRDT replica to preserve offline edits, concurrent deletions, and changes from every connected provider.',
'cloudSync.convergent.active':'CRDT v2 is active. Provider writes are verified after upload.',
'cloudSync.convergent.paused':'CRDT v2 is paused on this device; cloud metadata is retained.',
'vault.import.progress.persistFailed':'Imported hosts could not be saved. Free some local storage space and try again.',
'vault.import.progress.rollbackFailed':'The import could not be rolled back safely. Some imported hosts may remain; review the Vault before retrying.',
'vault.import.sshConfig.importOnlyDesc':'One-time import. Changes won\'t sync back to the file.',
'vault.import.sshConfig.managed':'Managed Sync',
'vault.import.sshConfig.managedDesc':'Keep in sync. Changes will be saved back to the file.',
'vault.import.sshConfig.managedDestinationHint':'Managed sync always uses its own managed group; the destination above applies to one-time import only.',
'sftp.transfers.pathConflict.paused':'Another transfer for "{name}" is paused. Resume or cancel it first.',
'sftp.transfers.pathConflict.inProgress':'Another transfer for "{name}" is already in progress.',
'sftp.transferCenter.title':'File Transfers',
'sftp.transferCenter.quitConfirm':'{count} file transfer(s) are unfinished. Progress will be saved and must be resumed manually next time. Quit anyway?',
'settings.sftp.transferConcurrency.desc':'Number of files to transfer in parallel when uploading or downloading folders. Higher values may improve speed but can overwhelm some servers.',
'settings.sftp.transferConcurrency.desc':'Maximum number of files transferred in parallel for the same endpoint, including folder contents and separately selected files. Higher values may improve speed but can overwhelm some servers.',
'settings.sftp.transferPoolIdleTtl.desc':'How long dedicated transfer SFTP connections stay warm after idle. Longer values reduce cold-start delay before the next upload or download. Busy transfers are never closed.',
'settings.ssh.transportIdleTtl.desc':'How long a shared SSH connection stays warm after the last terminal, SFTP browse, bulk transfer, or port-forward lease is released. While warm, reconnecting to the same host can skip re-authentication (including MFA). Active sessions and transfers are never closed.',
'settings.explorerContextMenu.enable':'Show “Open in Netcatty” in folder context menus',
'settings.explorerContextMenu.enableDesc':'Adds a right-click item for folders and folder backgrounds so you can open a local terminal there. Applies immediately; no reinstall needed.',
'settings.jmsDeepLink.enableDesc':'Register Netcatty as the handler for JumpServer jms:// client links. May conflict with the official JumpServer client.',
'hostDetails.hostname.placeholder':'IP or Hostname',
'hostDetails.section.general':'General',
'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.sftp.fileProtocol.auto':'Auto',
'hostDetails.sftp.fileProtocol.sftp':'SFTP only',
'hostDetails.sftp.fileProtocol.scp':'SCP mode',
'hostDetails.sftp.sudo':'Sudo Mode',
'hostDetails.sftp.sudo.desc':'Automatically acquire Root privileges using stored password',
'hostDetails.sftp.sudo.passwordWarning':'Sudo mode requires a password. Configure one above, or ensure the server allows passwordless sudo.',
'hostDetails.keepalive.disabledHint':'Interval = 0 disables keepalive for this host. The session will rely on TCP-level timeouts to detect a dead connection.',
'ai.codex.description':'Подключение OpenAI Codex. Здесь можно войти через ChatGPT или включить API-ключ OpenAI-совместимого провайдера и пользовательский endpoint в настройках.',
'ai.codex.appServer.description':'Постоянный протокол Codex с нативными подтверждениями, песочницей, актуальными моделями и вопросами во время выполнения. SDK остаётся режимом по умолчанию.',
'ai.codex.appServer.checking':'Проверка поддержки App Server…',
'ai.codex.appServer.available':'Эта версия Codex CLI поддерживает App Server.',
'ai.codex.appServer.modelCatalogWarning':'Динамический каталог моделей Codex недоступен. Используется встроенный список моделей.',
'ai.codex.appServer.approval.allowSession':'Разрешить для сеанса',
'ai.codex.appServer.userInput.title':'Codex требуется ваш ответ',
'ai.codex.appServer.userInput.description':'Ответьте на вопросы, чтобы продолжить текущую задачу.',
'ai.codex.appServer.userInput.other':'Введите другой ответ',
'ai.codex.appServer.userInput.autoResolve':'Если ответа не будет вовремя, Codex продолжит автоматически.',
'ai.cursor.description':'Использует Cursor SDK или локальный вход Agent CLI.',
'ai.cursor.detecting':'Обнаружение...',
'ai.cursor.detected':'Доступен',
'ai.cursor.notFound':'Недоступен',
'ai.cursor.path':'Среда:',
'ai.cursor.notFoundHint':'Укажите API-ключ, чтобы включить Cursor.',
'ai.cursor.notInstalledHint':'Cursor SDK не обнаружен.',
'ai.cursor.installStatus':'Cursor SDK',
'ai.cursor.notFoundHint':'Укажите API-ключ или переключитесь на CLI login.',
'ai.cursor.notInstalledHint':'Cursor SDK / Agent CLI не обнаружен.',
'ai.cursor.installStatus':'Среда Cursor',
'ai.cursor.installed':'Обнаружено',
'ai.cursor.notInstalled':'Не обнаружено',
'ai.cursor.modeCli':'CLI login',
'ai.cursor.modeApiKey':'API Key',
'ai.cursor.modeCliHint':'Использует локальный `cursor-agent login` и квоту подписки Auto. Сохранённый API-ключ сохраняется, но в этом режиме не используется.',
'ai.cursor.modeApiKeyHint':'Использует платный Cursor API. CLI login в этом режиме игнорируется.',
'ai.cursor.cliLoginStatus':'CLI login',
'ai.cursor.cliLoginOk':'Вход выполнен',
'ai.cursor.cliLoginAs':'Вход как {{email}}',
'ai.cursor.cliLoginMissing':'Нет входа',
'ai.cursor.cliLoginHint':'Выполните `cursor-agent login` в терминале, затем нажмите Check.',
'ai.codebuddy.envVars.hint':'По одной записи KEY=VALUE на строку, передаются агенту CodeBuddy. Укажите CODEBUDDY_API_KEY или CODEBUDDY_AUTH_TOKEN для аутентификации. Хранятся локально в открытом виде.',
'ai.codebuddy.elicitation.title':'CodeBuddy требуется ваш ответ',
'ai.codebuddy.elicitation.description':'Проверьте запрос, чтобы продолжить текущий ход.',
'ai.codebuddy.elicitation.validation.invalidType':'Поле «{field}» содержит недопустимое значение.',
'ai.codebuddy.elicitation.validation.integer':'Поле «{field}» должно быть целым числом.',
'ai.codebuddy.elicitation.validation.notInteger':'Поле «{field}» должно быть целым числом.',
'ai.codebuddy.elicitation.validation.minimum':'Значение «{field}» должно быть не меньше {limit}.',
'ai.codebuddy.elicitation.validation.maximum':'Значение «{field}» должно быть не больше {limit}.',
'ai.codebuddy.elicitation.validation.minLength':'Поле «{field}» должно содержать не менее {limit} символов.',
'ai.codebuddy.elicitation.validation.maxLength':'Поле «{field}» должно содержать не более {limit} символов.',
'ai.codebuddy.elicitation.validation.minItems':'Выберите не менее {limit} вариантов для «{field}».',
'ai.codebuddy.elicitation.validation.maxItems':'Выберите не более {limit} вариантов для «{field}».',
'ai.codebuddy.elicitation.validation.format':'Поле «{field}» должно соответствовать формату {format}.',
'ai.codebuddy.elicitation.validation.option':'Выберите допустимый вариант для «{field}».',
// AI Grok Build (in-app managed agent — distinct from External MCP client install)
'ai.grok.title':'Grok Build',
'ai.grok.description':'Агент программирования Grok Build от xAI (CLI). Установите Grok CLI, выполните `grok login` или задайте XAI_API_KEY, затем выберите его как внешнего агента.',
'ai.grok.detecting':'Обнаружение...',
'ai.grok.detected':'Обнаружен',
'ai.grok.notFound':'Не найден',
'ai.grok.path':'Путь:',
'ai.grok.notFoundHint':'Не удалось найти grok в PATH. Установите Grok Build CLI или укажите путь к исполняемому файлу ниже.',
'Подключение к Grok через Agent Client Protocol (grok agent stdio). Netcatty MCP внедряется в session/new. Выключите, чтобы использовать исходный headless streaming-json CLI.',
'ai.toolAccess.description':'Выберите, как внешние агенты получают доступ к сессиям Netcatty. MCP предоставляет встроенный сервер, а Skills + CLI указывает агентам на локальный skill Netcatty и команды CLI.',
'ai.toolAccess.mode.mcp':'MCP',
'ai.toolAccess.mode.skills':'Skills + CLI',
'ai.externalMcp.title':'Внешний MCP',
'ai.externalMcp.description':'Откройте Netcatty как MCP-сервер для Codex, Claude Code, Cursor и Grok. Набор инструментов тот же, что у встроенных агентов. Держите Netcatty запущенным, пока клиенты подключены.',
'ai.externalMcp.sessionsExposed':'Сессий в области: {count}',
'ai.externalMcp.mode':'Режим доступности',
'ai.externalMcp.mode.temporary':'Временный',
'ai.externalMcp.mode.persistent':'Постоянный',
'ai.externalMcp.mode.description':'Временный режим отключается после простоя. Постоянный восстанавливает External MCP при запуске Netcatty.',
'ai.externalMcp.idleTimeout':'Таймаут простоя',
'ai.externalMcp.idleTimeout.description':'В временном режиме отключить External MCP после указанного числа минут без MCP-операций.',
'ai.externalMcp.idleTimeout.minutes':'мин',
'ai.externalMcp.focusOnHostOpen':'Активировать окно при host_open',
'ai.externalMcp.focusOnHostOpen.description':'Когда MCP-клиент открывает хост, выводить главное окно на передний план. Отключите, чтобы работать без прерываний.',
'ai.externalMcp.silentSessions.description':'Сеансы, открытые ИИ, не отображаются на панели вкладок и не восстанавливаются после перезапуска. Их можно посмотреть в любой момент через панель в трее.',
'ai.externalMcp.sessionIdleTimeout.description':'Автоматически закрывать сеансы, открытые ИИ, после указанного числа минут без операций терминала или файлов.',
'ai.externalMcp.usage.title':'Как пользоваться',
'ai.externalMcp.usage.keepRunning':'1. Включите External MCP и держите Netcatty запущенным.',
'ai.externalMcp.usage.localhost':'2. Клиенты подключаются через локальный launcher (только 127.0.0.1). Discovery удаляется при отключении.',
'ai.externalMcp.usage.permissions':'3. Запись следует Settings → AI → Safety и списку блокировки команд.',
'ai.externalMcp.usage.capabilities':'4. Доступен полный catalog: терминал, SFTP, Vault и проброс портов. Секреты не возвращаются.',
'ai.externalMcp.security.description':'Слушает только 127.0.0.1 с ротацией token, использует AI Permission Mode для записи и удаляет discovery при отключении.',
'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.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.status.codexNotFound':'Codex не найден',
'ai.externalMcp.status.claudeNotFound':'Claude Code не найден',
'ai.externalMcp.status.grokNotFound':'Grok не найден',
'ai.externalMcp.status.conflict':'Конфликт',
'ai.userSkills.title':'Пользовательские skills',
'ai.userSkills.description':'Откройте папку skills Netcatty, чтобы добавить свои каталоги skills. Netcatty автоматически сканирует их и добавляет только лёгкие индексы, если skill явно не соответствует текущему запросу.',
'ai.safety.blocklist.reset':'Сбросить по умолчанию',
'ai.safety.blocklist.add':'Добавить шаблон',
'ai.safety.grants.title':'Память разрешений',
'ai.safety.grants.heading':'Правила для режима confirm',
'ai.safety.grants.description':'В режиме confirm действие сначала требует подтверждения. Сохранённые правила автоматически разрешают похожие действия во всех терминальных сессиях, их можно редактировать вручную.',
'ai.safety.grants.heading':'Правила для режима подтверждения',
'ai.safety.grants.description':'В режиме подтверждения действие сначала требует подтверждения. Сохранённые правила автоматически разрешают похожие действия во всех терминальных сессиях, их можно редактировать вручную.',
'ai.safety.grants.empty':'Правил пока нет. Выберите «Всегда разрешать» при одобрении или добавьте вручную.',
'settings.system.credentials.unavailableHint':'Учётные данные, зашифрованные в другом профиле пользователя или на другой машине, здесь расшифровать нельзя. Повторно введите и сохраните их на этом устройстве.',
'settings.system.credentials.portabilityHint':'Облачная синхронизация переносима, потому что использует шифрование вашим мастер-ключом. Локальное шифрование safeStorage привязано к устройству и пользователю.',
'settings.system.networkProxy.url.desc':'Поддерживаются URL вида http://, https:// и socks5:// host:port. Учётные данные в URL прокси не поддерживаются.',
'settings.vault.showRecentHosts':'Показывать недавно подключённые хосты',
'settings.vault.showRecentHostsDesc':'Показывать раздел недавно подключённых хостов в верхней части хранилища',
'settings.vault.selectBeforeConnect':'Сначала выбрать, потом подключать',
'settings.vault.selectBeforeConnectDesc':'Один клик выбирает хост (акцентная рамка в сетке), повторный клик подключает. Группы работают так же. По умолчанию — подключение одним кликом.',
'settings.vault.showOnlyUngroupedHostsInRoot':'Показывать в корне только хосты без группы',
'settings.vault.showOnlyUngroupedHostsInRootDesc':'Если включено, в корневом списке хостов будут показаны только хосты без группы. Откройте группу на боковой панели, чтобы увидеть сгруппированные хосты.',
'settings.vault.showSftpTabDesc':'Показывать отдельный SFTP-вид в верхней панели вкладок. Если скрыто, используйте боковую панель SFTP внутри сессии.',
'settings.vault.autoImportSystemKnownHostsDesc':'При открытии хранилища тихо импортировать записи из OpenSSH known_hosts. Отключите, чтобы держать известные хосты Netcatty отдельно; кнопка «Сканировать систему» по-прежнему импортирует вручную.',
'Добавьте пользовательский CSS, чтобы настроить внешний вид приложения. Изменения применяются сразу. Основные области интерфейса имеют атрибут [data-section="..."], который можно использовать для выбора элементов, например: snippets-panel, host-details-panel, group-details-panel, serial-host-details-panel, ai-chat-panel, vault-sidebar, vault-main, vault-hosts-header, vault-host-list, vault-view, terminal-workspace, terminal-workspace-sidebar (список терминалов в режиме Focus), terminal-host-tree-sidebar, terminal-host-tree-sidebar-content, terminal-host-tree-sidebar-row, terminal-side-panel (панель SFTP/скриптов/темы/AI, доступна пока открыта), terminal-side-panel-tabs, terminal-side-panel-content, terminal-sftp-panel, terminal-sftp-host-header, terminal-sftp-pane, terminal-sftp-toolbar, terminal-sftp-path, terminal-sftp-filter-bar, terminal-sftp-list, terminal-sftp-list-header, terminal-sftp-list-row, terminal-sftp-tree, terminal-sftp-tree-row, terminal-sftp-transfer-queue, terminal-sftp-transfer-row, terminal-split-pane, terminal-split-resizer, top-tabs, top-tabs-host-tree-toggle, top-tabs-quick-switcher-toggle.',
'Добавьте пользовательский CSS, чтобы настроить внешний вид. Изменения применяются сразу.',
'settings.appearance.customCss.help.ariaLabel':'Справка по пользовательскому CSS',
'settings.appearance.customCss.help.title':'Руководство по пользовательскому CSS',
'settings.appearance.customCss.help.body':`Основные области интерфейса помечены атрибутом \`data-section\` для CSS-селекторов. Изменения применяются сразу после правки.
###Каквыбиратьэлементы
Используйтеселекторыпоатрибуту:
\`\`\`css
[data-section="terminal-side-panel"]{
border: 2pxsolid#00c851!important;
border-radius: 6px!important;
}
\`\`\`
Унекоторыхстрокипанелейтакжеестьхукисостояниявроде\`[data-selected="true"]\` и \`[data-focused="true"]\`.
'settings.terminal.font.cjk.refresh':'Обновить список шрифтов',
'settings.terminal.font.cjk.alignmentWarning':'Этот шрифт не проверен как моноширинный. Проверьте выравнивание в предварительном просмотре.',
'settings.terminal.font.cjk.unavailableWarning':'Этот шрифт не установлен на устройстве. Netcatty сохранит настройку и использует безопасный запасной шрифт.',
'settings.terminal.font.size':'Размер шрифта',
'settings.terminal.font.size.desc':'Размер текста терминала',
'Показывать контекстное меню, даже когда tmux, vim или другие полноэкранные приложения перехватывают мышь. Когда выключено, правый щелчок передаётся приложению (Shift+правый щелчок всё равно открывает меню).',
'settings.terminal.behavior.autoCloseOnExit':'Автоматически закрывать терминал при выходе',
'settings.terminal.behavior.copyOnSelect':'Копировать при выделении',
'settings.terminal.behavior.copyOnSelect.desc':'Автоматически копировать выделенный текст. В tmux/vim с режимом мыши удерживайте Option на macOS или Shift на Windows/Linux для выделения',
'settings.terminal.behavior.normalizeTextOnCopy':'Нормализовать текст терминала при копировании',
'При копировании из терминала убирать отображающие хвостовые пробелы и объединять soft-wrap строки, чтобы вставка соответствовала логическому тексту. Отключите, чтобы копировать сырое выделение экрана (включая padding TUI и визуальные переносы).',
'settings.terminal.behavior.middleClickPaste':'Вставка средней кнопкой мыши',
'Символы, которые считаются границами слова при выделении двойным щелчком. Пробел тоже учитывается; добавьте =, запятую или : для строк вроде uid=name.',
'Оборачивать вставляемый текст escape-последовательностями, чтобы оболочка отличала вставку от обычного ввода. Отключите, если видите артефакты вида ^[[200~.',
'settings.terminal.behavior.autoUploadClipboardImage':'Авто-загрузка изображений буфера обмена при вставке',
'Когда в буфере обмена есть изображение, вставка в удалённой сессии загружает его по SFTP в каталог .netcatty-paste-images/ в текущем удалённом каталоге и вводит удалённый путь вместо вставки текста.',
'settings.terminal.behavior.shiftEnterNewline':'Текст для Shift+Enter',
'Выберите, когда вкладки сеансов должны следовать заголовкам окна, сообщаемым оболочкой.',
'Выберите, когда вкладки сеансов должны следовать заголовкам окна оболочки и обновлять значки coding CLI. «Отключено» также останавливает распознавание значков.',
'settings.terminal.connection.x11Display.desc':'Необязательный адрес локального дисплея для перенаправления X11. Оставьте пустым, чтобы использовать системное значение по умолчанию.',
'settings.terminal.connection.x11Display.placeholder':'Авто (:0 или DISPLAY)',
'settings.terminal.hostInfoBar.show':'Показывать сведения о хосте',
'settings.terminal.hostInfoBar.show.desc':'Показывать адрес хоста и сведения о сервере над терминалом. Кнопки действий остаются доступными после скрытия.',
'settings.terminal.hostInfoBar.titleMode':'Заголовок сведений о хосте',
'settings.terminal.hostInfoBar.titleMode.desc':'Показывать в панели адрес подключения (user@host) или метку хоста из хранилища.',
'settings.terminal.rendering.renderer.desc':'Выберите технологию рендеринга терминала. В режиме "Авто" на устройствах с малым объёмом памяти будет использоваться DOM. Изменения применяются к новым терминальным сессиям.',
'settings.terminal.inlineImages.enabled.desc':'Показывать настоящие изображения, которые выводят удалённые программы (kitten icat, chafa, предпросмотр в файловых менеджерах и TUI). Изменения применяются к новым терминальным сессиям.',
'settings.terminal.inlineImages.iip.desc':'Принимать последовательности OSC 1337 из iTerm2 (PNG, JPEG, первый кадр GIF).',
'settings.terminal.inlineImages.storageLimit':'Кэш изображений на терминал',
'settings.terminal.inlineImages.storageLimit.desc':'Сколько памяти терминал может занимать декодированными изображениями до вытеснения самых старых (8–512 МБ).',
'settings.terminal.inlineImages.maxMegapixels':'Максимальный размер изображения',
'settings.terminal.inlineImages.maxMegapixels.desc':'Наибольшее изображение, которое будет декодировано; более крупные отбрасываются без изменения буфера терминала (1–64 Мпикс, 16 Мпикс — это 4096 × 4096).',
'settings.terminal.inlineImages.sequenceLimit':'Максимальный размер последовательности',
'settings.terminal.inlineImages.sequenceLimit.desc':'Наибольшая escape-последовательность изображения, принимаемая до начала декодирования (1–64 МБ).',
'settings.terminal.inlineImages.hibernateNote':'Вкладки, показавшие встроенное изображение, сохраняют свой рендерер и не уходят в спящий режим, потому что снимок восстанавливает только текст.',
'vault.groups.deleteDialog.desc':'Группа будет безвозвратно удалена, а все хосты будут перемещены в корень.',
'vault.groups.deleteDialog.managedDesc':'Это управляемая группа SSH-конфига. При её удалении также будут удалены все хосты и снята связь с исходным файлом.',
'vault.groups.deleteDialog.deleteHosts':'Также удалить все хосты в этой группе',
'scripts.trigger.onConnect':'Запуск при подключении',
'scripts.trigger.onOutput':'Запуск при совпадении вывода',
'scripts.trigger.onOutputHint':'Срабатывает, когда вывод сервера совпадает с шаблоном (эхо нажатий клавиш игнорируется). Если целевые хосты не настроены, отслеживается текущая подключённая сессия; иначе — только указанные хосты. Не работает в приложениях альтернативного экрана, таких как vim или htop. Не запускается, пока в этой сессии выполняется другой скрипт; после его завершения проверка повторяется. Для ожидания внутри сценария используйте nct.screen.waitForText или nct.screen.waitForRegex в коде скрипта.',
'scripts.actions.save':'Сохранить',
'scripts.actions.runNow':'Запустить',
'scripts.actions.openEditor':'Открыть редактор',
'scripts.actions.openEditorHint':'Редактировать скрипт в большом окне',
'scripts.editor.modalTitle':'Редактор скриптов',
'scripts.editor.modalSubtitle':'Редактируйте метаданные и код скрипта в большом рабочем пространстве.',
'scripts.editor.lineCount':'{count} строк',
'scripts.editor.resize':'Изменить размер редактора',
'scripts.targets.hint':'Автоматические триггеры применяются к выбранным хостам или ко всем хостам, если включена эта настройка.',
'scripts.targets.connectOrderHint':'Порядок запуска скриптов подключения настраивается для каждого хоста в разделе «Сведения о хосте → Автоматизация».',
'scripts.actions.runNowHint':'Запускается для выбранных целей либо для всех доступных для подключения хостов, если включена эта настройка.',
'scripts.actions.runParallel':'Запустить на всех вкладках (параллельно)',
'scripts.actions.runSequential':'Запустить на всех вкладках (последовательно)',
'scripts.actions.runOnAllTabs':'Запустить на всех вкладках',
'scripts.actions.skippedConnectingSessions':'Пропущено вкладок, которые ещё подключаются: {count}',
'scripts.actions.skippedSensitiveSessions':'Пропущено вкладок с паролем/чувствительным вводом: {count}',
'scripts.actions.noRunnableHosts':'Нет доступных для подключения хостов, соответствующих целям этого скрипта',
'scripts.sidePanel.library':'Библиотека',
'scripts.sidePanel.running':'Выполняются',
'scripts.sidePanel.newScript':'Новый скрипт',
'scripts.running.empty':'В этой сессии не выполняются скрипты.',
'scripts.recording.startHint':'Записывайте действия в активном терминале и создавайте код скрипта nct',
'scripts.recording.unavailableHint':'Запись здесь недоступна — используйте панель скриптов справа',
'scripts.recording.activeHint':'Идёт запись этого терминала. Вводите команды как обычно; нажмите «Остановить» или кнопку REC на панели инструментов, чтобы завершить и сохранить запись.',
'scripts.recording.started':'Запись начата — работайте в терминале',
'scripts.recording.noSession':'Сначала подключите терминал (одну вкладку или рабочее пространство)',
'scripts.recording.alreadyActive':'В другом терминале уже идёт запись',
'scripts.recording.helpIntro':'Запись превращает ваши действия в терминале в повторно используемый сценарий автоматизации — удобно для развёртываний, проверок состояния и других повторяющихся задач.',
'scripts.recording.helpStep1':'Подключитесь к хосту. Подойдут как отдельная вкладка терминала, так и терминал в рабочем пространстве — откройте панель скриптов справа.',
'scripts.recording.helpStep2':'Нажмите «Начать запись». Красный значок REC на панели инструментов терминала означает, что запись активна.',
'scripts.recording.helpStep3':'Вводите команды в терминале как обычно. Каждое нажатие Enter сохраняется как отдельный шаг.',
'scripts.recording.helpStep4':'Закончив работу, нажмите «Остановить запись» или кнопку остановки рядом с REC на панели инструментов терминала.',
'scripts.recording.helpStep5':'Укажите название и сохраните скрипт. Выберите «Сохранить и редактировать», если хотите доработать созданный код в редакторе.',
'scripts.recording.helpTipsTitle':'Советы',
'scripts.recording.helpTip1':'Паузы между действиями дольше одной секунды записываются как время ожидания, чтобы воспроизведение не было слишком быстрым.',
'scripts.recording.helpTip2':'После каждой команды запись ждёт приглашения оболочки (например, $ или #), прежде чем перейти к следующему шагу.',
'scripts.recording.helpTip3':'Ввод пароля отмечается как конфиденциальный и не сохраняется в скрипте открытым текстом.',
'scripts.recording.helpTip4':'Для vim, меню и других интерактивных сценариев сначала запишите основные команды, а затем доработайте скрипт вручную.',
'hostDetails.automation.equivalenceHint':'Связь добавляет этот хост в список целей скрипта в разделе «Скрипты». Оба представления синхронизированы.',
'hostDetails.automation.queueHint':'Скрипты запускаются по порядку при подключении этого хоста. Сначала выполняются глобальные скрипты, затем эта очередь.',
// Network Device Mode auto-detection tip (session header)
'terminal.networkDevice.tip.message':'Похоже на сетевое устройство. Включите режим сетевого устройства, чтобы команды отправлялись как есть (без обёртки оболочки).',
'terminal.networkDevice.tip.action':'Включить',
'terminal.networkDevice.tip.dismiss':'Скрыть',
'terminal.networkDevice.tip.enabled':'Режим сетевого устройства включён для {host}',
'vault.import.progress.persistFailed':'Не удалось сохранить импортированные хосты. Освободите место в локальном хранилище и повторите попытку.',
'vault.import.progress.rollbackFailed':'Не удалось безопасно отменить импорт. Некоторые хосты могли остаться в хранилище; проверьте его перед повторной попыткой.',
'vault.import.sshConfig.managedDestinationHint':'Управляемая синхронизация всегда использует отдельную группу; выбранная выше группа применяется только к разовому импорту.',
'vault.import.sshConfig.managedSuccess':'Импортировано {count} хостов. Файл теперь находится под управлением.',
'vault.import.sshConfig.alreadyManaged':'Этот файл уже находится под управлением.',
'vault.import.sshConfig.alreadyManagedDesc':'Этот файл уже управляется в группе "{group}". Если хотите импортировать его заново, сначала удалите существующий управляемый источник.',
'sftp.transfers.pathConflict.paused':'Другая передача «{name}» приостановлена. Сначала возобновите или отмените её.',
'sftp.transfers.pathConflict.inProgress':'Другая передача «{name}» уже выполняется.',
'sftp.transferCenter.title':'Передача файлов',
'sftp.transferCenter.quitConfirm':'Незавершённых передач: {count}. Прогресс будет сохранён, а после следующего запуска передачу нужно продолжить вручную. Выйти?',
'settings.sftp.transferConcurrency.desc':'Количество файлов, передаваемых параллельно при загрузке или скачивании папок. Более высокие значения могут ускорить работу, но способны перегрузить некоторые серверы.',
'settings.sftp.transferConcurrency.desc':'Максимальное число файлов, одновременно передаваемых для одной цели, включая содержимое папок и отдельно выбранные файлы. Большие значения могут ускорить передачу, но перегрузить некоторые серверы.',
'settings.sftp.transferPoolIdleTtl.desc':'Как долго простаивающие SFTP-соединения пула передачи остаются открытыми. Большее время снижает задержку перед следующей передачей. Активные передачи не закрываются.',
'settings.ssh.transportIdleTtl.desc':'Как долго общее SSH-соединение остаётся открытым после освобождения последнего lease терминала, SFTP-браузера, пакетной передачи или проброса портов. Пока оно тёплое, повторное подключение к тому же хосту может обойти повторную аутентификацию (включая MFA). Активные сессии и передачи не закрываются.',
'settings.ssh.transportIdleTtl.1m':'1 минута',
'settings.ssh.transportIdleTtl.5m':'5 минут',
'settings.ssh.transportIdleTtl.15m':'15 минут',
'settings.ssh.transportIdleTtl.30m':'30 минут',
'settings.ssh.transportIdleTtl.never':'До выхода из приложения',
'settings.sftp.defaultOpener':'Приложение для открытия по умолчанию',
'settings.sftp.defaultOpener.desc':'Выберите приложение по умолчанию для открытия файлов без конкретной ассоциации',
'settings.explorerContextMenu.enable':'Показывать «Open in Netcatty» в контекстном меню папок',
'settings.explorerContextMenu.enableDesc':'Добавляет пункт в меню папок и фона папки, чтобы открыть локальный терминал в этом каталоге. Применяется сразу, переустановка не нужна.',
'settings.jmsDeepLink.enableDesc':'Зарегистрировать Netcatty как обработчик клиентских ссылок JumpServer jms://. Может конфликтовать с официальным клиентом JumpServer.',
// Settings > SFTP Auto Sync
'settings.sftp.autoSync':'Автосинхронизация с удалённым сервером',
'hostDetails.sftp.fileProtocol.desc':'Авто сначала пробует SFTP, затем SCP-режим, если подсистема SFTP недоступна. Принудительный SCP — для хостов только с SCP.',
'hostDetails.agentForwarding.agentNotRunningHint':'SSH Agent не обнаружен. Включите OpenSSH Authentication Agent в службах Windows или используйте совместимый агент, например Bitwarden, 1Password или gpg-agent.',
'hostDetails.agentForwarding.agentNotRunningHint':'SSH-агент не обнаружен. Включите OpenSSH Authentication Agent в службах Windows или используйте совместимый агент, например Bitwarden, 1Password или gpg-agent.',
'hostDetails.legacyAlgorithms.desc':'Включить устаревшие SSH-алгоритмы (diffie-hellman-group1, ssh-dss, 3des-cbc и т. д.) для подключения к старому сетевому оборудованию.',
'hostDetails.legacyAlgorithms.warning':'У этих алгоритмов есть известные слабые места безопасности. Включайте только для устаревших устройств, которые не поддерживают современную криптографию.',
'hostDetails.skipEcdsaHostKey.desc':'Некоторые старые коммутаторы Huawei / Cisco выдают нестандартные подписи ECDSA host-key, из-за чего соединение падает с "signature verification failed". Включение этой опции убирает все ecdsa-sha2-* из предложения клиента, и согласование переходит к RSA / Ed25519.',
'hostDetails.algorithms.advanced.desc':'Заменить предлагаемый список алгоритмов для любой категории для конкретного хоста. Не трогать категорию = использовать значение по умолчанию; выбранное подмножество полностью заменяет список по умолчанию. Неверные значения могут сделать хост недоступным.',
'hostDetails.keepalive.desc':'Использовать для этого хоста собственную политику keepalive вместо глобальной настройки. Полезно для старых маршрутизаторов и коммутаторов, чей SSH-сервер не отвечает на запросы keepalive@openssh.com. Установите интервал 0, чтобы полностью отключить keepalive для этого хоста.',
'hostDetails.keepalive.countMax':'Макс. число пропущенных keepalive',
'hostDetails.keepalive.disabledHint':'Интервал = 0 отключает keepalive для этого хоста. Для определения разорванного соединения сессия будет полагаться на TCP-таймауты.',
Loaded 100 of 1734 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.