Add explicit confirmation, on-disk cleanup and failure retry for local vault backup history, while preserving active and interrupted recovery snapshots.
* Polish monitoring panels and preserve host icons
* Simplify monitoring layout and show every disk partition
* Simplify list rows and add resizable monitoring overlays
* feat(monitor): add modal drawers, sampled hover values and faster refresh
* feat(monitor): reset run history and support saved drag ordering
* perf(monitor): stream history deltas and keep fast samples lightweight
* fix(monitor): separate chart histories across connection identities
* refine(monitor): show disk capacity on hover and use inline sidebars
* fix(monitor): omit unchanged stats from polling deltas
* test(monitor): freeze time in full-history delta checks
* perf(monitor): bound chart paths and reuse unchanged histories
* Codex worktree snapshot: startup-cleanup
Co-authored-by: Codex
* fix(browser): close malformed proxy connections without a reply
* feat(browser): embed host browsing in terminal side panels
* style(browser): blend compact browser chrome into terminal panels
* feat(browser): open terminal links internally with a system browser preference
* fix(browser): retain the first terminal link during panel mount replay
* fix(browser): release contexts when their split pane closes
* style(browser): remove the downloads panel shadow
* fix(browser): ignore late downloads after panel closure
* fix(browser): mount browser content on terminal auto-open
* fix(browser): preserve empty-vault recovery with bookmarks
* fix(browser): expose actions for supported terminal types
* fix(browser): retain open requests before the panel mounts
* fix(browser): align vault actions with supported hosts
* fix(browser): share download controls across host contexts
* fix(browser): keep vault browser requests on visible sessions
* fix(browser): release contexts after workspace detach
* fix(browser): enforce WebRTC routing on page contents
* fix(browser): skip unsupported sessions in open requests
* style(browser): encode glyphs in ASCII-safe source
Deleting saved hosts now removes their jump references from surviving hosts and group defaults in all ordinary deletion flows. Host-only bulk deletion uses the existing vault mutation boundary, and explicit empty chains retain their original group-override meaning.
Validation: 282 focused checks, lint/build, two independent full-diff reviews, latest-head Codex review, current GitHub checks, and actual isolated Electron deletion/restart/native SSH reconnection.
Fixes#3691
Add the Global history clear-all action with confirmation and five-language copy. Clear local shell history, autocomplete and compose recall while preserving drafts, keeping peer windows consistent on clears, and allowing new commands afterwards.
Verified with 116 focused checks, two clean independent final reviews, real Electron multi-window SSH-fixture interaction and restart, current-head Codex review, and both required CI jobs.
Co-authored-by: fyhax1ki <fyhax1ki@gmail.com>
Preserve the original local shell name and icon when a saved host is edited and a terminal is copied.
Related to #3615 and #3616.
Validated with 70 focused checks, lint, production build and actual terminal copy flows. Current-head remote review and CI pass.
Related to #3615 (stays open: hiding/restoring running sessions is separate work).
Merged by judgment after ~49 Codex findings; remaining P2 (Copy Tab name/icon snapshot when host shell is edited after launch) is a cosmetic edge case for a follow-up.
Match host reuse to the actual Telnet login, ET hop ports and serial/local session settings. Preserve already-scoped reconnecting tabs when other sessions remain live, and reject Telnet proxy settings consistently before reusing a connection.
Follow-up to #3614. Related to #3613; no claim of a released fix or full PR3616 inherited-default coverage.
Validated with focused checks, real Electron/TCP Telnet connections, build/lint, exact-head Codex review and current automated test/package checks.
Report transport closure without inventing a successful remote exit status, while preserving any actual exit code or signal already received. Reuse the existing exit classification to keep normal exits from being treated as reconnectable disconnects.
Related to #3631. This changes the diagnostic message only; the frequent-disconnection report remains open.
Validation: 458 focused checks, lint, production build, real Electron/local SSH disconnect workflow, current-head Codex review, and repository CI passed.
Classify SSH exit-signal as signaled so auto-reconnect does not fight admin idle kicks. Bare transport closed and error still reconnect.
Related to #3617.
* feat(terminal): disable auto-close-on-exit by default
Remote graceful channel closes (VPN half-close, server-side idle kick) can be reported as a clean exit with code 0, so with auto-close enabled the tab gets destroyed exactly when the user needs it kept - bypassing the SSH auto-reconnect loop, which intentionally skips exited events.
Keep tabs on exit by default; users who want close-on-exit can enable it in Settings > Terminal > Behavior. Attach-mode close behavior is unchanged.
* fix(terminal): keep session tabs when the auto-close setting is unavailable
The resolver's own default and the TerminalLayer call-site fallback still
treated a missing terminal setting as auto-close enabled. Remote graceful
channel closes (a jump host reaping an idle session) are reported as a
clean exit, so before the settings store had loaded, those closes silently
deleted the session tab and its scrollback. This completes the opt-in
default introduced above: only an explicit autoCloseOnExit true closes tabs.
* fix(terminal): migrate the materialized auto-close-on-exit default once
Builds before the default flip shipped autoCloseOnExit: true as the
default and materialized it into netcatty_term_settings_v1 on any
settings write, so most upgraders carry a value they never chose and
the new default-off never reaches them: bastion and idle-harvesting
servers reporting clean exits keep silently deleting their tabs.
Reset that materialized true exactly once behind a marker key before
the settings blob is first ingested, then persist the marker so every
later stored value is treated as the user's own choice. The cost is
documented: a user who deliberately enabled auto-close before this
release must re-enable it once.
* fix(terminal): keep synced legacy auto-close from coming back
A device that already wrote the one-shot migration marker was accepting
a pre-migration cloud or backup payload with autoCloseOnExit true.
Unversioned true is rewritten to false. Payloads that record the
migration still sync an explicit opt-in, including across a three-way
merge that would otherwise glue the local marker onto a legacy true.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: xu <tbqyomnnnbltzxy2t@aliyun.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(terminal): enable SSH auto-reconnect by default
Unexpected transport drops (VPN / Wi-Fi blips) are recoverable events, not
shell exits: the tab stays and the 5s in-place reconnect loop keeps the same
session id, so AI sessions and long-running work continue once the link is
back. With the previous default (off), a dropped link left the tab parked on
'Press Enter to reconnect', which reads as 'the session closed'.
Clean shell exits still close the tab; explicit opt-out in Settings is
preserved. mosh/et sessions self-heal and are unaffected by this gate.
* fix(terminal): skip auto-reconnect after idle timeouts
SSH auto-reconnect stays on by default for transport drops. A timeout
exit (TMOUT, server idle logout, or connect timeout) leaves the tab
disconnected instead of logging back in every 5 seconds.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: yuyang2230 <yuyang2230@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* feat(ai): raise default AI session idle timeout from 30m to 24h
Sessions opened via host_open are auto-closed after 30 minutes without
terminal/file activity. In production use over flaky links this silently
destroyed in-flight agent work: the AI reopens a host, the link drops, the
session sits disconnected (counts as idle), and the reaper closes it
mid-workflow with no error surfaced anywhere. 24h is the normalize clamp
maximum; users can still choose a shorter window in Settings → AI, and the
idle manager still skips sessions with active executions.
* fix(ai): preserve 24-hour session default during renderer sync
* style(ai): keep session idle manager comment ASCII-only
Replace the unicode arrow in the Settings pointer comment with ASCII per the repo AGENTS.md convention (Maintain ASCII-only unless required by existing file content).
* fix(ai): default AI session idle timeout to 8 hours
480 minutes still covers a workday of flaky-link agent work without
sitting on the 24h clamp. Stored values are left untouched.
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
---------
Co-authored-by: yuyang2230 <yuyang2230@users.noreply.github.com>
Co-authored-by: 陈奇 <16399091+binaricat@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
* fix(ai): shed oversized tool output before evicting visible chat history
Under localStorage quota pressure the AI sessions serializer degraded
replay ciphertext fine-grained but never shed tool result payloads, so
AI-driven terminal chats (whose persisted bulk is terminal output)
forced the ladder to evict whole oldest sessions and finally collapse
persistence to the newest chat alone. After a restart the history list
looked empty.
Extend the per-message degradation pass to cap oversized tool result
content at a bounded head+tail with a truncation marker, mirroring the
existing ciphertext-shedding tradeoff: every visible conversation now
survives storage pressure at the cost of full tool output on affected
messages.
* fix(ai): keep tool-result truncation strictly smaller than the original
Codex review P2: for tool results only slightly over the cap, the
truncation marker (with JSON-escaped newlines) could make the degraded
copy serialize at the same size or larger than the original, breaking
the ladder's assumption that the degraded representation is the
smallest and sending quota retries farther off.
Drop the marker newlines and shrink the retained head+tail until the
whole copy fits the 8 KiB cap, so a truncated result is always
strictly shorter than its source.
---------
Co-authored-by: xu <tbqyomnnnbltzxy2t@aliyun.com>
Preserve pending local notes and conflicting external content across failed writes and successful retries.
Retain explicit clear/import replacement ownership until the current generation persists.
Cloud sync uploaded the whole encrypted vault as a single payload. Two
problems follow from that, both of which we hit in production:
1. A one-sided deletion propagated to every device. Restoring an older
local backup is indistinguishable from "the user deleted these 25
hosts", so the next sync happily overwrote the cloud replica and
every other machine with the older, smaller vault.
2. AI chat sessions were not part of the sync payload at all, so they
never travelled between machines and never appeared in any backup.
This change adds an opt-in "keep all" merge and makes sessions syncable.
## Non-destructive merge
`mergeSyncPayloads` gains `{ preserveDeletions }`. When set, an entity
missing on one side is carried forward from the side that still has it;
only entities absent from base *and* both sides are removed. It is
exposed as the new `smartMergePreserve` strategy ("Smart merge (never
delete)") and threaded through every merge call site:
- `providerSyncMethods.syncToProviderImpl`
- `syncAllStorageMethods`
- `useAutoSync`
Default behaviour (`smartMerge`) is unchanged, so existing users see no
behaviour change until they opt in.
## AI sessions in the payload
`SyncPayload.aiSessions` is new and opt-in via the
"Sync AI chat history" switch in Settings → Cloud Sync. Sessions are
unioned by id, and messages are merged **append-only**: a message is
identified by id and the merged transcript is the union of all three
sides ordered by timestamp. Two devices that appended different turns to
the same conversation therefore keep both turns, instead of one device
silently dropping the other's work.
Applying a downloaded payload unions with the local transcript rather
than replacing it, so an older cloud snapshot can never delete turns
that only exist locally.
Inline attachment blobs (`base64Data`) are stripped before upload —
image/PDF payloads would multiply the encrypted vault size and may carry
data the user never intended to leave the device. Attachment metadata
(filename, media type, line count) is kept for context.
## Tests
- `domain/syncMerge.preserveDeletions.test.ts` — one-sided deletions
(local and remote), both-sides deletion, additions, notes/snippets/
port-forwarding/string entities, idempotence, summary reporting.
- `domain/syncMerge.aiSessions.test.ts` — session union, append-only
message merge, timestamp ordering, metadata LWW, strategy-aware
deletion, attachment stripping, idempotence.
Verified locally: 1939/1939 tests pass across the domain, convergentSync,
i18n, cloudSync and application sync suites; eslint clean on all touched
files.
Add a detached multi-tab editor window with acknowledged content ownership, focused Monaco shortcuts, and shared close/save confirmation. Preserve source contents until handoff acknowledgement and block edits or incoming transfers during ownership changes.
Continuation of #3439. Original implementation by pplulee.
Co-authored-by: pplulee <hi@pplulee.me>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(vault): add host connection test to the host editor
Add a 'Test' button next to Save in the host editor that opens a dialog reusing the terminal's connection progress/auth/host-key UI, and runs a headless connection probe:
- Main process: netcatty:test-connection reuses startSSHSession in a testMode that authenticates then tears down without opening a shell, reports via netcatty:test:result, and supports cancellation (netcatty:test-connection:cancel).
- Renderer: useHostConnectionTest hook + HostConnectionTestDialog component; single-credential auth (no agent/default-key fallback) with short test timeouts (8s TCP / 15s auth).
- Reuses terminal chain progress, host-key verification, and auth prompts; success keeps the same dialog with retry/close.
* fix(ssh): cancel test connections during chain/proxy dial
Register the headless test-connection transport before connectThroughChain / createProxySocket runs, and update its pendingConn / chainConnections as hops and proxy sockets are established. This lets cancelTestConnection abort a slow/unreachable bastion or proxy dial instead of leaving it running until timeout.
* fix(ssh): address host connection test review feedback
- Route jump-host keyboard-interactive prompts through the external scope in test mode
- Always offer keyboard-interactive for requiresMfa hosts in test mode, including key/certificate-only auth
- Derive the renderer test deadline from the probe options and cancel the attempt on timeout
- Run the macOS Local Network preflight before connection tests
- Add regression tests for jump-host MFA, MFA auth order, test deadline, and macOS preflight
Author: yangheng
Model: claude-opus-5
---------
Co-authored-by: yangheng <492238647@qq.com>
* feat(ai): scope external MCP approvals to connection or host
* test(ai): keep MCP fixture in Netcatty temp directory
* fix(ai): refine external MCP approval UI and grant safety
* fix(ai): wrap long approval targets and support Node 22
* fix(ai): keep automatic scripts in observer mode
* fix(sftp): release preflight markers after transfer
* fix(sftp): retain preflight cap through conflict resolution
* Keep SFTP preflight cap across panel remount
* Update worker terminal metadata expectations
* Persist SFTP preflight skip state across restart
* Limit persistent host approvals to saved hosts
* Scope host-open approvals to the selected saved host
* Exclude serial hosts from persistent approval scopes