Recreate cursor-line markers/decorations when xterm disposes them externally, and treat empty decoration sets as intact so fully colored rows do not churn markers every frame.
Fixes#3656
Clamps the window layout minimum to the current display work area so small screens (e.g. 1366×768 at 125%) can fit and shrink. Restores terminal keyboard focus after OS resize/maximize, without stealing from active editable fields.
Fixes#3647
Merged by judgment: four Codex rounds already addressed the real bugs and multi-monitor sync; the latest P2 (protect every non-editable focusable control on resize) is edge-case thrash.
* 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>
Match argument snippets conservatively and remove stale snippet rows on input edits before the debounced refresh.
Related to #3622; the email-entry/editor concern remains unverified and the issue stays open.
Preserve pending local notes and conflicting external content across failed writes and successful retries.
Retain explicit clear/import replacement ownership until the current generation persists.
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>
Use xterm's native synchronized output state for viewport preservation, remove the redundant mode tracker, and exercise the production callback path in regression tests.
Fixes#3595
Co-authored-by: si1enteee <si1enteee@users.noreply.github.com>
* feat(vault): add host connection test to the host editor
Add a 'Test' button next to Save in the host editor that opens a dialog reusing the terminal's connection progress/auth/host-key UI, and runs a headless connection probe:
- Main process: netcatty:test-connection reuses startSSHSession in a testMode that authenticates then tears down without opening a shell, reports via netcatty:test:result, and supports cancellation (netcatty:test-connection:cancel).
- Renderer: useHostConnectionTest hook + HostConnectionTestDialog component; single-credential auth (no agent/default-key fallback) with short test timeouts (8s TCP / 15s auth).
- Reuses terminal chain progress, host-key verification, and auth prompts; success keeps the same dialog with retry/close.
* fix(ssh): cancel test connections during chain/proxy dial
Register the headless test-connection transport before connectThroughChain / createProxySocket runs, and update its pendingConn / chainConnections as hops and proxy sockets are established. This lets cancelTestConnection abort a slow/unreachable bastion or proxy dial instead of leaving it running until timeout.
* fix(ssh): address host connection test review feedback
- Route jump-host keyboard-interactive prompts through the external scope in test mode
- Always offer keyboard-interactive for requiresMfa hosts in test mode, including key/certificate-only auth
- Derive the renderer test deadline from the probe options and cancel the attempt on timeout
- Run the macOS Local Network preflight before connection tests
- Add regression tests for jump-host MFA, MFA auth order, test deadline, and macOS preflight
Author: yangheng
Model: claude-opus-5
---------
Co-authored-by: yangheng <492238647@qq.com>
* feat(ai): scope external MCP approvals to connection or host
* test(ai): keep MCP fixture in Netcatty temp directory
* fix(ai): refine external MCP approval UI and grant safety
* fix(ai): wrap long approval targets and support Node 22
* fix(ai): keep automatic scripts in observer mode
* fix(sftp): release preflight markers after transfer
* fix(sftp): retain preflight cap through conflict resolution
* Keep SFTP preflight cap across panel remount
* Update worker terminal metadata expectations
* Persist SFTP preflight skip state across restart
* Limit persistent host approvals to saved hosts
* Scope host-open approvals to the selected saved host
* Exclude serial hosts from persistent approval scopes
* fix(terminal): commit live IME transcription once
xterm sends a rewritten dictation hypothesis as the interim, the full
final string, and the tail. Keep the pending textarea diff from firing
again when composition starts, and send only the part that is not
already on the PTY.
* fix(terminal): retype rewritten IME tails as separate backspaces
A shared suffix has to be deleted and typed again because the cursor
stays at the end of the line. One-character hypotheses are real
prefixes, and each backspace is its own input event so Ctrl-H hosts
still erase.
* fix(terminal): rewrite a revised IME hypothesis in place
A composition that replaces the snapshot, instead of extending it,
sends the cancelled textarea edit. Deletions are counted in graphemes
so a surrogate pair is one backspace.
* fix(terminal): send a new IME word that shares a finished prefix
A composition that starts at or after the snapshot is a new word.
Sharing the first character with text already on the PTY is not
enough to treat it as the same hypothesis.
* fix(terminal): remember a rewritten IME hypothesis in full
A 229 rewrite stored only the inserted tail, so a later
compositionend with no composition marker sent the revised
line again. Remember the whole revised textarea instead.
* fix(terminal): preserve IME rewrite state across delayed commits
* fix(ui): apply the chosen app icon to the Windows tray
Settings already swap the taskbar icon. On Windows, the notification
area now follows that choice. Original keeps the multi-size tray ico;
other variants are scaled to the small-icon size Windows asks for.
* fix(ui): crop the app-icon margin before sizing the Windows tray
The variant PNGs keep a transparent inset, so scaling them straight into
the notification slot made every non-original style smaller than the
full-bleed tray ico. Crop to the opaque artwork first.
* test(sftp): update the side-panel reuse source lock
The single-channel change replaced the connected-only sourceSessionId
ternary with reuseTerminalTransport. The source lock still expected the
old expression, so lint-and-test fails on main.
Add a host and group toggle for bastions that allow one session channel per TCP connection. After the handshake, BHostSSH, TERM-SSHD, and CLOUDBILITY take that path automatically. File transfer uses a separate SFTP login, and the interactive terminal is not given another exec, SFTP, copy/split, or port-forward channel.
The manual toggle remains for bastions that still advertise OpenSSH.
fontLigatures already defaulted to on, but the terminal never loaded
the ligatures addon, so Fira Code and Maple Mono kept drawing != as two
cells. Activate the addon after open and before WebGL, and rebuild the
WebGL atlas when the setting changes so glyphs are not rasterized
without the calt feature.
The addon bundles lru-cache's node:diagnostics_channel import. Alias
that builtin to a no-op channel in the Vite renderer build and in the
Electron live-test bundles.
Closes#3543
* feat(ai): add MiMo Code (mimo) agent support scaffolding
Registry/wiring only: discovery entry, managed-agent metadata, model presets, icon, i18n and Settings UI for Xiaomi's MiMo Code CLI (an OpenCode fork). The SDK driver is intentionally not wired yet - it depends on the pending @mimo-ai/sdk parity probe, so DRIVER_REGISTRY keeps a TODO and run-turn/list-models reject the mimo backend until then.
* feat(ai): add MiMo Code (mimo) agent support
Adds Xiaomi's MiMo Code CLI as a managed agent backend, mirroring the existing
OpenCode integration (discovery, managed-agent metadata, model presets, icon,
i18n, Settings UI) and adding the SDK driver that was left as a TODO in the
scaffolding commit.
Driver notes:
- New electron/bridges/aiBridge/sdk/mimoDriver.cjs reuses the pure translation
helpers exported by opencodeDriver.cjs (config/prompt building, event
translation, model mapping) because MiMo Code is an OpenCode fork and speaks
the same wire protocol.
- It does NOT use @mimo-ai/sdk's createOpencode(). On 0.1.15 that helper spawns
a binary named "opencode", waits for an "opencode server listening" banner,
and passes config via OPENCODE_CONFIG_CONTENT; the mimo CLI prints
"mimocode server listening on <url>", reads MIMOCODE_CONFIG_CONTENT, and ships
as "mimo"/"mimo.exe". So createOpencode() can only ever time out. The driver
runs "mimo serve" itself, parses the readiness URL, and attaches the SDK's
createOpencodeClient({ baseUrl }).
- Binary resolution honours binPath, MIMOCODE_BIN and MIMOCODE_BIN_PATH, then
falls back to PATH via resolveCliFromPath (the npm shim is mimo.cmd on
Windows, which Node cannot spawn directly).
- @mimo-ai/sdk ^0.1.15 added as a dependency.
Verified on Windows against mimo 0.1.15 + @mimo-ai/sdk 0.1.15:
- "mimo serve --port" boots and reports its URL; driver parses it.
- SDK symbols used all exist: createOpencodeClient, client.config.providers(),
client.global.event() (SSE stream), client.session.create/abort/promptAsync.
- Live provider catalog reads 2 providers (xiaomi, mimo); MIMO_MODEL_PRESETS
updated to the real lower-case provider-prefixed ids and its TODO removed.
- Server process is killed on close (taskkill tree kill), no orphan processes.
Not yet verified at runtime:
- A full end-to-end turn (no prompt was sent, so no tokens were spent): text /
reasoning / tool-part streaming and abort handling are untested against a
live model.
- MIMOCODE_CONFIG_CONTENT accepting the OpenCode config schema (permission,
mcp, model, share, autoupdate) end-to-end; only that the env var exists in
the binary has been confirmed.
- Whether the loopback server needs MIMOCODE_SERVER_PASSWORD when the API
client attaches.
- The placeholder public/ai/agents/mimo.svg still needs an official asset.
- Multi-turn resume (resumeSessionId) and MCP injection against the live CLI.
* feat(ai): inject MIMOCODE_BIN for discovered MiMo Code agents
Mirror the OpenCode OPENCODE_BIN handling so a discovered or re-detected
mimo binary path reaches the driver, and cover it with the same assertion
shape as the opencode managed-agent env test.
* fix(ai): use MiMo branding in the mimo driver
* test(ai): cover the mimo driver
Add a unit test file for the MiMo Code driver, matching the existing
opencode driver test in shape, runner (node:test via node --test) and
`.test.cjs` extension. It is the last driver in the registry without
one.
Coverage:
- spawn/launch: `mimo serve --hostname/--port` argv, binary resolution
(explicit binPath / MIMOCODE_BIN / MIMOCODE_BIN_PATH), pass-through
env and MIMOCODE_CONFIG_CONTENT; `child_process.spawn` is stubbed, so
no real `mimo` process is ever started.
- readiness: parsing of `mimocode server listening on <url>` from
stdout (including split chunks) and stderr, the OpenCode spelling,
and the MIMO_SERVE_TIMEOUT_MS timeout path (asserts the rejection and
that the child is killed).
- abort/cleanup: stopMimoProcess kill/idempotency/swallow, the Windows
taskkill tree kill, server.close() teardown, and abort mid-turn with
a throwing close().
- branding: OpenCode -> MiMo Code for the busy/idle/status, failed and
tool-failure fallback strings, with tool output and non-brand
technical errors left verbatim.
- happy path: session create -> streamed event translation -> done,
plus listMimoModels catalog mapping and the empty/aborted paths.
* fix(ai): filter MiMo events by session and stop the server behind the npm shim
Address review on #3500:
- The global SSE stream carries every session on the server, and
runMimoTurn translated all of them, so another session's text could leak
into the reply and its session.idle could end the turn early. Skip events
whose session id is not ours, reusing the OpenCode driver's
getOpenCodeSessionIdFromEvent (now exported). Covered by a test with
interleaved sessions.
- On macOS/Linux the official @mimo-ai/cli `mimo` is a Node shim that runs
the native binary through spawnSync, so killing the shim left the server
listening. `mimo serve` is now spawned detached on POSIX and
stopMimoProcess signals its whole process group (SIGTERM, then SIGKILL
after a grace period). Windows keeps taskkill /T.
Verified against the real @mimo-ai/cli@0.1.15 npm shim: on Linux (WSL
Ubuntu 22.04) the listener is the native .mimocode child; before this change
it was still listening 3s after close(), now the port is released within 1s
and no mimo process remains. On Windows the .cmd shim path also releases the
port within 1s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: harden MiMo CLI integration before merge
* fix: wait for MiMo events and handle setup cancellation
* fix: finish MiMo stream handling and scope skill access
* fix: preflight MiMo skill paths before one server startup
* fix: scope MiMo skill reads to trusted roots
* fix: allow MiMo skills from ancestor project roots
* fix: honor MiMo observer and confirm command permissions
* fix: clear MiMo approval when its service exits
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* feat(vault): let Skills + CLI operate notes with the same approval as MCP
Expose vault note list, read, write, and markdown import on the tool CLI so agents can save generated documents only after the user approves.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): keep explicit empty note fields in the CLI
A supplied empty string was treated as an omitted flag, so note clears and empty markdown imports never reached the note service.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): allow an explicit empty note body on CLI create
notes create --content "" was rejected as a missing flag, so it did not match vault_notes_create.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): stop echoing imported note bodies and recognize leading CLI flags
A batch import resent every document over IPC, and a leading --json hid the notes command from artifact inference.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(vault): make note CLI import safe and fail writes cleanly
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* feat(terminal): Add configurable terminal bar cursor width
Adds a configurable bar-cursor width for the terminal so thin vertical cursors are easier to find, particularly when vi/vim switches to insert mode.
* Add terminal bar cursor width setting
This adds the terminal bar cursor width control to the settings UI with an aria-label for accessibility and a matching test. It also adds localized labels/descriptions and search catalog entries so the setting is discoverable in both English and Chinese.
* Fix terminal cursor width override bug
Prevent width-only cursor updates from clearing DEC/vi cursor overrides by tracking the last applied shape/blink snapshot. The runtime now resets the snapshot on teardown, skips full cursor reapplication for width-only changes, and preserves remote bar/underline cursor styles. Added regression tests covering boot-time and later width updates.
* Fix terminal cursor preference lifecycle
Preserve DEC/vi cursor overrides during the first width-only terminal update after runtime creation. The runtime now records the user cursor preference snapshot when a term is published and applies width-only updates without clearing active cursor style or blink overrides. This prevents regressions when terminal settings change before the panel has fully booted.
---------
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* terminal: allow Shift+Enter text inside ConPTY Win32 input mode
Win32 input mode (PR #3247) delivers Shift+Enter to ConPTY as a lossless
INPUT_RECORD, which CLIs reading console records natively (Codex, Grok) can
use. Node/Bun CLIs read input through libuv, which drops modifier state when
translating INPUT_RECORD to VT bytes, so Claude Code and CodeBuddy still see a
bare CR and submit instead of inserting a newline (anthropics/claude-code#92771,
reproducing on 2.1.278).
While the mode is active, Netcatty also skips its Shift+Enter text fallback and
Kitty encoding, so shiftEnterNewlineText had no effect on local Windows
sessions at all.
Add shiftEnterForceText (default false): when enabled, Shift+Enter sends the
configured text even inside Win32 input mode, and consumes the keydown so no
native record is emitted as well. Add `\e` (ESC) to the escape decoder so the
text can carry the legacy Alt+Enter encoding, which is the only sequence that
survives the ConPTY input path.
Defaults keep the #2974 behaviour untouched for Codex/Grok.
* docs: record libuv input findings and the Win32 input mode opt-out
* fix(terminal): don't swallow Shift+Enter when there is no text to send
The Shift+Enter send-text interception resolved its text after already
calling preventDefault/stopPropagation, and returned false outside the
`if (shiftEnterText)` guard. When resolveShiftEnterText() yields an empty
string (e.g. a persisted empty setting) the keydown was therefore consumed
with nothing written, and the handler never fell through to the Win32
bookkeeping or Kitty paths below, so no Enter reached the session either.
The result was indistinguishable from a dead feature.
Resolve the text first and only enter the interception block when it is
non-empty; empty text now falls through exactly as it did before the
`shiftEnterForceText` setting existed, so a normal Enter is still produced.
The opt-out semantics for the non-empty case are unchanged.
Also tighten the source-shape test so it verifies the real intent: the
interception (preventDefault through return false) must live inside the
empty-text guard, which fails against the previous code.
* fix(terminal): scope Shift+Enter force-text to Win32 mode, honor it per broadcast target
Address review on #3464:
- The force-text opt-out could override a negotiated Kitty encoding that
preserves Shift+Enter in non-Win32 sessions. The keydown decision now
lives in shouldClaimShiftEnterForText: under Win32 input mode it follows
shiftEnterForceText, otherwise only the existing collapse fallback
applies. Covered by a regression test for a non-Win32 session whose Kitty
encoding already preserves the chord.
- Broadcast Win32 targets returned the native record without consulting
their own shiftEnterForceText. They now send their configured text for
Shift+Enter when opted in, whether the source forwarded a key chord or a
native Win32 record, and drop the paired release so ConPTY never sees a
key-up without its key-down. Targets without the opt-out keep the native
press/release pair. Tests cover source and target settings that differ.
ShiftEnterEvent now accepts KittyKeyboardEvent's optional type/modifier
fields so broadcast events can be checked with the same helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(#3496): automated AI fix
* fix: address Codex review on PR #3499
* fix: address Codex review on PR #3499
* fix: keep live model catalogs scoped and surface discovery failures
* fix: isolate Claude catalog profiles and support xhigh effort
* fix: handle missing external model catalog state
* fix: isolate Claude model catalogs by credentials
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>
* fix(terminal): keep broadcast going on vim ex command-line
Broadcast pauses on fail-closed colon prompts so passwords are not
copied to peers. Vim command mode's last line is a bare `:`, which
matched that heuristic. Exempt lines that start with `:` so `:w` and
friends stay in sync; `Password:` is unchanged.
* fix(terminal): only treat a bare colon as vim command-mode
Codex: exempting every `:`-prefixed line would broadcast into custom
prompts such as `sudo -p ':'`. The hang is the first `:` key; later
`:w` no longer matches the trailing-colon heuristic.
* fix(terminal): exempt vim colon only on the alternate screen
A bare `:` on the primary screen can be `sudo -p ':'`. Vim command mode
uses DEC 1049, so require alternateScreen before treating `:` as ex.
* test(terminal): allow alternateScreen in prompt-security options
* fix(#3455): automated AI fix
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix: address Codex review on PR #3456
* fix(terminal): let explicit SFTP auto-open beat the saved layout
A JumpServer SFTP deep link or host.autoOpenSftpPanel must still open
file transfer when the saved default layout omitted SFTP.
* fix(terminal): skip saved layout only when SFTP auto-open is available
Telnet/plugin hosts can carry autoOpenSidePanel=sftp even though the
protocol cannot serve SFTP. Those sessions should still get the saved
default with the SFTP pane pruned.
---------
Co-authored-by: netcatty-bot <308658023+netcatty-bot@users.noreply.github.com>
Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com>