* 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>
* 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>
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>
* 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>
* 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>
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>
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>
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.
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>
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.
* 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>
* 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>
* 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>
* 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>
* 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>
* 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
* 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>
- 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>
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>
- 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>
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.
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.
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.
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.
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.
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.
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.
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.
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.