Commit Graph

55 Commits

Author SHA1 Message Date
bincxz f3ac99e587 Address terminal UI review findings 2026-07-02 01:13:38 +08:00
bincxz fe7a867f38 Improve terminal tab switching stability 2026-07-02 01:00:32 +08:00
bincxz 0b94a82579 Fix multiline snippet execution modes 2026-06-30 17:01:27 +08:00
陈大猫 6b9b71ac8a feat(scripts): terminal script automation, recording, and side panel (#1763)
* feat(scripts): add terminal script automation, recording, and side panel

Introduce nct script runtime in the main process with terminal-side recording,
execution overlay, Monaco editor, connect-script queues, and scripts side panel
integration. Fix recording to wire scriptRecorderRef into xterm, use waitForPrompt
for root/user shells, and save recorded scripts through the global editor dialog.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): address Bugbot review findings for recording and side panel

Refresh script run state in side panel memo, guard toolbar recording against
multi-session conflicts, reset connect scripts on reconnect, resolve focused
session via getActiveTerminalSessionId, and avoid wiping host edit form when
snippets list updates.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): prevent pending script re-run and improve workspace run feedback

Track one-shot pendingScript execution separately from onConnect queue so
reconnects do not replay Run-now scripts. Fall back to focused session when
running on all tabs outside a workspace, and toast when every tab is still
connecting.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): consume connect-script window once and filter serial hosts

Mark the onConnect script evaluation as consumed even when the queue is
empty so later snippet/host edits do not rerun automation mid-session.
Restore serial-host filtering in SelectHostPanel confirm handler.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): grace period before giving up on connect-script queue

Only mark onConnect automation as consumed after scripts run or a short
post-connect grace window expires, so late-loading snippets still trigger
while mid-session Vault edits do not.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): gate connect-script consumption on vault hydration

Replace the grace timer with vault-init and unresolved host script binding
checks so late-loading snippets still run onConnect automation while
mid-session edits stay suppressed. Clear pending script run state on
disconnect so reconnect retries workspace pending scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): honor run targets and retry failed connect automation

Route QuickScriptEditorDialog Run through onRunSnippet for multi-host
workspaces. Defer connect/pending script completion markers until each
script succeeds and retry the queue after failures.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): stabilize pending runs and global connect queue sync

Keep pending script run state across reconnects, run onConnect scripts
before one-shot workspace pending scripts, and strip per-host queue entries
when an onConnect script targets all hosts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): defer host queue snippet sync and guard connect runs

Persist snippet target links only when the host panel is saved, block
duplicate connect-script scheduling while a batch is running, and require
runnable hosts before showing Run in the snippets editor footer.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): retry connect failures and surface output-trigger errors

Use a retry tick in connect-script effect deps, toast automation failures
from output triggers, and require runnable hosts before QuickScript Run.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): track id-less pending runs and re-arm output triggers

Remember anonymous pendingScript objects after first run and allow
onOutput triggers to fire again when new terminal output arrives.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): unblock paused connect runs and pass session meta

Resolve waitForScriptRun on pause, run workspace scripts per session
with hostname context, defer output-trigger arming until runs start, and
add noRunnableHosts i18n for QuickScript Run.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): serialize output triggers and defer empty connect finalize

Fire at most one onOutput trigger per scan while a run is active, and
only finalize an empty connect queue after vault snippets are loaded.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): guard output launch race and script run waits

Block overlapping onOutput launches, timeout stuck waitForScriptRun
promises, and disable vault Run when no connectable targets exist.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): match output triggers on new chunks only

Scan onOutput patterns against recent terminal output instead of buffer
length dedupe, and restore overflow-visible on SelectHostPanel new-host flow.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): await sequential workspace runs and output launch

Wait for each tab script to finish in sequential workspace mode, and hold
output-trigger launch guard until the session run becomes active.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): wait through pause and hold output trigger until done

Keep connect and sequential workspace queues blocked until scripts
complete or fail, and retain output-trigger guard until the launched run
finishes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): harden output trigger wait helpers

Add explicit timeouts to output-trigger wait helpers and stop rescanning
the buffer after script completion to avoid sticky-match rerun loops.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): allow empty connect queue and match output in new chunks

Treat an empty connectScriptIds array as an explicit cleared queue instead
of re-migrating from snippet targets, and only fire onOutput triggers when
the latest terminal output contains the pattern.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): require fresh output overlap for onOutput triggers

Only fire output-matched scripts when the regex match extends into the
latest terminal chunk, preventing reruns from stale buffer text.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): promote manual scripts added to host connect queue

Diff connect queue changes from raw connectScriptIds so saving a host
updates manual scripts to onConnect when they are newly queued.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): dedupe output triggers by match position

Track the buffer end offset of each output trigger match and ignore
stale re-scans, with a longer wait for run registration before retry.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): record output trigger matches only after run starts

Propagate automation launch failures to output triggers and commit match
dedupe only once the session run is registered as active.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): address Codex review on wait cursor and sensitive codegen

Track scan offset so waitFor/waitForAny only match output after the previous wait, and emit sendLine after sensitive prompt in recorded scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(scripts): harden onOutput triggers against PTY echo and local input

Filter server output before pattern matching so onOutput fires only after
Enter on real stdout, not on keystroke/paste echo, OSC/SGR sequences, or
alternate-screen apps. Add dev diagnostics and scope triggers to the active
session when no explicit host targets are configured.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(scripts): remove onOutput trigger debug logging

Drop temporary diagnostics module and console traces now that PTY echo
filtering is verified; keep the production trigger pipeline unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 23:48:12 +08:00
陈大猫 bd3491a3e4 revert(terminal): remove auto-run snippet stty protection (#1594) (#1599)
* fix(terminal): narrow snippet terminal restore to RHEL-family hosts

Limit auto-run snippet stty protection to distros affected by #1498 and
suppress wrapper echo so Ubuntu, Rocky, and macOS users see their raw
commands again.

Fixes #1594

Co-authored-by: Cursor <cursoragent@cursor.com>

* revert(terminal): remove auto-run snippet stty protection (#1594)

The wrapper added in #1498 caused visible preamble noise for all snippet
executions. Revert to inserting the original command directly; the underlying
CentOS 7 terminal issue reproduces when running the script manually too.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 20:25:16 +08:00
bincxz 4b874410f9 fix(opencode): harden SDK streaming, model list, and Windows shim path
Route Netcatty context through body.system, filter echoed user parts,
keep OpenCode env/shim alive for server lifetime, and cap model preset dropdown height.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 18:56:46 +08:00
陈大猫 c6bdcffb89 feat: dynamic terminal tab titles (fixes #1572) (#1576)
* feat: add dynamic terminal tab titles for shell-reported session names

Follow OSC 0/2 window titles on session tabs so tools like Claude Code can show live session state, with a per-host opt-out to keep connection names instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address dynamic tab title review findings

Restore the missing cn import, clear dynamic titles when the shell sends an empty OSC title, and drop stored dynamic titles when a host disables the feature.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: preserve renamed session labels and align focus sidebar titles

Prefer user-assigned customName over shell titles, skip no-op dynamic title updates, and use resolveSessionTabTitle in the workspace focus sidebar.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: show coding agent icons on session tabs

Detect Claude Code, Codex, OpenCode, Droid, and other CLIs from OSC titles and launch commands, reusing shared agent icon assets inspired by freshell pane icons.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: harden coding CLI detection across SSH and startup paths

Wire output scanning through attachSessionToTerminal, seed providers from
auto startup commands, prevent title signals from overriding command
detection, clear sticky state on shell titles, and release output scanners
on session close.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: reset CLI output scanner when provider state clears

Prevent exited-agent banners from immediately re-sticking the tab icon
after the shell title returns to a normal shell name.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: update sticky CLI provider when title signals a different agent

Allow agent switches in the same terminal while still resetting the output
scanner whenever the provider identity changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: avoid clearing CLI provider on blank titles and cap output scan

Ignore transient empty OSC titles and stop banner scanning after 16KB of
unmatched output so regular SSH sessions do not pay perpetual scan cost.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: prefer runtime CLI provider over host startup command on tabs

Runtime detection from commands, output, and titles should win over static
host startup commands when resolving the session tab icon.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: satisfy eslint for terminal escape stripping regexes

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 14:20:05 +08:00
bincxz affe197f26 Improve Netcatty responsiveness 2026-06-23 01:26:12 +08:00
bincxz a238a5ac2f Restore terminal mode after auto-run snippets 2026-06-22 14:10:50 +08:00
Zheng Liu 9674cd8ae6 fix: protect peer restore windows 2026-06-18 11:39:18 +08:00
Zheng Liu ba9d9c1e58 fix: isolate session restore side effects 2026-06-18 11:04:41 +08:00
Zheng Liu 73e615cb64 fix: isolate restored peer session windows 2026-06-18 10:52:15 +08:00
Zheng Liu 93079bb576 fix: prevent session restore snapshot loss 2026-06-17 23:41:29 +08:00
Zheng Liu 04906a80ff fix: tighten session restore edge cases 2026-06-17 22:39:10 +08:00
Zheng Liu 193b2b73c9 fix: harden session restore reconnect and sanitization 2026-06-17 22:07:32 +08:00
Zheng Liu 5a27dbe955 feat: polish restored terminal sessions 2026-06-17 21:37:50 +08:00
Zheng Liu 644d55a0c0 feat: restore terminal tabs and workspace layout 2026-06-17 18:43:42 +08:00
陈大猫 f5c3302329 feat: terminal rename, closeSession shortcut, and pane zoom (#1459)
* feat: auto-poll Docker capabilities while Docker tab is active

When the Docker tab is visible and hasDocker is not yet true,
poll refreshCapabilities() at the process refresh interval.
Stop polling once hasDocker becomes true, or when switching
to a different tab.

* fix: use resolvedTab instead of activeTab for Docker auto-poll condition

The auto-poll useEffect condition used activeTab, which stays stale
when Docker becomes unavailable. Changed to resolvedTab which reflects
the actual displayed tab. Also updated the dep array.

* fix: replace setInterval with setTimeout recursion in Docker tab probe

Replace setInterval-based polling with setTimeout recursion in the Docker
tab capability probe effect. This ensures the next probe only starts after
the previous one finishes, avoiding overlapping probes when SSH timeout
exceeds the polling interval.

- Add dockerPollTimerRef to track the timeout handle
- Use async pollOnce() that awaits refreshCapabilities() before scheduling next
- Use cancelled flag in cleanup to prevent scheduling after unmount
- Keep same dependency array for correctness

* fix: stabilize docker poll timer by using useRef for refreshCapabilities

refreshCapabilities() can return a new reference on every render, causing
the useEffect to re-run on every render — cleanup cancels the polling timer,
then the effect immediately calls pollOnce(), effectively bypassing the
configured timeout interval.

Fix: store refreshCapabilities in a useRef (refreshRef), use
refreshRef.current() inside pollOnce(), and replace refreshCapabilities
with refreshRef in the useEffect dependency array.

Closes #PR1456 Codex P2 review item.

* fix: delay auto-poll first probe by one interval to avoid overlap with tab-switch probe

When switching to the Docker tab, two mechanisms were triggering probes:
1. tab-switch effect (line 67-76): immediate probe via refreshCapabilities()
2. auto-poll effect: pollOnce() executing immediately on mount

This caused duplicate probes that waste SSH channel resources.

Fix: pollOnce() no longer fires on mount. Instead, the effect schedules the
first probe with setTimeout(pollOnce, capabilitiesTtlMs), so the first probe
happens after one full interval. Subsequent probes continue at interval pace
via the setTimeout recursion in pollOnce itself.

The tab-switch effect still fires the immediate probe (the correct one),
so responsiveness on tab switch is preserved.

* fix: reset cancelledRef in effect body to prevent permanent stalling of Docker polling

The cancelledRef was set to true in the cleanup function when dependencies
changed, but never reset when the effect re-ran. This caused pollOnce to
always early-return on subsequent timer ticks, permanently halting
Docker capability probing after the first dependency change.

* fix(system-manager): replace cancelledRef with closure variables for per-effect cancellation

Each effect generation now has its own  and  closure
variables instead of shared  / . This
prevents stale probes from surviving cleanup when the panel hides and
re-shows (Codex P2 review).

* fix: wrap refreshCapabilities in try/catch to keep polling on exception

If refreshCapabilities throws (instead of returning {success: false}),
the await would exit pollOnce without scheduling the next setTimeout,
silently killing Docker auto-detection polling.

* fix: add in-flight probe guard to prevent tab-switch and auto-poll concurrent probes

Add probingRef to track whether a capabilities probe is already in-flight.
- Tab-switch effect for Docker branch checks probingRef before starting a new probe
- Auto-poll pollOnce checks probingRef at entry and sets/clears it around the actual probe
- Tmux branch left unchanged as it has no auto-poll overlap risk

* fix: re-schedule next poll timer when probe is in-flight

When probingRef.current is true (tab-switch probe still running),
pollOnce was returning early without scheduling the next timer,
causing auto-poll to stop permanently afterward.

Now it schedules the next poll within the interval and returns,
so the polling loop keeps running until a slot where no probe is
active.

* fix: convert comments to ASCII-only English

- Line 105: translate Chinese comment to 'probe is in-flight, reschedule for next cycle'
- Line 113: replace em dash (U+2014) with ASCII dash

* feat: session inline rename, closeSession shortcut, pane zoom

* fix: sidebar inline rename with local state

* fix: add sessionDisplayName to terminalPropsAreEqual comparator

The Terminal component is wrapped with React.memo(…, terminalPropsAreEqual),
but the comparator was missing a check for sessionDisplayName. After renaming
a session, the pane title bar would show the old name until some other prop
changed and triggered a re-render.

Add prev.sessionDisplayName === next.sessionDisplayName to the comparator
so that display name changes cause the Terminal to re-render immediately.

* fix: add onStartSessionRename to TerminalLayerWorkspaceSection ctx destructuring and TerminalPanesHost props

* fix: add toggleWorkspaceViewMode to executeHotkeyActionImpl destructuring

The togglePaneZoom handler calls toggleWorkspaceViewMode() but it
wasn't destructured from getCtx(), causing a ReferenceError at runtime.

* fix: restore truncated ctx object in TerminalView render call

The TerminalView ctx object literal on line 1265 was truncated to
'showSele...' due to an editing tool truncation bug, causing
Parsing error: ',' expected on npm run lint / tsc --noEmit.

Restored the missing fields from the base commit:
showSelectionAIAction, snippets, status, statusDotTone, sudoHintRef,
sudoHintText: t("terminal.sudoHint.pressEnter"), t, termRef,
terminalBackend, terminalContextActions, terminalCwdTracker,
terminalPreviewVars, terminalSettings, timeLeft, toast, zmodem

Kept the PR's new additions (isVisible, onRename, sessionDisplayName)
intact.

* fix: add toggleWorkspaceViewMode to executeHotkeyAction context and add terminal.menu.rename translations

- Add toggleWorkspaceViewMode to the context getter in executeHotkeyAction (App.tsx)
- Add terminal.menu.rename translation for en (Rename), zh-CN (重命名), ru (Переименовать)

* fix: validate focusedSessionId before closing in closeSession hotkey

When closeSession hotkey fires, workspace.focusedSessionId may reference
a session that was already closed by another trigger (e.g., mouse click
on tab close button). Collect alive session IDs from the workspace root
and fall back to the first living pane if the stored focusedSessionId
is stale.

* fix(auto-poll): check useSessionCapabilities probing state in pollOnce

When auto-poll timer fires before the initial probe (from
useSessionCapabilities) completes, probingRef.current is still false
because the initial probe doesn't set it — causing a second overlapping
probe.

Add  check so that any in-flight probe from any path
(initial/auto-poll/tab-switch) prevents auto-poll overlap.

PR #1459

* fix: address remaining Codex review issues

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: add detach session from workspace with toolbar button and context menu

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: use customName in pane header display name for renamed sessions

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: refine workspace terminal detach interactions

* fix: preserve workspace detach tab ordering

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 01:30:44 +08:00
陈大猫 8be5865b76 fix(terminal): isolate workspace pane font zoom from global settings (#1379)
Store per-session font size in workspace splits so Ctrl+wheel zoom no longer
changes sibling panes or reverts on blur when terminalSettings re-sync runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 19:38:39 +08:00
bincxz 037b85bd66 fix(ui): smooth work-tab chrome transitions and split-pane autocomplete
Replace immersive instant-switch with animated active chrome theme sync so
top tabs match terminal sessions immediately on tab click, and clamp
autocomplete popups to the active pane so they stay anchored to the cursor
in split workspaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 01:10:33 +08:00
陈大猫 29a6172120 Add duplicate tab to new window
Adds a tab context menu action to duplicate a terminal into an independent peer window, with per-window active-tab titles and multi-window lifecycle safeguards.
2026-06-06 22:13:47 +08:00
陈大猫 9e2b8093fb fix terminal split connection reuse (#1249) 2026-06-04 23:44:27 +08:00
bincxz 9d0f6a9cea Merge origin/main into feat/et 2026-06-04 18:12:54 +08:00
bincxz 85f486e6cd feat(ssh): reuse authenticated connection for Copy Tab to skip re-MFA
Duplicating an SSH tab ("Copy Tab") used to open a brand-new SSH
connection, forcing a second MFA prompt on hosts with multi-factor auth.
Like Tabby's session multiplexing, open a new shell *channel* on the
source tab's already-authenticated connection instead, so the duplicate
reuses the existing transport and skips key exchange + authentication.

- copySession records the source session id (reuseConnectionFromSessionId)
  for connected, non-mosh SSH sessions; it is threaded down to the SSH
  bridge as options.sourceSessionId.
- startSession.cjs gains a reuse path that opens conn.shell() on the
  source connection. The shell wiring is extracted into a shared
  setupShellSession helper used by both the fresh and reuse paths.
- Connection lifecycle is reference-counted via a new sshConnectionPool
  module (mirrors Tabby ref/unref/destroy): the shared transport + jump
  host chain are torn down only when the last channel closes, so closing
  a copy — or the original while a copy is open — never kills siblings.
  terminalBridge.closeSession routes SSH teardown through the same release.
- Reuse falls back to a fresh connection when the source is gone, and is
  skipped for X11 hosts (X11 is negotiated per channel).

Tests: sshConnectionPool refcount unit tests, bridge reuse integration
tests (reuse vs fresh, sibling survival, X11 skip, fallback), and
terminalBridge close lifecycle tests.

Refs #1204

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 10:43:49 +08:00
lateautumn233 816e274dfc Route ET protocol through application session state and backend
Resolve 'et' protocol in tray panel and host connection handlers with
priority over mosh. Propagate etEnabled through session factories and
useSessionState. Expose etAvailable guard and startEtSession wrapper
in useTerminalBackend.
2026-06-03 01:45:28 +08:00
pplulee 03cd9bc968 feat(snippets): implement snippet variable handling and UI prompts (#1159) 2026-05-31 18:01:43 +08:00
陈大猫 1fec5925eb Refactor large modules and fix runtime errors (#1136) 2026-05-28 15:12:19 +08:00
Bet4 ac819fd4fd feat(workspace): add focus sidebar drag reorder (#992) 2026-05-18 01:26:14 +08:00
陈大猫 7c55381f39 Add terminals to workspace + New Workspace from QuickSwitcher (#790)
* Add terminals to workspace + New Workspace from QuickSwitcher

Two entry points share a single multi-select picker that lets the
user add Local Terminal + any combination of hosts into a workspace:

1. Focus-mode sidebar "+" button appends the selected targets to the
   active workspace as new panes.
2. QuickSwitcher "New Workspace" button (small inline action next to
   the Jump To hint) spins up a brand-new workspace tab populated
   with the selected targets.

## Changes

### domain/workspace.ts
- pruneWorkspaceNode now rebalances surviving siblings to EQUAL
  sizes after removal, instead of re-normalising the prior skew.
  Matches the "auto-redistribute on close" expectation.
- New appendPaneToWorkspaceRoot(root, sessionId, direction='vertical'):
  if root already splits in the requested direction, pushes the new
  pane onto its children and resets sizes to equal; otherwise wraps
  root + new pane in a new 0.5/0.5 split. Flattens long chains of
  appends instead of producing degenerate nested trees.

### application/state/useSessionState.ts
- appendHostToWorkspace(workspaceId, host, direction?) — atomic
  "build a session for this host and append it to the root", keeps
  activeTab on the workspace and focuses the new pane.
- appendLocalTerminalToWorkspace(workspaceId, options?, direction?)
  — mirror of the above for local shells.
- createWorkspaceFromTargets(targets, name?) — accepts a mixed list
  of {kind:'local',...} / {kind:'host',host} and creates a new
  workspace with one pane per target. Defaults viewMode to 'focus'
  so the QuickSwitcher flow lands in the sidebar layout.
- All three exported from the hook.

### components/workspace/AddToWorkspaceDialog.tsx (new)
QuickSwitcher-styled multi-select picker:
- Fixed top-center overlay, same chrome as QuickSwitcher (border,
  shadow, rounded-xl, borderless search input, bg-primary/15 cursor).
- Two sections: Local Shells (currently just Local Terminal) and
  Hosts. Hover follows keyboard cursor.
- Toggle rows with click or Space / Enter; ⌘/Ctrl+Enter submits;
  Esc closes. Right-side Check marks visible items.
- Thin footer bar with Cancel + "Add N" button.

### App.tsx
- Root-mounted single instance of AddToWorkspaceDialog with a
  discriminated-union state:
  { mode: 'append'; workspaceId } | { mode: 'create' } | null.
- onAdd dispatches based on mode — append loops through the picker
  targets calling the two append helpers; create calls
  createWorkspaceFromTargets once.
- TerminalLayer's focus "+" now sends an onRequestAddToWorkspace
  (workspaceId) up to App instead of owning its own dialog.
- QuickSwitcher's onCreateWorkspace callback repurposed to open the
  dialog in create mode (replaces the older CreateWorkspaceDialog
  route for this specific flow).

### components/TerminalLayer.tsx
- Dropped the inline AddToWorkspaceDialog + addHostPanelOpen state;
  replaced the two append callbacks with a single
  onRequestAddToWorkspace prop wired to the "+" button.
- Focus-sidebar header: replaced the "Terminals · N" counter with an
  immersive borderless search input (bg-transparent, shadow-none,
  termFg color) for filtering the terminal list; "+" and Columns2
  buttons moved to the right.
- Session list filtered client-side by the search term across
  hostLabel / hostname / username.

### components/QuickSwitcher.tsx
- Re-introduced onCreateWorkspace prop (was removed as unused).
- "New Workspace" inline button (Plus icon + label) sits on the
  right of the Jump To hint row: border, rounded, hover bg. Click
  fires onCreateWorkspace then closes QS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add configurable New Workspace shortcut

Mirrors QuickSwitcher's "+ New Workspace" button via a keyboard
binding so the dialog can open in one keystroke without passing
through QS.

- domain/models.ts: new DEFAULT_KEY_BINDINGS entry id=new-workspace,
  action=newWorkspace, default ⌘+Shift+J (Mac) / Ctrl+Shift+J (PC).
  Audited the defaults — only quick-switch uses J (⌘+J), so the
  shifted combo is free. The binding sits in the 'app' category so
  it shows up in Settings → Shortcuts and can be rebound by the user.
- application/state/useGlobalHotkeys.ts: wire newWorkspace into the
  HotkeyActions interface, getAppLevelActions() allowlist, and the
  global keydown switch so the scheme-driven handler dispatches it.
- App.tsx: handle case 'newWorkspace' inside executeHotkeyAction by
  calling setAddToWorkspaceDialog({ mode: 'create' }) — same entry
  as QuickSwitcher's button, just without having to open QS first.
- application/i18n/locales/zh-CN.ts: add '新建工作区' translation for
  settings.shortcuts.binding.new-workspace. English falls back to
  the KeyBinding.label field ("New Workspace"), so no en.ts change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address codex P1: don't check setState flag after the updater returns

Codex flagged that appendHostToWorkspace / appendLocalTerminalToWorkspace
were racy: both flipped an `inserted` flag inside setWorkspaces'
updater and then read it synchronously to decide whether to commit
the matching session via setSessions. React does NOT guarantee
updaters run synchronously (concurrent rendering, StrictMode
double-invoke, etc.), so the flag could still be false at the read
site even though the workspace exists. In that case setSessions was
skipped while the queued workspace update could still insert a new
pane referencing newSessionId — leaving a pane with no backing
session in state.

Fix: add a workspacesRef kept in sync with the workspaces state on
every render, and perform the existence check synchronously *before*
queuing any setState. Once we've confirmed the workspace exists on
the latest committed state, both setWorkspaces and setSessions are
called unconditionally, so they can never diverge.

The ref approach also correctly handles the multi-target append
loop path — React batches the updaters and applies them in sequence,
so sibling pane/session writes land in matching order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address codex P1+P2: narrow prune rebalance; append in root direction

### P1 — pruneWorkspaceNode over-rebalanced ancestor splits

The equal-sizes rebalance was unconditional during the recursive
walk, so closing a pane deep in one branch also rewrote unrelated
ancestor ratios (e.g., a root 0.8/0.2 vertical split got normalised
to 0.5/0.5 when a grand-child horizontal pane closed).

Now each split level tracks whether it actually lost a DIRECT
child. Only splits where a direct child disappeared get their
siblings reset to equal sizes. Ancestors whose direct children all
survived keep their original ratios (defensively re-normalised in
case a descendant subtree collapsed shape).

### P2 — Append path ignored the root's current direction

onAdd in App.tsx called the two append helpers without a direction,
so both defaulted to 'vertical'. appendPaneToWorkspaceRoot only
flattens into the root split when the directions match; if the
workspace root was horizontal (e.g., user split top/bottom earlier),
each append wrapped the entire existing tree into one side of a new
vertical split — existing panes crammed into one branch, new pane
hoarding half the space.

Read the current root direction out of the target workspace and
pass it down so new panes become peers of the existing root
siblings regardless of horizontal vs vertical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address codex P2: allow serial hosts in create-workspace picker

The picker used to filter out every host with protocol='serial'
regardless of mode. That was correct for append mode (the
appendHostToWorkspace helper has no serial path and early-returns)
but a regression for create mode — the old createWorkspaceWithHosts
flow passed serial hosts through and createWorkspaceFromTargets
still builds a SerialConfig-backed session for them, so there was
no reason to block them in the "+ New Workspace" entry.

Move the filter from the dialog up to App.tsx:
- AddToWorkspaceDialog drops the serial filter; selectableHosts is
  simply the hosts prop.
- App.tsx passes `hosts.filter(h => h.protocol !== 'serial')` when
  mode is 'append', and the full list when mode is 'create'.
Result: users can once again build a workspace from serial hosts
via QuickSwitcher's "+ New Workspace" button or the ⌘/Ctrl+Shift+J
hotkey, while append-to-existing keeps its earlier safe behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address codex P2: don't commit session when append target disappears

Follow-up to the earlier ref-based guard. The ref check eliminates
the common "workspace already gone" case but still leaves a small
race: if closeWorkspace runs between the ref read and setWorkspaces'
updater firing, prev.map returns the unchanged workspaces but
setSessions / setActiveTabId still execute — leaving an orphan
session whose workspaceId points at a deleted workspace and jumping
activeTabId to a closed tab.

Nest setSessions + setActiveTabId inside the setWorkspaces updater
so the writes are gated on the same authoritative match used for
the tree update. The setSessions updater also de-dupes by newSessionId
so React 18 StrictMode's dev-time double-invoke of the outer updater
doesn't append the same row twice. Same pattern applied to
appendLocalTerminalToWorkspace.

The existing closeSession already uses the nested-setState shape, so
this matches the codebase convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 01:19:33 +08:00
陈大猫 f6f3147afb Tab bar: duplicate-adjacent insertion + wheel-to-horizontal scroll (#743)
* Improve tab UX: insert duplicated tabs adjacent to source, enable wheel scroll on tab bar

Addresses #737.

- Duplicating a tab now inserts the new tab immediately after the source
  in the tab order, instead of appending it to the far right where it
  was hard to find with many tabs open.
- The top tab strip now translates vertical mouse-wheel deltas into
  horizontal scrolling, so users with many tabs can reach the ends of
  the strip without dragging. Trackpad gestures that already carry
  horizontal delta are left alone to preserve native two-finger swiping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address Codex review: read source session inside functional updater

Codex flagged that reading `session` from the closure broke the atomicity
guarantee of the previous implementation — rapid repeated duplicates could
miss freshly queued state.

- Pre-allocate the new session id outside both setters so it stays stable
  across StrictMode double-invocations.
- Move the source lookup back into `setSessions`' functional updater so it
  always reads the freshest committed/queued state.
- Drop `sessions` from the useCallback dependency list now that we no
  longer read it.
- Fast-path tabOrder insertion when the source is already in tabOrder to
  avoid re-deriving the full effective order in the common case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address Codex review: gate active-tab and tab-order updates on successful create

Codex flagged that `setActiveTabId(newSessionId)` and `setTabOrder(...)` ran
unconditionally even when `setSessions` bailed out (source tab was closed
before the duplicate handler ran). That left activeTabId pointing at an id
that was never appended to sessions, putting the terminal layer into an
invalid "no matching tab" state.

Move both nested setState calls inside the `setSessions` functional updater
so they only fire when the source is actually present. Mirrors the original
pre-PR pattern; nested updates are idempotent so StrictMode's
double-invocation is harmless.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 00:41:31 +08:00
陈大猫 8ef91e1266 Ctrl+W close priority + local shell busy confirmation (#739)
* feat(ctrl-w): add ps-node + windows-process-tree + tsx deps for close-priority feature

* fix(ctrl-w): drop ps-node dep and add windows-process-tree to asarUnpack

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): add ptyProcessTree bridge with per-platform child-process enumeration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ctrl-w): ptyProcessTree uses args= for full command + warns on pid overwrite

- Replace `comm=` with `args=` in defaultListPosix so the full command
  line is captured on both macOS (BSD ps) and Linux (GNU ps), avoiding
  the 15-char TASK_COMM_LEN truncation.
- Add console.warn in registerPid when the same sessionId is overwritten
  with a different pid, making the race condition visible in logs.
- Add test: registerPid warns exactly once on a pid change, not on a
  same-pid re-registration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): register local PTY pid with ptyProcessTree on spawn/exit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ctrl-w): unregister pids in cleanupAllSessions to match per-delete invariant

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): add IPC handlers for pty child processes and confirm-close dialog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ctrl-w): guard BrowserWindow.fromWebContents null and document dialog dismiss contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): expose ptyGetChildProcesses and confirmCloseBusy on window.netcatty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): add i18n strings for close-busy-terminal dialog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): add resolveCloseIntent pure function with 8 unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): expose handleCloseSidePanel via ref to App.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): wire resolveCloseIntent + local-shell busy confirmation into closeTab hotkey

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ctrl-w): add re-entrancy guard, aggregate busy count, sync sidebar ref, dedupe intent branches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ctrl-w): auto-close workspace when its last session is closed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ctrl-w): sidebar close wins over focused terminal in priority chain

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ctrl-w): sidebar priority applies to single-session tabs too, not just workspaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ctrl-w): compute empty-workspace auto-close outside setSessions updater

Addresses Codex P2 on #739: React 18+ does not guarantee updater
execution timing under concurrent scheduling. Moving the decision
outside the updater makes the microtask queue deterministic.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:30:11 +08:00
陈大猫 6ac36be04b feat: local shell selection with auto-discovery (#605) 2026-04-02 08:59:49 +08:00
陈大猫 c3224d30c6 feat: network device mode for SSH + serial charset encoding support (#540)
* feat: add deviceType field to Host model for network device support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: pass deviceType through session metadata pipeline

Thread deviceType from Host model through AITerminalSessionInfo, IPC
types, and mcpServerBridge so AI agents can inspect device type per session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: route network device SSH sessions to raw PTY execution

When deviceType === 'network', handleExec now uses execViaRawPty
instead of execViaPty so vendor CLIs (Huawei VRP, Cisco IOS, etc.)
receive commands as-is without POSIX shell wrapping or markers.
The command blocklist is also skipped for network devices, consistent
with the existing serial session bypass. AI context description updated
to document the raw-execution behaviour for network device sessions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add network device mode toggle to host settings UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add network device awareness to Catty Agent system prompt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: extend network device mode to Catty Agent exec path and host context

- Add network device detection and raw execution routing to aiBridge.cjs
  (the primary Catty Agent command path), not just the MCP bridge
- Export getSessionMeta from mcpServerBridge for reuse in aiBridge
- Surface deviceType in Catty Agent system prompt host list so the AI
  can identify which sessions are network devices
- Pass deviceType through buildSystemPrompt context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: exempt network device sessions from client-side blocklist and update ACP context

- Add deviceType to ExecutorContext sessions type
- Skip renderer-side command blocklist for deviceType=network sessions
  in shared toolExecutors.ts (not just main-process side)
- Update ACP agent context hint to mention network device sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: only show network device mode toggle for SSH hosts

Telnet and local hosts don't support the network device execution path,
so hiding the toggle prevents users from enabling a broken configuration.
Serial hosts already use raw mode by default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: exclude Mosh sessions from network device raw execution path

Mosh uses a shell-backed PTY and cannot connect to vendor CLIs, so
network device mode should only apply to SSH and serial sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prefer session.protocol over metadata for Mosh detection

Mosh tabs report protocol:"ssh" in renderer metadata but "mosh" in
the main-process session object. Prioritize session.protocol (runtime
truth) to correctly exclude Mosh from network device raw execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: suppress deviceType metadata for Mosh sessions

Mosh requires a shell-backed PTY and cannot connect to vendor CLIs,
so omit deviceType from AI-facing metadata when session is Mosh-backed.
This prevents the AI from being told to use vendor CLI syntax when the
actual execution path uses normal shell wrapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use exit code 0 for network device sessions and hide toggle for Mosh

- Network device / serial sessions return exitCode: null from vendor
  CLIs. Default to 0 instead of -1 so the AI doesn't misinterpret
  successful commands as failures.
- Hide the network device mode toggle when Mosh is enabled, since
  the setting is suppressed at runtime for Mosh sessions anyway.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: preserve null exit codes and restrict raw mode to SSH/serial

- Preserve exitCode: null for network device sessions instead of
  coercing to 0, so the AI knows exit status is unavailable rather
  than seeing a misleading success code.
- Explicitly whitelist SSH/serial protocols for network device mode
  instead of just excluding mosh, preventing local/telnet sessions
  from accidentally entering raw execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use UTF-8 encoding for SSH network device raw execution

execViaRawPty hardcodes latin1 for serial port data decoding. Add an
encoding option (default: latin1) and pass utf8 from SSH network
device call sites so multi-byte characters aren't corrupted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use host charset for serial port decoding instead of hardcoded latin1

- Extract charsetToNodeEncoding() to module scope in terminalBridge
- Serial sessions now read options.charset (from Host.charset) for
  both terminal display decoding and AI command output
- Store serialEncoding on session object so exec paths can use it
- Pass encoding through all execViaRawPty call sites
- Default encoding changed from latin1 to utf8 (matches most modern
  network equipment and is the safer default for CJK environments)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move serialEncoding declaration before session object creation

serialEncoding was referenced in the session object literal before its
const declaration, causing a TDZ ReferenceError that would crash every
serial connection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: tighten isNetworkDevice logic and clean up edge cases

- Align toolExecutors isNetworkDevice check with bridge logic: require
  explicit SSH/serial protocol match instead of trusting deviceType alone
- Remove empty-string protocol match from isSshOrSerial in both bridges
  to prevent local/unknown sessions from being treated as network devices
- Widen exitCode return type to `number | null` to match actual behavior
- Clear deviceType when enabling Mosh (incompatible combination)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update MCP server tool descriptions for network device sessions

The get_environment and terminal_execute tool descriptions only
mentioned serial/raw sessions for network devices. Updated to also
reference deviceType: network SSH sessions so external AI agents
(Claude, Codex) know about the new execution mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: include deviceType in get_environment and guard execViaChannel fallback

- Add deviceType to executeWorkspaceGetInfo session mapping and return
  type so Catty Agent's get_environment tool matches MCP bridge output
- Guard both aiBridge and mcpServerBridge against falling through to
  execViaChannel for network device sessions — network devices require
  an interactive PTY and exec channels would produce broken behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add charset setting to serial host configuration UI

Serial hosts now have a charset input in the Advanced section,
defaulting to UTF-8. The value is saved to Host.charset and used
by the serial decoder in terminalBridge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add charset to serial quick-connect modal with full pipeline

- Add charset input to SerialConnectModal (Advanced section)
- Thread charset through onConnect callback → handleConnectSerial →
  createSerialSession → TerminalSession.charset
- Add charset field to TerminalSession interface
- Include charset in fallback host builder for quick-connect sessions
  so createTerminalSessionStarters can pass it to startSerialSession
- Saved hosts also store charset via onSaveHost

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: constrain serial connect modal height with scrollable content

Modal content could overflow the viewport when Advanced section was
expanded. Add max-h-[85vh] to DialogContent with flex layout so the
content area scrolls while header and footer buttons stay visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: propagate charset through all serial session creation paths

- Add charset to startSerialSession type in global.d.ts
- Copy host.charset to TerminalSession in connectToHost serial path
- Copy host.charset in createWorkspaceWithHosts serial path
- Propagate session.charset in splitSession (both workspace and standalone)
- Propagate session.charset in copySession

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: propagate charset in remaining session creation paths

Add host.charset to connectToHost (non-serial), createWorkspaceWithHosts
(non-serial), and runSnippet session creation for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 18:33:16 +08:00
陈大猫 40d80fe535 perf: comprehensive UI and state management optimization (#539)
* perf: comprehensive performance optimization across UI and state management

- Replace Array.find/includes with Map/Set lookups for O(1) access in hot paths
- Add requestAnimationFrame throttling to all mousemove resize handlers
- Remove redundant forceUpdate + useSyncExternalStore double subscription
- Extract terminal search decoration config to module-level constant
- Pause server stats polling and resize handlers for hidden terminals
- Add timer cleanup for useEffect/useLayoutEffect with setTimeout
- Use useEffectEvent to stabilize effect callbacks and reduce effect re-runs
- Use useDeferredValue for QuickSwitcher search input
- Batch activeTabStore notifications with microtask coalescing
- Memoize sessionLogConfig and activityTrackedSessions to prevent child re-renders
- Use ref pattern for stable onTerminalDataCapture callback
- Skip TerminalLayer pre-warming when no sessions or workspaces exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: flush final resize value before canceling RAF

Apply the last computed size synchronously on mouseup/cleanup before
canceling the pending requestAnimationFrame. This prevents the final
drag delta from being dropped when mouseup fires before the queued
frame executes.

Addresses review feedback from codex on all 3 RAF-throttled resize
handlers: split resize, side panel resize, and SFTP column resize.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: initialize lastClientXRef on resize start to prevent click-collapse

Without initialization, a click on the resize handle without dragging
would use lastClientXRef=0, computing a large negative diff and
collapsing the column to minimum width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert useDeferredValue for QuickSwitcher search

useDeferredValue can lag behind the actual input, causing quickResults
to reflect a stale query when the user types fast and presses Enter.
This is a correctness regression - the selected item may not match the
user's intent. The host list is typically small (<200), so synchronous
filtering is fast enough without deferral.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore runtime activity guard to prevent stale badge on tab switch

The pre-filtered activityTrackedSessions reduces subscriptions for
disconnected sessions, but removing the runtime shouldMarkSessionActivity
check introduced a race: between tab switch and effect re-subscription,
old listeners could mark the newly-focused session as unread. Restore
the activeTabIdRef.current guard inside the callback as a safety net.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: defer initialConnectDoneRef flag until auto-connect executes

Moving the flag inside the setTimeout callback prevents it from being
set when the timer is canceled by cleanup. Previously, if the effect
re-ran before the setTimeout(0) fired, the timer was cleared but the
ref was already true, permanently skipping the initial local connect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: capture resizingRef fields before setState updater

Destructure field/startX/startWidth from the ref upfront so the
functional updater closure never reads resizingRef.current after
it may have been cleared by handleResizeEnd.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove activeTabId from activityTrackedSessions to stabilize subscriptions

Depending on activeTabId caused subscriptions to tear down and recreate
on every tab switch, resetting the ChunkedEscapeFilter mid-sequence and
producing false unread badges. The runtime guard via activeTabIdRef
already handles the active-tab check, so pre-filtering only needs to
exclude disconnected sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fetch server stats immediately when tab becomes visible again

Use hasFetchedRef to distinguish first connect (2s delay for connection
stabilization) from tab resume (immediate fetch). Prevents showing
stale CPU/memory data for 2 seconds after switching back to a terminal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore cold-start prewarm and reset network stats on tab resume

1. Revert shouldPrewarm guard - TerminalLayer should always prewarm
   after 1.2s regardless of session/workspace count, as the purpose is
   to hide lazy-load latency before the user opens their first terminal.

2. Reset netRxSpeed/netTxSpeed to 0 when resuming a hidden terminal
   tab. The backend computes network throughput as a delta from the
   previous sample, so the first fetch after a long hidden interval
   would show artificially low throughput averaged over the gap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reset hasFetchedRef on disconnect and preserve built-in theme precedence

1. Clear hasFetchedRef when connection drops so reconnects get the 2s
   stabilization delay before first stats fetch.

2. Reverse theme merge order in themeById Map so built-in themes are
   written last and take precedence over custom themes with duplicate
   IDs, matching the original find() semantics and other resolution
   sites (customThemeStore.getThemeById, Terminal.tsx).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: also clear per-interface network speeds on tab resume

Reset rxSpeed/txSpeed on each netInterfaces entry in addition to the
aggregate values, so the network hovercard doesn't show stale
throughput while waiting for the first fresh poll after resume.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reset capture ref on retry and skip warmup for established connections

1. Reset terminalDataCapturedRef in handleRetry() so log capture works
   for retried sessions (retry doesn't change sessionId, so the effect
   that resets the ref never re-runs).

2. Track connection start time to skip the 2s warmup delay when a tab
   becomes visible for a connection that was already established while
   hidden. Only apply the warmup for truly fresh connections (<2s old).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent overlapping stats requests and track connection time while hidden

1. Add fetchInFlightRef guard to prevent concurrent getServerStats
   requests that could race and corrupt baseline CPU/network data.

2. Move connectedAtRef initialization before the isVisible check so
   connections that complete while the tab is hidden record their
   start time. This ensures the warmup delay is correctly skipped
   when the tab becomes visible for an already-stable connection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reset fetchInFlightRef on disconnect to unblock reconnect stats

A pending getServerStats request from a previous connection could keep
fetchInFlightRef set, causing the reconnected session's initial fetch
to be skipped until the old request timed out.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: clear fetchInFlightRef when tab becomes hidden

Ensures the resume fetch isn't blocked by an in-flight request from
the previous visible cycle. Any stale response from the old request
will be quickly overwritten by the fresh immediate fetch on resume.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use generation counter to invalidate stale stats responses

Replace fetchInFlightRef with a generation counter that increments on
each fetch. Stale responses from before a hide/show cycle are discarded
by comparing the captured generation against the current value, fully
preventing pre-hide requests from overwriting zeroed network stats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: increment fetch generation on effect setup to invalidate in-flight requests

The generation was only incremented inside fetchStats, but the resume
setTimeout hadn't fired yet when old responses arrived. Incrementing
at effect setup time ensures any pre-hide in-flight request is
immediately stale, preventing it from overwriting zeroed network stats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:45:47 +08:00
陈大猫 17e98090ad Add AI support for local terminal sessions (#419)
* Add AI support for local terminal sessions

* Fix local AI session metadata and shell safety

* Fix local session cloning and multi-exec errors

* Refactor local shell detection helpers

* Fix local shell helper import path

* Fix CJS imports in renderer

* Use ESM local shell helpers in renderer

* Normalize local shell paths and platform metadata
2026-03-20 17:32:29 +08:00
陈大猫 338ba94d42 feat: add paste-only option for snippets (no auto-execute) (#375)
* feat: add "paste only" option for snippets (no auto-execute)

Add a noAutoRun flag to snippets that pastes the command into the
terminal without appending a carriage return, so users can review
and edit before manually pressing Enter.

Applies to all snippet execution paths: snippet runner (new session),
keyboard shortcut, and startup command.

Closes #371

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use clearer wording "仅粘贴" instead of "仅上屏"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: skip onCommandExecuted for paste-only shortcut snippets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: persist noAutoRun on save and apply to Scripts panel clicks

- Include noAutoRun in handleSubmit serialization (was being lost)
- Pass noAutoRun through ScriptsSidePanel click handler to TerminalLayer
  so paste-only snippets work from the Scripts panel too

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 18:09:17 +08:00
陈大猫 71ff9953bd Fix issue #278 identity refresh and session log autosave (#281)
* Fix issue #278 identity refresh and session log autosave

* Sync session log settings across windows
2026-03-06 15:12:38 +08:00
陈大猫 087ce0f3b1 feat: implement workspace creation from Quick Switcher (#162)
- Added `CreateWorkspaceDialog` component for creating named workspaces with multiple hosts.
- Implemented `createWorkspaceWithHosts` in `useSessionState`.
- Integrated dialog into `App.tsx` and triggered from Quick Switcher.
- Updated `QuickSwitcher` logic to improve visibility of recent connections.
- Added i18n keys for the dialog.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-02-01 14:31:12 +08:00
bincxz e8be0d72de feat: add Copy Tab option to SSH session context menu
Add the ability to duplicate an SSH session by right-clicking on a tab
and selecting "Copy Tab". This creates a new session with the same
connection parameters (host, port, protocol, etc.).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 13:41:11 +08:00
copilot-swe-agent[bot] 8cf6e9243d Support connecting saved serial hosts and add visual indicators
- Add serialConfig field to Host interface for storing full serial configuration
- Update connectToHost to handle serial hosts and create proper serial sessions
- Update handleConnectToHost to log serial connections properly
- Update DistroAvatar to show USB icon for serial hosts

Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
2026-01-13 06:09:31 +00:00
copilot-swe-agent[bot] ccf707df5a Add serial port connection support
Co-authored-by: binaricat <16399091+binaricat@users.noreply.github.com>
2026-01-05 12:13:54 +00:00
LAPTOP-O016UC3M\Qi Chen 3ececaeb76 Adds session rename feature with context menu option
Enables users to rename terminal sessions via a context menu in the tab bar, introducing a dialog for input and updating translations for both English and Chinese. Improves toolbar usability with clearer button labels and enhances session management UX.
2025-12-19 10:48:39 +08:00
bincxz 3b608834f6 Adds connection log capture and replay for terminals
Implements persistent logging of terminal sessions, capturing system info and terminal output for each connection, including local and remote. Introduces a logs section in the UI to view, save, delete, and replay connection logs in read-only tabs. Adds deduplication for known hosts and improves tab management to support log replays alongside terminal and workspace tabs.

Enables future auditing, troubleshooting, and session review by storing terminal data and session metadata.
2025-12-14 21:37:46 +08:00
bincxz a0f244b56b Adds workspace broadcast mode with hotkey toggle
Implements a broadcast mode that lets users send terminal input to all sessions within a workspace. Introduces a hotkey to toggle broadcast for the active workspace and a UI indicator/button in the terminal header. Removes the unused side panel hotkey action. Updates tab navigation logic to accommodate new sections.

Enables more efficient multi-session workflows by allowing synchronized input across terminals.
2025-12-14 15:19:40 +08:00
bincxz fb35f989b8 Refactors to enforce backend access via application hooks
Replaces all direct usage of browser globals and infrastructure service imports in UI components with dedicated application/state backend hooks. Introduces lint rules to prevent direct access to backend bridges and localStorage from components, promoting a cleaner separation of concerns and improved maintainability.

Moves user preferences (e.g., port forwarding form mode) to persistent state hooks, updates port forwarding and SFTP logic to rely on backend hooks, and centralizes logging through a logger utility. Cleans up debug code and removes obsolete scripts from HTML.

Improves testability, prepares for alternative backend implementations, and enforces architectural boundaries.
2025-12-13 01:38:44 +08:00
LAPTOP-O016UC3M\Qi Chen a0c9d0d224 Adds pane focus navigation for split terminal workspaces
Enables keyboard navigation between split terminal panes using arrow key shortcuts, implementing directional focus movement with wraparound and modifier-aware handling. Improves workspace usability by visually marking and programmatically updating the focused pane, ensuring reliable focus control even with concurrent DOM updates. Also refines keybinding recording for special shortcuts and updates settings sync token usage for consistency.
2025-12-12 18:39:46 +08:00
bincxz 8a49eeeb4c Adds terminal split functionality with context menu
Introduces support for horizontal and vertical splitting of terminal sessions, enabling users to split standalone sessions and manage panes within workspaces. Implements a context menu in the terminal for split actions and standard clipboard operations, improving usability and workflow efficiency. Integrates split logic across hotkeys, UI callbacks, and session state management.
2025-12-12 03:49:13 +08:00
LAPTOP-O016UC3M\Qi Chen ba32d0d75b Migrates to Tailwind CSS v4 and improves hook deps clarity
Switches to Tailwind CSS v4 with theme configuration via CSS, removing inline CDN config for better maintainability and security.

Adds explicit eslint-disable comments in hooks to clarify dependency intent, aiding future development and preventing unnecessary warnings.

Renames props and variables reserved for future features to underscore-prefixed names, improving clarity and reducing confusion.

Removes unused catch parameters for cleaner error handling.

References to future UI and logic enhancements are annotated for maintainability and roadmap visibility.
2025-12-11 17:07:55 +08:00
LAPTOP-O016UC3M\Qi Chen b8c221d112 Organize and normalize imports across project
Reorder and normalize import statements, group icon imports, and add
missing utility imports (cn). Fix export/newline formatting and minor
type import tweaks. Add ESLint scripts to package.json and update the
lockfile.
2025-12-11 16:01:03 +08:00
bincxz aff8a603d3 Adds protocol selection and native Telnet support
Improves host connection flow by allowing users to choose SSH, Mosh, or Telnet protocols when multiple are available.

Implements native Telnet client, removing system dependency and enabling proper Telnet negotiation, window resizing, and terminal type handling.

Ensures session protocol overrides persist during connection, improving accuracy of terminal sessions.

Enhances UI logic for protocol-specific host details and quick connect wizard navigation.
2025-12-11 03:28:44 +08:00