c771979178192258edfe8fdffa3e27c882698ae4
194 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c771979178 |
Add Skills + CLI mode for external agents (#599)
* Add Skills + CLI external agent workflow * feat: add Skills + CLI transport for ACP agents * chore: remove branch-local compatibility shims |
||
|
|
58c651500e |
feat: add Gist revision history UI for vault restore (#685)
* feat: add Gist revision history UI for vault restore (#679) Adds a "History" button on the GitHub Gist provider card in Settings → Sync & Cloud. Clicking it opens a modal that lists all Gist revisions (newest first) and lets the user preview and restore any historical version with one click. ## How it works 1. The GitHub API already returns a `history` array when fetching a Gist (`GET /gists/{id}`). The existing `getGistHistory()` reads this. A new `downloadGistRevision(sha)` function fetches a specific revision via `GET /gists/{id}/{sha}`. 2. CloudSyncManager exposes `getGistRevisionHistory()` (metadata only, no decryption) and `downloadGistRevision(sha)` (decrypt + return payload and preview counts). 3. useCloudSync threads both methods through to the UI. 4. CloudSyncSettings renders a three-state modal: - **Loading**: spinner while fetching revision list - **Revision list**: clickable rows with SHA prefix + date, "Current" badge on the latest - **Preview**: after clicking a revision, shows entity counts (hosts, keys, snippets, identities) and a "Restore This Version" button 5. Decryption uses the current master password. If the revision was encrypted with a different password (user changed it since then), a clear error message is shown instead of a crash. ## Changes - `GitHubAdapter.ts`: add `downloadGistRevision()` standalone function + `getHistory()` / `downloadRevision()` class methods - `CloudSyncManager.ts`: add `getGistRevisionHistory()` and `downloadGistRevision(sha)` with decrypt + preview - `useCloudSync.ts`: expose both methods - `CloudSyncSettings.tsx`: add `extraActions` slot to ProviderCard, render "History" button on GitHub card, revision history modal with list → preview → restore flow - `en.ts` + `zh-CN.ts`: 18 new i18n keys for the modal Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use getConnectedAdapter and lazy gist discovery for history APIs P1: CloudSyncManager's history methods accessed this.adapters directly instead of getConnectedAdapter(), which lazily initializes adapters. After an app restart the adapter map is empty even though the provider is persisted as connected, making history fail until another sync path initializes it. P2: GitHubAdapter.getHistory() and downloadRevision() bailed early when gistId was missing, unlike download() which calls findSyncGist() to lazily discover it. Users whose gist was created after initial setup would see no revisions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address round-2 codex review on PR #685 P1: Renamed cloudSync.history.* keys to cloudSync.revisionHistory.* to avoid duplicate key collision with the existing "Sync History" section title. P2: Added getGistRevisionHistory and downloadGistRevision to the CloudSyncHook type interface so the hook contract matches reality. P2: Simplified decrypt error handling — any error from the decrypt path now shows the friendly "cannot decrypt" message rather than relying on fragile substring matching. P2: Clear historyRevisions on each handleOpenHistory call so stale data doesn't linger under error banners on retry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore correct i18n key for Sync History section title The sed rename pass accidentally changed the Sync History panel heading (line 1290) from cloudSync.history.title to cloudSync.revisionHistory.title. Restored the original key so the two sections have distinct titles. Also removed unused err parameter in the catch block. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
bcf653dd2e |
fix: prevent empty vault from overwriting cloud data on startup (#683)
* fix: prevent empty vault from overwriting cloud data on startup (#679) Fixes a data-loss scenario where an empty local vault (caused by an update, storage corruption, or import failure) silently overwrites a non-empty cloud vault on startup via auto-sync. The root cause is a startup timing race: the debounced auto-sync effect (3s after data change) can fire before checkRemoteVersion (1s delay + async download) completes its remote pull. When the local vault is empty, this pushes an empty payload to the Gist, permanently erasing the user's data. Four complementary fixes: A. Empty vault push guard (useAutoSync syncNow): Auto-sync refuses to push a payload where hosts, keys, snippets, and identities are ALL empty. Manual sync from Settings is still allowed for the rare case where the user intentionally emptied everything. Prevents the most dangerous path. B. Skip redundant post-merge push (useAutoSync checkRemoteVersion): After applying a three-way merge result from the remote, set skipNextSyncRef so the data-change effect does not immediately re-upload the same payload. Removes one unnecessary API call per startup sync. C. Gate auto-sync on remote check completion (useAutoSync effect): Added remoteCheckDoneRef — the debounced auto-sync effect will not fire until checkRemoteVersion has completed (success or failure). This closes the timing window entirely: an empty vault can no longer race ahead of the remote pull. D. Empty-vault-vs-cloud confirmation dialog (App.tsx + useAutoSync): When checkRemoteVersion detects local is empty but cloud has data, it pauses and shows a root-level dialog with two options: - "Restore from Cloud" (recommended) — applies the remote payload - "Keep Empty" — starts fresh with an empty vault The dialog blocks the sync flow via a Promise that resolves when the user picks an option. This gives users explicit control over a situation that previously happened silently behind their backs. Also adds en + zh-CN i18n strings for the new dialog and toast messages. Closes #679 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address codex review on PR #683 P1-1: Unified isPayloadEffectivelyEmpty helper covering all synced entity arrays (hosts, keys, snippets, identities, customGroups, snippetPackages, portForwardingRules, knownHosts, groupConfigs). Replaces the three inline checks in syncNow and checkRemoteVersion that only covered hosts/keys/snippets/identities. P1-2: Replaced hand-rolled overlay div with the project's existing Dialog/DialogContent/DialogHeader/DialogFooter components. This adds role="dialog", aria-modal, focus trap, and ESC-key dismiss for free. Used lucide-react AlertTriangle/Download/Trash2 icons instead of inline SVGs. P2-1: Guard against double-resolve in resolveEmptyVaultConflict by nulling the ref immediately on first call. P2-2: Replaced hardcoded "N hosts, N keys, N snippets" with an i18n key using interpolation (cloudSummary) so the count text is properly translated in zh-CN. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address round-2 codex review on PR #683 P1: isPayloadEffectivelyEmpty now also checks the settings object. A vault with only settings (e.g. custom theme, font size) and zero hosts/keys/snippets is no longer treated as empty. P1: Dialog accessibility — use hideCloseButton to remove the non- functional close button, onEscapeKeyDown + onOpenChange prevent dismiss (the user MUST choose an option), and wrap the description in DialogDescription so aria-describedby is properly linked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use single-brace interpolation syntax for cloudSummary i18n key The project's i18n system uses single-brace placeholders ({var}), not double-brace ({{var}}). The double-brace syntax was rendering as raw text instead of being interpolated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
e8b9122270 |
feat: auto-detect network devices from SSH banner and skip stats polling (#680)
* feat: auto-detect network devices from SSH banner and skip stats polling (#674) Fixes rapid AAA session churn reported on Cisco/HPE/similar network devices running Netcatty. The root cause was two separate polls that both open fresh exec channels (each counted as its own AAA session on many network devices): - runDistroDetection() opens a brand new SSH connection every time a host connects to run `cat /etc/os-release || uname -a` - useServerStats polls `conn.exec(statsCommand)` every 5 seconds Both commands fail on non-POSIX CLIs, but the channels still hit AAA. This change avoids both by reading the SSH server identification string that ssh2 already captures during the handshake (`conn._remoteVer`). No extra network round-trips, zero additional AAA entries. ## Changes **sshBridge.cjs** - Store `conn._remoteVer` on the session object at connect time as `session.remoteSshVersion` - New IPC handler `netcatty:ssh:remoteInfo` (`getSessionRemoteInfo`) returning the captured SSH server software string **preload.cjs / global.d.ts / useTerminalBackend.ts** - Thread `getSessionRemoteInfo(sessionId)` through to the renderer **domain/host.ts** - `NETWORK_DEVICE_OPTIONS` constant listing the vendor IDs we can recognize (cisco, juniper, huawei, hpe, mikrotik, fortinet, paloalto, zyxel) - `detectVendorFromSshVersion()` — pure function that parses an SSH server software string and returns a vendor ID or ''. Pattern set is sourced from Nmap nmap-service-probes (authoritative), the ssh-audit software.py reference, and vendor docs; see code comments for the exact matches used. - `classifyDistroId()` returns `linux-like | network-device | other` so features that require a POSIX shell can gate on the result. **createTerminalSessionStarters.ts (runDistroDetection)** - Before running the /etc/os-release probe, call `getSessionRemoteInfo` on the already-connected session and feed the banner into `detectVendorFromSshVersion`. If the vendor maps to a known network device, emit the vendor ID via the existing `onOsDetected` callback and skip the shell probe entirely. For unknown or generic OpenSSH/Dropbear banners the existing behavior is preserved. **Terminal.tsx** - `isSupportedOs` now derives from `classifyDistroId(effectiveDistro)` combined with `host.deviceType !== 'network'`, so neither explicit network-device hosts nor banner-detected ones trigger the stats polling loop. **useServerStats.ts** - Add a consecutive-failure counter. After 3 consecutive failed polls, stop the interval for this session (reset on disconnect / sessionId change / settings toggle). This is the fallback for hosts the banner classifier cannot identify (Juniper JUNOS, Cisco NX-OS, Arista EOS — all present as plain `OpenSSH_*` but do not support the POSIX stats pipeline). **DistroAvatar.tsx / HostDetailsPanel.tsx** - Add 8 network-device vendor icons (Cisco, Juniper, Huawei, HPE, MikroTik, Fortinet, Palo Alto, ZyXEL) alongside the existing Linux distro icons, with brand colors. Icons sourced from Simple Icons (CC0) where available; HPE and ZyXEL use simple abbreviation placeholders. - Network device vendors are added to the manual distro override dropdown so users can pin an icon even if their device has an exotic banner we don't auto-detect. **i18n** - English + Chinese labels for the new vendor options in the Host Details distro selector. Closes #674 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: gate network-device detection on raw host.distro, not manual icon override Per Codex review on PR #680: the stats-polling gate was passing `host` through getEffectiveHostDistro() before classifying, which honors the manual distro override (`distroMode: 'manual'` + `manualDistro`). That meant a user who previously pinned an "ubuntu" icon on a host that later gets banner-detected as Cisco would still be classified as linux-like and keep generating the AAA session flood #674 is meant to eliminate. Separate display from gating: - Display (DistroAvatar, host cards): keeps using getEffectiveHostDistro so users can cosmetically override the icon. - Gating (useServerStats via Terminal.tsx isSupportedOs): reads host.distro directly — the value populated by banner detection — alongside the explicit host.deviceType flag. Manual icon choice can no longer re-enable polling on a detected network device. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: guard distro detection against stale session timers Per Codex review on PR #680: runDistroDetection is scheduled on a 600ms setTimeout after connection and also makes async calls of its own. A quick disconnect + reconnect on the same session slot could fire the old timer against the new session, reading host B's SSH banner via getSessionRemoteInfo and writing host B's vendor onto host A's distro field — wrong icon and wrong stats-polling state. Follow the same pattern already used for the startup-command timer in this file (scheduledSessionId captured at schedule time, checked inside the timer). Capture `id` at schedule time, bail out if ctx.sessionRef.current no longer matches, and re-check after every async await inside runDistroDetection so that a reconnect during the banner fetch or the os-release probe also bails cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address local codex review on PR #680 Addresses three issues found in a local Codex review pass after the remote reviewer gate was flaky: ## P0 — session tokens instead of sessionId for stale-timer guard The previous guard captured `id` returned from startSSHSession and compared against `ctx.sessionRef.current` inside the setTimeout and the async runDistroDetection. But the renderer passes `sessionId: ctx.sessionId` into startSSHSession (see createTerminalSessionStarters.ts:543), meaning a tab reuses the SAME sessionId across disconnect+reconnect. The comparison `T1 === T1` always passed, so the guard was a no-op. Replaced with a module-level Map<sessionId, object> that stores the live "connection token" for each sessionId slot. Each call to startSSH mints a fresh `{}` token and overwrites the entry. Timers and async continuations compare their captured token against the current map value by reference — a reconnect replaces the map entry with a new token, so stale callbacks bail cleanly. ## P1 — run os-release probe on the existing SSH connection The fallback /etc/os-release probe used `execCommand` which creates a brand-new SSHClient() on every call. On network devices that present as plain `OpenSSH_*` and fall through to this step (JUNOS, NX-OS, EOS) it added one extra full-auth AAA session log entry per connect, in addition to the failing stats polls. Added `getSessionDistroInfo(sessionId)` as a new IPC handler that runs the same probe via `session.conn.exec()` — an exec channel on the already-open connection, no new handshake. Plumbed through preload.cjs, global.d.ts, and useTerminalBackend.ts. runDistroDetection uses this instead of execCommand in the fallback path, also removing the unused auth-credentials argument (we are no longer opening a new connection, so no credentials are needed). ## P2.1 — don't re-arm timers after giving up After the consecutive-failure counter trips, useServerStats cleared the interval but a subsequent effect rerun (visibility change, settings tweak, etc.) would schedule a fresh `setTimeout` and `setInterval` that would just call the early-return path forever. The scheduling block now checks `givenUpRef.current` before arming either timer. The flag is still cleared on the normal disconnect / sessionId-change reset path so a reconnect gets a fresh attempt. ## P2.2 — drop the ambiguous IPSSH-* → cisco mapping Nmap's `match ssh m|^SSH-([\d.]+)-IPSSH-` line is labelled as `Cisco/3com IPSSHd` — it cannot identify a specific vendor from the banner alone. Mapping it to `cisco` would risk showing the wrong vendor icon on a 3Com device. Removed the rule entirely and documented why with a code comment; users with such devices can still use the Host Details manual distro override. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address remaining gaps from local codex follow-up review P0 gap — delete connection token on session exit. Previously the map entry lingered after disconnect, so a very late-firing timer could still pass the isConnectionTokenCurrent check even though the session no longer existed. Functionally harmless (the IPC calls would fail) but semantically wrong. Now connectionTokensBySessionId.delete() is called in the onSessionExit handler. P1 new — exec channel leak on timeout in getSessionDistroInfo. The timeout branch resolved the promise but didn't close the stream, so a hanging remote command would leave the exec channel open until the SSH connection itself dropped. Added a settled guard (resolve-once) and stream.close() on timeout. P2.1 gap — givenUpRef not reset on sessionId change. The failure counter reset only happened in the !isConnected branch of the main effect, so a sessionId swap while still connected (rare, but possible if the tab reconnects without toggling connected state) would permanently suppress polling. Added a small dedicated effect that resets both counters when sessionId changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
60071424d0 |
fix: prevent crash when clicking external links with no default browser (#676)
* fix: prevent crash when clicking external links with no default browser (#663) On systems like Tiny11 where no default browser is associated with http/https URLs, shell.openExternal() rejects with Windows error 0x483 ("No application is associated..."). The main process treated that rejection as an unhandledRejection, which the global handler re-throws as fatal, crashing the entire app. Root cause: windowManager.cjs used `void shell?.openExternal?.(url)` inside a try/catch, assuming the try would cover the call. `void` only discards the returned Promise — it does not catch async rejections, so when openExternal rejected, the error escaped as a floating unhandledRejection. The IPC handler in main.cjs (`netcatty:openExternal`) also awaited shell.openExternal() without any try/catch. Electron's ipcMain.handle forwards rejections to the renderer over IPC, but the renderer-side fallback called `window.open()`, which re-entered the same buggy windowManager path — and that is where the process actually died. Changes: - windowManager.cjs: attach an explicit `.catch` on the openExternal Promise in both createExternalOnlyWindowOpenHandler and createAppWindowOpenHandler so rejections cannot propagate. - main.cjs: wrap the IPC handler in try/catch and return a structured { success, error } result instead of throwing. This lets the renderer render an informative message. - global.d.ts: update the openExternal return type to match. - useApplicationBackend.ts: read the structured result and throw on failure so callers can react; drop the now-redundant window.open() fallback for the Electron branch (kept only for non-Electron envs). - SettingsApplicationTab.tsx: show a friendly toast ("No default browser configured — please set one in system settings") when openExternal fails, instead of the previous silent failure. - i18n: add en + zh-CN strings for the toast. Closes #663 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: fall back to in-app browser window when system has no default browser Instead of showing a toast when shell.openExternal() fails (e.g. Tiny11 with no default browser), open the URL in a minimal in-app BrowserWindow so users can still read the linked page. windowManager.cjs now exposes: - openFallbackBrowser(url, opts): creates a stripped-down BrowserWindow that loads the URL. No preload script (remote content must never touch contextBridge), contextIsolation/nodeIntegration/sandbox all set to safe defaults, and an isolated persist:netcatty-fallback-browser session so cookies and storage do not leak into the main app. Basic Alt+Left / Alt+Right / Ctrl-or-Cmd+R shortcuts for navigation and reload. - tryOpenExternalWithFallback(shell, url, opts): tries shell.openExternal first; on rejection, falls back to openFallbackBrowser. Returns { success, fallback?: "in-app-browser" }. All three external-URL call paths now route through this helper: - main.cjs netcatty:openExternal IPC handler - createExternalOnlyWindowOpenHandler (popup blocker for child windows) - createAppWindowOpenHandler (main/settings window window-open handler) The renderer-side toast is retained as a last-resort for the rare case that both system and in-app browsers fail (e.g. BrowserWindow creation error). Copy updated to reflect the new behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve rejection semantics for failed external opens Per Codex review on PR #676: returning { success, error } from bridge.openExternal changed the contract from "reject on failure" to "resolve with a failure object on failure", which silently broke callers that rely on rejection to abort flows. useCloudSync's OAuth path is the clearest example: it wraps bridge.openExternal in a try/catch and rejects browserPromise inside the catch. With the resolved-failure contract, that catch never fires, so Promise.race([callbackPromise, browserPromise]) can hang indefinitely when no browser is available. Revert the contract: - tryOpenExternalWithFallback resolves void on success (system browser or in-app fallback) and throws on total failure - main.cjs IPC handler awaits and lets rejections propagate - global.d.ts openExternal is Promise<void> again - useApplicationBackend just awaits — rejections propagate naturally - SettingsApplicationTab's existing try/catch + toast continues to work as before Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: propagate fallback browser loadURL failures Per Codex P2: openFallbackBrowser swallowed loadURL rejections by attaching a .catch that only logged, so any caller using tryOpenExternalWithFallback as a success signal saw an opened window as success even when the page failed to load. OAuth flows would then wait for the downstream callback timeout instead of canceling early on malformed or unreachable URLs. openFallbackBrowser now returns { window, loaded } where `loaded` is the raw loadURL Promise, and tryOpenExternalWithFallback awaits it in the fallback path. On initial load failure, the broken window is closed and the original shell.openExternal error is re-thrown. The internal popup handler inside the fallback window keeps its fire-and-forget behavior (it must return synchronously) but now explicitly catches the loaded rejection to avoid unhandledRejection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
80fbf0da2f |
feat: add data-section hooks for Custom CSS targeting (#642) (#655)
Custom CSS already exists in Settings → Appearance, but major UI
components use only Tailwind utility classes, making it hard for
users to reliably target regions in their custom styles.
This adds stable `data-section="..."` attributes on the root element
of the most commonly customized UI regions so users can write selectors
like `[data-section="snippets-panel"] { font-size: 14px !important; }`
without depending on implementation details.
Instrumented regions:
- snippets-panel (ScriptsSidePanel)
- host-details-panel (HostDetailsPanel via AsidePanel dataSection prop)
- group-details-panel (GroupDetailsPanel)
- serial-host-details-panel (SerialHostDetailsPanel)
- ai-chat-panel (AIChatSidePanel)
- vault-view / vault-sidebar / vault-main / vault-hosts-header / vault-host-list (VaultView)
- terminal-workspace / terminal-workspace-sidebar (TerminalLayer)
- top-tabs (TopTabs — also keeps existing data-top-tabs-root)
Also updated the Custom CSS description and placeholder in both
English and Chinese to list available hooks and show a working
example (snippet panel font-size override).
Closes #642
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
24df4b6548 |
fix: support CSV password import and save password in keyboard-interactive auth (#629)
* fix: support CSV password import and save password in keyboard-interactive auth (#627) - Add Password column support to CSV import/export/template - Add isAPasswordPrompt detection (prompt contains "password" + echo=false) - Auto-fill saved password in keyboard-interactive modal - Add "Save password" checkbox for password prompts in keyboard-interactive modal - Wire save callback through sessionId → host to persist password Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback for keyboard-interactive and CSV changes - Merge password field in dedupeHosts to avoid losing passwords from duplicate CSV rows - Extract isAPasswordPrompt to module-level pure function - Only render save-password checkbox at the first password prompt index - Clean up orphaned i18n keys (useSaved, useSavedPassword, fill, fillSaved) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve whitespace in CSV imported passwords Passwords may intentionally contain leading/trailing whitespace. Removing .trim() ensures lossless CSV round-trip and correct auth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: exclude OTP prompts from password detection and guard jump host save - Add negative patterns (one-time, otp, verification, token, code) to isAPasswordPrompt to avoid auto-filling SSH password into OTP fields - Only save password when request hostname matches session hostname, preventing jump host passwords from overwriting the destination host Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip formula injection guard for password column in CSV export Password values starting with =, +, -, @ were getting a ' prefix from the CSV formula injection protection, breaking round-trip fidelity. Now password column is escaped for CSV syntax only, preserving the credential verbatim. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: only skip formula guard for data rows, not header row Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
f284fb0505 | Refine host group drop feedback (#617) | ||
|
|
d2fe0ecefe |
feat: replace inline custom shell input with modal dialog
When selecting "Custom..." from the shell dropdown, opens a modal with: - Full-width input field for shell executable path - Path validation feedback (valid/not found/is directory) - Quick-pick buttons for common shell paths - Confirm/Cancel buttons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
1cccbfe5fb |
fix: update renderer description text from Canvas to DOM
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
1c5960a054 |
feat: add font weight slider to terminal theme side panel
- Add range slider (100-900) in the Font tab of ThemeSidePanel
- Wire through TerminalLayer → App.tsx → useSettingsState
- Changes persist immediately via updateTerminalSetting('fontWeight')
- Display current weight value in status bar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
6ac36be04b | feat: local shell selection with auto-discovery (#605) | ||
|
|
8ed1588fdb |
feat: add per-host option for Backspace sends ^H (#604)
* feat: add per-host option for Backspace sends ^H (#602) Add backspaceSendsCtrlH option at host and group level to send ^H (0x08) instead of DEL (0x7F) when pressing Backspace, for legacy system compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add per-host backspace behavior option (#602) Add backspaceBehavior option at host and group level. When not configured, xterm default behavior is preserved with zero interception. When set to 'ctrl-h', remaps DEL (0x7F) → ^H (0x08) for legacy system compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use remapped backspace byte for broadcast input Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
be80741314 |
feat: custom keywords and colors in keyword highlighting (#597)
Some checks failed
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
* feat: support custom keywords and colors in global keyword highlighting (#590) Add ability to create custom keyword highlight rules in global settings (Settings > Terminal > Keyword Highlighting): - Per-rule enable/disable toggle for both built-in and custom rules - Add custom rules with label, regex pattern, and color picker - Delete custom rules (built-in rules cannot be deleted) - Pattern validation with error feedback - Custom rules sync across devices via cloud sync - i18n support (en, zh-CN) Built-in categories (Error, Warning, OK, Info, Debug, URL/IP/MAC) are preserved and cannot be deleted, only toggled and recolored. Closes #590 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: use dialog modal for adding custom keyword highlight rules Replace inline form with a proper modal dialog: - Button opens dialog instead of showing inline inputs - Dialog has label+color, regex pattern, and live preview - Reset and Add buttons side by side in footer area - Add common.add i18n key (en, zh-CN) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ui: unify button styles in keyword highlight section Both buttons now use ghost variant with equal flex-1 width for a cleaner, balanced layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ui: fix keyword highlight rule list alignment - Add placeholder spacer (w-5) for built-in rules to match delete button width on custom rules, keeping color pickers aligned - Move regex pattern to second line for custom rules - Use block+truncate for label and pattern text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ui: hide regex, show edit/delete icons after label for custom rules - Remove regex pattern display from rule list - Add pencil (edit) and trash (delete) icons after custom rule label, visible on hover - Edit opens the same dialog pre-filled with rule data - Dialog supports both add and edit modes with appropriate titles/buttons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ui: remove toggle dots, simplify edit/delete to plain icons - Remove the red enable/disable dot button from all rules - Replace Button wrappers with plain Lucide icons for edit/delete (no hover background, just cursor pointer) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve multi-pattern rules on edit, keep disabled state on reset - Editing a custom rule now preserves patterns beyond the first one - Reset to default colors no longer force-enables disabled rules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace all patterns on edit instead of preserving hidden ones When editing a custom rule, save only the single user-visible pattern rather than silently keeping extra patterns the user cannot see. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve regex whitespace and multi-pattern rules on edit - Stop trimming regex patterns on save (only trim for empty check) - If pattern field unchanged during edit, preserve all original patterns so changing just label/color doesn't drop extra regexes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve additional patterns when editing custom rule When editing, replace only the first pattern (the one shown in the dialog) and keep any additional patterns intact to prevent data loss for multi-pattern rules from sync or import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
33f8221d5c |
refactor: remove immersive mode toggle remnants — always enabled
Immersive mode was already hardcoded to true with a no-op setter. Clean up all dead code: - Remove isImmersive param from useImmersiveMode hook - Remove immersiveMode/setImmersiveMode from useSettingsState - Remove toggle from SettingsPage and SettingsAppearanceTab - Remove sync read/write of immersiveMode setting - Remove i18n keys for the removed toggle - Simplify App.tsx conditionals Kept: useImmersiveMode hook (core logic), CSS classes (fade overlay), sync type field (backward compat), storage key. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2f314c3588 |
feat: group configuration inheritance (#220) (#593)
* feat(i18n): add translations for group config panel * feat(models): add GroupConfig data model, resolution logic, and encryption Add the GroupConfig interface for group-level default settings that hosts inherit. Includes ancestor-chain resolution (A/B/C merges from A, A/B, A/B/C), host-level application logic, storage key, and secure field encryption/decryption for sensitive GroupConfig fields. Part of #220. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(state): add groupConfigs state management with encryption Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(ui): create GroupDetailsPanel with full config editing Side panel for editing group-level default configuration using AsidePanel. Includes General, SSH, Telnet, Advanced, Mosh, and Appearance sections with sub-panel navigation for Proxy, Chain, EnvVars, and Theme selection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): wire GroupDetailsPanel, replace rename dialog with full config panel Replace all group rename dialog triggers with the new GroupDetailsPanel sidebar. The hover edit button, context menu, and tree view edit callbacks now open the full group configuration panel instead of a simple rename dialog. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(connect): apply group config defaults at connection time When connecting to a host, merge group-level default configuration so hosts inherit their group's settings for auth, protocol, appearance, and other inheritable fields. Connection logs still reference the original host's label/hostname. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(sync): include groupConfigs in sync and export payloads Add groupConfigs to SyncPayload, SyncableVaultData, buildSyncPayload, and applySyncPayload so group connection defaults are preserved during cloud sync and data import/export. Also wire groupConfigs into the vault object in SettingsPage so it flows through to the sync payload builder. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(vault): update group configs on move and delete * feat(host-panel): show inherited group defaults as placeholders When editing a host that belongs to a group with configuration, group default values now appear as placeholder text in username, startup command, and charset fields where the host doesn't have its own value. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: clean up unused imports in GroupDetailsPanel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(group-panel): add/remove protocol sections, editable parent group - SSH and Telnet sections are now add/remove — click "Add Protocol" to enable, "..." menu to remove. Only enabled protocols override hosts. - Parent Group is now editable via Combobox dropdown for quick group moving. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move SSH-specific fields into SSH protocol section Startup Command, Legacy Algorithms, Proxy, Host Chaining, Environment Variables, and Mosh are all SSH-specific and now only visible when SSH protocol is added. Only Charset remains as a shared field in the Advanced section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: hide charset and appearance when no protocol is added Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: close Add Protocol dropdown after selection Use controlled open state to explicitly close the dropdown when a protocol is selected, preventing residual content from overlapping the newly rendered section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: apply group defaults in TerminalLayer sessionHostsMap Terminal component was re-reading the original host from the hosts array by hostId, bypassing the group defaults applied in handleConnectToHost. Now sessionHostsMap applies resolveGroupDefaults + applyGroupDefaults when building the host object for each session, so Terminal sees the merged credentials/settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move Add Protocol to bottom, fix i18n for protocol/font labels - Add Protocol button moved below Appearance section - Added i18n keys: addProtocol, removeProtocol, fontFamily, fontSize - All hardcoded English strings replaced with t() calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace font family text input with TerminalFontSelect dropdown Use the same font selector component as settings, showing available terminal fonts with preview. Includes "Use Global" reset button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(group-panel): match HostDetailsPanel key/certificate selection pattern Replace the simple Combobox key selector with the same credential selection flow used in HostDetailsPanel: a popover with Key/Certificate options, inline combobox per type, and proper badge display with certificate icon. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(group-panel): add Local Key File option to credential selection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(group-panel): add identityFilePaths to GroupConfig and Local Key File option - Added identityFilePaths to GroupConfig interface and INHERITABLE_KEYS - GroupDetailsPanel now supports Key, Certificate, and Local Key File credential selection, matching HostDetailsPanel's full credential flow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: prevent local key file input from overflowing panel width Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: constrain local key file input width with w-0 flex-1 Native input elements have a large default min-width. Using w-0 with flex-1 forces the input to shrink within the flex container. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add overflow-hidden to SSH Card to contain local key file input Matches HostDetailsPanel's Card which uses overflow-hidden on the credentials section to prevent long file paths from overflowing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add min-w-0 to key file path row for proper text truncation Flex children need min-w-0 for truncate to work correctly, otherwise the text pushes the container wider. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: force key file path text truncation with inline max-width calc Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use fixed 320px max-width on key file path text to force truncation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add overflow-hidden to AsidePanelContent to prevent content overflow The root cause was the inner div of AsidePanelContent only had overflow-x-hidden which was being overridden by ScrollArea's viewport. Changed to full overflow-hidden with w-full box-border. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: override Radix ScrollArea viewport's display:table in AsidePanel Radix ScrollArea Viewport wraps content in a div with display:table and min-width:100%, causing content to expand beyond the panel width. Override this on AsidePanelContent's ScrollArea to use display:block and min-width:0 instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: critical issues — seed new hosts from group defaults, validate group names, fix empty import - HostDetailsPanel: When groupDefaults has values for port/username/charset, new hosts start with undefined/empty so group defaults take effect via applyGroupDefaults() instead of being blocked by hardcoded values - GroupDetailsPanel: Validate group name in handleSubmit to reject '/' and '\' characters, matching the old rename dialog behavior, with visual error - useVaultState: Check groupConfigs !== undefined instead of truthy so that importing an empty array [] properly clears all group configs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: safe prefix replacement, remove dead code, extract shared resolveEffectiveHost - Replace all .replace(oldPath, newPath) / .replace(sourcePath, newPath) with explicit prefix slicing (newPath + str.slice(oldPath.length)) in handleSaveGroupConfig and moveGroup for more robust path renaming - Remove dead c.path === oldPath branch in finalConfigs mapping since updatedConfigs already contains the config with newPath - Extract resolveEffectiveHost helper in App.tsx to deduplicate group defaults resolution in _handleTrayPanelConnect and handleConnectToHost Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: preserve undefined port on save when group has port default form.port || 22 was forcing port to 22 even when intentionally left undefined for group inheritance. Now uses nullish coalescing and only defaults to 22 when no group port default exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: SSH-adjacent field detection, chain host defaults, telnet inheritance, theme clear - hasSshFields() now checks proxyConfig, hostChain, startupCommand, legacyAlgorithms, environmentVariables, moshEnabled, moshServerPath, and identityFilePaths so the SSH section auto-opens when editing - Chain hosts in sessionChainHostsMap now get group defaults applied via resolveGroupDefaults + applyGroupDefaults - Added telnetEnabled to GroupConfig interface and INHERITABLE_KEYS; save handler sets telnetEnabled: true when Telnet section is on - Theme/font "Use global" clear now sets override to false instead of undefined, preventing parent group theme from leaking through Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: review round 4 — sync, SFTP, port forwarding, type safety, UX - Scan groupConfigs in encrypted credential guard (P1 security) - Add groupConfigs to auto-sync payload and three-way merge (P1 sync) - Apply group defaults in SFTP connections (P1 SFTP) - Apply group defaults in all port forwarding paths (P1 port forwarding) - Make Host.port optional to fix unsafe type cast (P1 type safety) - Fix port input empty → 0 instead of undefined (P2) - Add port placeholder showing inherited value (P2) - Mutual exclusion of group/host detail panels (P2) - Fix sub-panel width jump 420px → 380px (P2) - Validate duplicate group path on rename/reparent (P2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: review round 5 — null guard, empty array inheritance, memo comparator, form reset - Guard groupConfigs import against null payload (P1 crash) - Validate duplicate path on moveGroup drag-drop (P2 data corruption) - Clear empty environmentVariables to undefined for group inheritance (P1) - Clear empty hostChain to undefined for group inheritance (P2) - Add groupConfigs to SftpView memo comparator (P1 stale defaults) - Add key={editingGroupPath} to GroupDetailsPanel for form reset (P1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: review round 6 — copy credentials, protocol dialog use effective host - Apply group defaults in handleCopyCredentials (P2) - Apply group defaults in hasMultipleProtocols check (P2) - Pass effective host to ProtocolSelectDialog (P2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: serialize protocol:'ssh' marker to persist SSH section in group config - Add protocol:'ssh' as marker field in handleSubmit SSH block - Detect protocol:'ssh' in hasSshFields() to preserve section on reopen - Clean up protocol field in removeSsh() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
c7ae51b952 |
feat: host/group management improvements (#506) (#589)
* feat(models): add pinned and lastConnectedAt fields to Host Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(i18n): add translations for pinned and recently connected sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(vault): add pin toggle, lastConnectedAt tracking, and computed sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): render Pinned and Recently Connected sections at root level Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): add pin/unpin context menus and hover edit buttons in all views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): make breadcrumb a drop target for moving groups back to root Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(settings): add toggle for showing recently connected hosts section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve lint warnings for unused vars and unnecessary dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve pin performance and add pop-in animation - Use ref for hosts in callbacks to avoid stale closures and unnecessary re-renders when hosts array changes - Add pop-in spring animation on pinned host cards with staggered delay for a satisfying visual effect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fix pop-in animation visibility and improve pin responsiveness - Move @keyframes pop-in out of @layer base to global scope so inline styles can reference it - Add translateY to animation for a bouncier, more satisfying feel - Use pinnedAnimKey to force card remount on pin changes so animation replays each time - Wrap onUpdateHosts in startTransition for non-blocking pin updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: only animate newly pinned card, increase section spacing - Track lastPinnedId instead of global animKey so only the newly pinned card gets the pop-in animation, not all existing pinned cards - Clear animation state via onAnimationEnd for clean re-trigger - Add mb-4 to Pinned and Recent sections for better visual separation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(vault): show pin indicator icon on pinned host cards Small semi-transparent pin icon in top-right corner of pinned host cards in the Hosts section (grid view only). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: use solid amber/yellow pin indicator icon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: tilt pin indicator icon 45 degrees Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: replace pin indicator with filled amber star on all pinned cards Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move lastConnectedAt tracking to App-level handleConnectToHost Previously updating lastConnectedAt in VaultView's handleHostConnect which could be lost during tab switches. Now tracked at the App level where all connections are handled, ensuring the timestamp persists regardless of UI navigation state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Codex review findings (P2 issues) 1. useStoredBoolean now syncs across same-window components via CustomEvent dispatch, so Settings toggle immediately updates VaultView 2. lastConnectedAt updated after connectToHost succeeds, not before 3. Pinned and Recently Connected sections now respect active search and tag filters Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address second round Codex review findings 1. Track lastConnectedAt on actual 'connected' status instead of session creation - handles via handleSessionStatusChange wrapper 2. Covers tray panel connections since all paths go through updateSessionStatus 3. Pinned/Recent cards now honor multi-select mode with checkbox UI instead of triggering connections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address third round Codex review findings 1. [P1] Use hostsRef in handleSessionStatusChange to avoid overwriting concurrent host changes with stale snapshot 2. [P2] Exclude pinned/recent hosts from main host list at root level to prevent duplicate cards on screen 3. [P2] Remove Pin action from tree view context menu since tree view has no pinned ordering/indicator support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address fourth round Codex review findings 1. [P1] Remove leftover onToggleHostPinned references in HostTreeView root-level component that were missed in previous cleanup 2. [P2] Add draggable + onDragStart to pinned/recent host cards so drag-and-drop between groups still works 3. [P3] Fix grouped view header count to exclude hosts already shown in pinned/recent sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use functional state update for lastConnectedAt, dedupe pinned from recent 1. [P2] Add updateHostLastConnected using setHosts(prev => ...) functional update pattern (same as updateHostDistro) to avoid overwriting concurrent host changes when multiple sessions connect simultaneously 2. [P3] Exclude pinned hosts from Recently Connected section to prevent duplicate cards between the two top sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: wire showRecentHosts into settings sync, clear pin on duplicate 1. [P2] Add showRecentHosts to SyncPayload settings so the preference survives cloud sync and settings export/import 2. [P2] Clear pinned and lastConnectedAt on duplicated hosts so copies don't inherit pin/recent status from the original Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
d9b51c3a50 | feat: add GitHub Copilot CLI agent support | ||
|
|
db604e4c41 |
fix: localize delete dialog labels and preserve moved tab tree selection
- Add i18n keys for "Host" and "Path" labels in delete confirmation dialog (was hardcoded English, broken under zh-CN) - Pass moved tab ID as extra keepId when clearing tree selections after moveTabToOtherSide, since the ref still has pre-move state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
21daccf6ed |
fix: enforce cross-pane selection mutual exclusivity and improve delete dialog
- Add clearSelectionsExcept to clear all file/tree selections except the target pane, called on focus change, tab switch, tab add, and tab move - Fix SftpFileRow areEqual to include showSelectionHighlight so highlight updates when focus changes between panes - Improve delete confirmation dialog with host/path context and separate single vs multi-delete descriptions - Fix hover style on selected rows to prevent flicker Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
255a4730e7 |
feat: make SFTP folder transfer concurrency configurable (#558)
* feat: make SFTP folder transfer concurrency configurable The number of files transferred in parallel during folder uploads/ downloads was hardcoded to 4. Add a setting (1-16, default 4) in Settings > SFTP so users can tune it for their server and network. The value is read from localStorage at transfer start time, so changes take effect on the next folder transfer without restart. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sync transfer concurrency setting across windows Add notifySettingsChanged broadcast, IPC onSettingsChanged handler, and storage event listener for the transfer concurrency setting so changes propagate to all open windows immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move setSftpTransferConcurrency after notifySettingsChanged The useCallback referenced notifySettingsChanged before it was defined (const is not hoisted), causing a ReferenceError on mount. Move the definition after notifySettingsChanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
dd50f95583 |
feat: add workspace focus indicator style setting (dim vs border) (#557)
* feat: add workspace focus indicator style setting (dim vs border) Users can now choose between two focus indicator styles for split terminal panes: - Dim: reduces opacity of unfocused panes (current default) - Border: shows a colored border on the focused pane (old style) The setting is in Settings > Terminal > Workspace Focus Indicator. Implementation uses a CSS data attribute on documentElement to toggle between the two styles, avoiding prop threading. Closes #556 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sync workspace focus style across windows Add cross-window notification handling for the workspace focus style setting so changes in the Settings window take effect in the main terminal window immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
c8d145f52e |
feat: add default file opener setting for SFTP (#554)
* feat: add default file opener setting for SFTP Add a global default opener that is used as fallback when no per-extension file association exists, eliminating the need to select an editor for every new file type. The default opener is stored as a special "*" key in the existing file associations map, so it syncs and persists automatically. Settings UI provides three options: always ask (current behavior), built-in editor, or a chosen system application. Closes #550 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use reserved key for default opener to avoid extension collision Replace "*" with "__default__" as the default opener storage key to prevent a theoretical collision with files named "foo.*" where getFileExtension would return "*". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip built-in editor default for known binary files When the global default opener is set to built-in editor, binary files (zip, png, etc.) should not be opened as text. Fall back to the chooser dialog for known binary formats instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: store default opener in separate localStorage key Move the default opener out of the FileAssociationsMap into its own storage key (STORAGE_KEY_SFTP_DEFAULT_OPENER) to completely eliminate any possibility of key collision with file extensions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
e3b882bdf9 |
feat(sftp): add tree view explorer for SFTP pane (#547)
* feat(sftp): add onListDirectory to SftpPaneCallbacks interface
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* feat(sftp): implement onListDirectory in left and right callbacks
* feat(sftp): add tree view i18n keys
* feat(sftp): add list/tree view mode toggle to toolbar
* feat(sftp): add viewMode state and tree view conditional rendering to SftpPaneView
* feat(sftp): implement SftpPaneTreeView with lazy loading and context menu
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(sftp): resolve lint errors in tree view implementation
Rename inner `t` and `ts` variables in onListDirectory callbacks to
`toSize`/`toTs`/`ms` to avoid shadowing the outer `t` translation param.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(sftp): resolve post-merge lint errors
- Remove duplicate sftp.context.copyPath i18n key (upstream added it too)
- Remove unused AlertCircle import from SftpPaneFileList (upstream removed usage)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* perf(sftp): optimize SftpPaneTreeView render pipeline
Split useMemo into two stages so selection changes no longer
rebuild the full node descriptor array. Extract stable
selection-aware callbacks (drag, copy, delete) via refs so
TreeNode React.memo can reliably bail out. Remove unused props
(onNavigateTo, draggedFiles), move NodeDescriptor type to
module scope, and fix selectedFiles undefined bug in context menu.
* feat(sftp): add path-aware rename and delete for tree view
Wire renameFileAtPath and deleteFilesAtPath through the full
callback stack so tree view context menu actions operate on
full paths instead of basenames. Update useSftpPaneDialogs to
accept entryPath in openRenameDialog and resolve parent dir
in handleDelete, keeping list view behaviour unchanged.
* fix: harden SFTP tree view actions and selection
* fix: support tree selection shortcuts and nested create targets
* fix: keep SFTP tree view sorting in sync
* Improve SFTP tree view interactions and refresh behavior
* Optimize SFTP tree refresh and pane state usage
* Reduce remaining SFTP tree performance overhead
* Fix nested SFTP drop target routing
* Restore keyboard access to parent tree entry
* Revert "Display approved AI commands in terminal sessions before their output. (#546)"
This reverts commit
|
||
|
|
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> |
||
|
|
e6166a1de3 |
feat: AI Provider 高级参数配置 (#532) (#533)
* feat: expose advanced AI model parameters in provider settings (#532) Add collapsible "Advanced Parameters" section to provider config with optional max_tokens, temperature, top_p, frequency_penalty, and presence_penalty fields. Parameters are merged into streamText() calls only when explicitly set, otherwise provider defaults apply. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use maxOutputTokens instead of maxTokens for ai@6 SDK The streamText CallSettings in ai@6 expects maxOutputTokens, not maxTokens. Without this fix the user's max_tokens setting is silently ignored. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: allow negative penalty input and clamp params on save - Use raw string state for penalty fields so typing "-" is not discarded before the digit is entered - Clamp all parameters to valid ranges on save (temperature 0-2, topP 0-1, penalties -2 to 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use raw string state for all numeric advanced param inputs Prevents intermediate text like "0." from being normalized to "0" during keyboard entry of decimal values for temperature, topP, and maxTokens fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: clamp max_tokens to minimum of 1 after rounding Prevents Math.round(0.4) = 0 from being persisted and causing streamText to reject with "maxOutputTokens must be >= 1". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject non-finite max_tokens before persisting Guard with Number.isFinite to prevent Infinity from being stored and forwarded to streamText. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
9a7d4decff |
feat: 终端命令自动补全系统 (#527)
* feat: 终端命令自动补全系统 实现类似 WindTerm/Fish 的终端命令自动补全功能,不依赖机器学习: - 历史命令持久化存储:按主机分组,频率+时间衰减排序,跨会话共享 - 前缀匹配引擎:支持精确前缀匹配和模糊匹配(首字符+连续字符+词边界加权) - Prompt 检测器:识别 bash/$、zsh/%、fish/> 等常见 prompt 模式,排除 vim/less 等程序 - Ghost Text 插件:xterm.js 自定义 addon,光标后灰色行内建议,→ 接受全部,Ctrl+→ 接受一词 - 弹出补全菜单:浮动列表 UI,↑↓ 导航,Tab/Enter 选中,Esc 关闭,来源标记(h/c/s/o/a) - @withfig/autocomplete 集成:600+ 命令规范的子命令、选项、参数补全 - 上下文感知:解析命令行 token,根据当前位置提供对应类型的补全 - 用户配置:启用/禁用、Ghost Text、弹出菜单、防抖延迟、最小字符数等 - 快速打字防误触:检测打字速度,快速输入时抑制建议 - 输入防抖 100ms,异步匹配不阻塞 UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 补全菜单混合展示历史命令和 spec 子命令 - 输入已知命令名(如 docker)时即使没有空格也预览子命令 - 历史命令条数从 8 降为 5,留空间给 spec 建议 - 修复 wordIndex === 0 时 spec 补全被跳过的问题 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 补全菜单在终端底部时向上展开 当光标在终端下方、空间不足时,弹出菜单向上展开(底边对齐光标行), 避免溢出终端区域。列表顺序和选中逻辑不变——最可能的选项始终在顶部, 用户初始向下选择。参考 Termius 的做法。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 补全菜单跟随终端主题 + Enter 直接执行命令 1. 补全菜单颜色从终端主题动态派生(color-mix),不再硬编码色值, 确保与任何主题视觉一致 2. 在弹出菜单中按 Enter 选择命令时,直接插入并发送 \r 执行, 无需用户再按一次回车 3. Tab/鼠标点击仍然只插入不执行(保留选择后编辑的能力) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复 PR review 发现的全部 20 个问题 功能修复: - #1 修复 selectAndExecute 导致命令双重录入历史:用 suppressNextEnterRecordRef 标志位让 handleInput 的 Enter 分支跳过已经录入过的命令 - #2 修复 Prompt 末尾 $ 误判:重写 findPromptBoundary 为从左到右逐字符扫描, 排除 $HOME/$PATH 等变量引用(检查 $ 前是否有空格、是否在 token 内部) - #6 快速打字检测实际生效:快速打字时 debounce 延迟翻倍(200ms),等用户停顿 - #8 resolveSpecContext 处理带参数的 option(如 --name value): 识别 option 的 args 字段,自动跳过下一个 token - #9 Ghost text 位置随终端滚动/渲染更新:注册 term.onRender 回调 - #13 Escape 键不再拦截 vi-mode:仅在 popup 可见时消费 Escape, ghost text 显示时不拦截(ghost text 是被动的,不应阻止 shell 交互) - #14 所有 setState 统一使用 EMPTY_STATE 常量,不再遗漏 expandUpward 字段 架构修复: - #3 消除 CustomEvent 通信:改为 onAcceptText 回调注入, Terminal.tsx 直接传 writeToSession 回调给 hook, 删除 createXTermRuntime 中约 20 行 listener 代码和 cleanupAutocompleteListener 字段 - #7 xterm 私有 API 访问集中到 xtermUtils.ts:getCellDimensions 统一入口, 带缓存机制,仅在首次访问或 terminal 切换时触发 DOM 测量 - #16 删除 getCommandNameSuggestions 中多余的动态自导入 await import("./figSpecLoader") 性能修复: - #5 合并 ghost text 和 popup 的查询路径:删除独立的 getInlineSuggestion, fetchSuggestions 只调一次 getCompletions,ghost text 取 completions[0] - #10 preloadCommonSpecs 分批加载(每批 8 个,requestIdleCallback 间隔), 延迟 200ms 启动,且检查 enabled 才执行 - #11 scoreEntry 改为 scoreEntryAt(entry, now),now 在查询开始时缓存一次 - #15 scrollIntoView 从 smooth 改为 instant,消除快速导航动画排队 - #19 loadSpec 添加 in-flight 去重(inFlightLoads Map),同一 spec 并发加载只触发一次 import - #20 存储满时淘汰改为按 score 排序后保留前半,而非按插入顺序 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复二审发现的全部 10 个问题 功能修复: - #1(高) insertSuggestion 改用实时 detectPrompt 而非过时的 lastPromptRef, 修复用户继续打字后 Tab 选择建议导致字符重复插入的 bug - #2(中) handleInput Enter 录入历史优先用实时 detectPrompt, 修复快速打字场景下 recordCommand 记录不完整命令 - #9 suppressNextEnterRecordRef 添加 100ms 安全超时清除,防止 flag 残留 - #10 getNextWord 从 index 1 开始搜索分隔符,修复 ghost text 以 / 开头时 一次接受全部而非逐段的问题 性能修复: - #3(中) GhostTextAddon 注册 term.onResize 调用 invalidateCellDimensionCache, 确保 resize/字体变化后 cell 尺寸缓存正确失效 - #4 updatePosition 缓存 lastLeft/lastTop,位置无变化时跳过 DOM 写入; 字体属性移到 show() 中只设置一次,不再每帧写 6 个 style - #5 统一 clearState() 函数替代所有 setState({...EMPTY_STATE}), 带 popupVisible 守卫避免无效 re-render - #6 hasSpec 中 specs.includes() 改为 Set.has(),O(1) 查找 架构修复: - #7 Terminal.tsx 中 autocompleteAcceptTextRef 去掉多余的 useCallback 包装 - #8 删除 AutocompletePopup 的 onClose 死代码 prop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: popup 默认不选中任何项,用户按 ↑/↓ 后才选中 修复输入 ls 等简单命令时回车误执行联想结果的问题: - selectedIndex 初始为 -1(无选中),Enter 直接执行用户输入的命令 - 用户按 ↑/↓ 导航后 selectedIndex >= 0,此时 Enter 才执行选中的建议 - Tab 仍然可以直接接受第一条建议(主动接受行为) - Enter 无选中时关闭 popup 并让按键透传到终端 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fig spec 改为从静态资源 fetch 加载,修复生产构建中补全不工作 根因:@vite-ignore 动态 import 在 Electron 生产构建中无法解析 node_modules 路径(app:// 协议只能访问 dist/ 目录)。 修复方案(与 Monaco 编辑器相同的模式): - 新增 scripts/copy-fig-specs.cjs,prebuild 时将全部 739 个 fig spec 从 node_modules/@withfig/autocomplete/build/ 复制到 public/fig-specs/ - Vite 自动将 public/ 内容复制到 dist/,app:// 协议可以正常访问 - figSpecLoader.ts 改用 fetch + Blob URL + dynamic import 加载 spec, 同时保留 @vite-ignore import 作为 fallback(兼容 dev 模式) - public/fig-specs 加入 .gitignore(构建时生成,不进版本控制) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: ESLint 忽略 public/fig-specs 目录(第三方生成代码) 与 public/monaco 相同的处理方式——这些是从 node_modules 复制的 第三方构建产物,不应被项目 ESLint 规则检查。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 输入完整子命令名时展示其选项(如 git commit 显示 --message 等) 当 currentToken 完全匹配一个子命令时(如 "git commit" 中的 "commit"), 导航进入该子命令并展示其 options 和 sub-subcommands 作为预览。 之前的逻辑因为 name !== currentToken 过滤掉了完全匹配的项, 且 resolveSpecContext 的 consumedTokens 不包含当前 token, 导致停留在父级而看不到子级的选项。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复 fig spec index.js 解析失败导致补全不工作 根因:index.js 格式为 var e=[...],diffVersionedCompletions=[...]; 正则 /var\s+\w+\s*=\s*(\[[\s\S]*?\]);/ 要求 ] 后紧跟 ;, 但第一个数组后面是 , 不是 ;,导致非贪婪匹配跳到第二个 ];, 捕获了两个数组拼在一起,JSON.parse 失败,spec 列表为空。 修复:改用 indexOf 找第一个 [ 和对应的 ],直接截取子串解析。 fig spec 的 index 是简单的字符串平坦数组,无嵌套括号。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fig spec 改用 URL 直接 dynamic import,移除 fetch+Blob 方案 fetch + Blob URL + import() 方案可能被 Electron CSP 策略阻止。 改为直接用完整 URL 做 dynamic import: - dev: import("http://localhost:5173/fig-specs/git.js") - prod: import("app://./fig-specs/git.js") 两种环境下动态 import 都能正常解析模块,无需 fetch 中间步骤。 同时简化 getAvailableSpecs 也用同样方式,移除 fetch+正则解析。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fig spec 改为通过 Electron IPC 加载,彻底解决 dev/prod 加载问题 之前的方案(静态文件 + dynamic import / fetch + Blob URL)都因为 Vite dev server 对 .js 文件的模块转换和 Electron CSP 限制而失败。 新方案:通过 main process 的 Node.js require() 加载 fig spec, 通过 IPC 传给 renderer: - main.cjs: 添加 netcatty:figspec:list 和 netcatty:figspec:load handler - preload.cjs: 暴露 listFigSpecs() 和 loadFigSpec() API - figSpecLoader.ts: 通过 window.netcatty bridge 调用 IPC 优势: - main process 直接访问 node_modules,dev 和 production 都可靠 - 无需复制文件到 public/、无需 @vite-ignore hack - spec 数据通过 IPC 序列化传输,无 CSP 限制 - 删除了 scripts/copy-fig-specs.cjs 和 public/fig-specs/ 相关代码 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: main process fig spec 加载改用 import() 替代 require() @withfig/autocomplete 是 ESM 包("type": "module"), CommonJS 的 require() 无法加载 ESM 模块会抛 ERR_REQUIRE_ESM。 改用 dynamic import() 在 async handler 中加载。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fig spec 加载用 pathToFileURL 绕过 package.json exports 限制 @withfig/autocomplete 的 exports 字段只允许 import "." 和 "./dynamic", Node.js 严格遵守 exports map 拒绝解析 build/git.js 等子路径。 改为手动拼接文件绝对路径 + pathToFileURL 转换为 file:// URL 后 import, 完全绕过 Node.js 的 package exports 限制。 同时修复 promptDetector 不再 trim 尾部空格(用 cursorX 确定实际输入长度), 确保 "git commit " 的尾部空格被保留,触发空 token 显示选项列表。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: 补全菜单添加详情面板 + 清理调试日志 - 选中或悬停补全项时,右侧显示详情面板(类似 VS Code IntelliSense) - 显示完整命令名、来源类型标签(Option/Subcommand/History 等) - 显示完整的描述文本(不再截断) - source 标记移到左侧,与描述分离,更易读 - 悬停和键盘选中都能触发详情面板 - 向上展开时详情面板也正确对齐 - 清理所有临时调试 console.log Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: 清理全部调试日志 移除 autocomplete 模块中所有临时 console.log 调试语句, 仅保留 figSpecLoader 中的 console.warn 用于真实错误报告。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 三审问题修复 — 移除多余 prop、过滤子路径 spec、防路径遍历 1. 移除 Terminal.tsx 传给 AutocompletePopup 的多余 onClose prop 2. getCommandNameSuggestions 过滤含 / 的 spec 名(aws/s3 等不是直接命令) 3. figspec:load IPC handler 添加 .. 路径遍历检查 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 5 个问题全部修复 1. [P1] fuzzy 匹配建议不以 userInput 开头时,用 Ctrl+U 清行再写入完整命令, 避免 substring 截断产生损坏的命令行 2. [P2] Ghost addon 初始化改用 polling 等待 termRef,解决首次挂载时 termRef.current 为 null 导致 ghost text 永远不激活的问题 3. [P2] popup overlay 改为 pointer-events-none 透传,仅 popup 自身设 pointer-events: auto,不再阻止终端区域的鼠标交互 4. [P2] getCompletions 异步返回后重新 detectPrompt 校验输入是否已变, 丢弃过时的补全结果避免覆盖新状态 5. [P2] prompt 检测支持折行:当 line.isWrapped 时向上回溯查找 prompt 行, 拼接多行内容作为完整 userInput Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第二轮 3 个问题修复 1. [P2] broadcast 模式下 autocomplete 插入也触发广播 — onAcceptText 回调中调用 onBroadcastInputRef 通知其他 session 2. [P2] 支持无尾随空格的 prompt(如 cmd.exe C:\path>)— prompt 字符后允许直接是行尾,boundary 为 i+1 3. [P2] 光标移动 escape 序列(Left/Home/End)清除过时建议 — 不再静默忽略,改为 clearState() 清除 popup 和 ghost text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第三轮 3 个问题修复 1. [P2] commandBufferRef 处理 Ctrl+U 清行 — fuzzy 匹配发送 \x15 时 重置 buffer,避免 onCommandExecuted 记录错误的拼接命令 2. [P2] fetchVersionRef 递增计数器废弃过时异步结果 — clearState/Escape 关闭 popup 时 bump version,getCompletions 返回后检查 version 匹配, 防止已关闭的 popup 被旧请求重新打开 3. [P2] prompt scanLimit 从 80 提高到 200 — 支持包含 git branch、 kube context、长路径的 prompt,超过 80 列不再失效 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第四轮 3 个问题修复 1. [P1] 拒绝绝对路径 — figspec:load IPC handler 检查 commandName 不以 / 或 \ 开头,防止 path.join 丢弃前缀导致任意 JS 执行 2. [P1] cmd.exe prompt > 后不要求空格 — 对 > ❯ ➜ › 等 prompt 字符 不强制要求后跟空格,支持 C:\src>dir 格式 3. [P2] serial line mode 下 autocomplete 走 serialLineBufferRef — 在串口 lineMode 时不直接 writeToSession,而是缓冲到 line buffer 并处理 local echo,与正常按键输入行为一致 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第五轮 — translateToString(false) 保留尾部空格 translateToString(true) 会 trim 行尾空格,导致 cursorX 截取的 userInput 与实际行内容不一致。改为 translateToString(false) 保留 原始空格,确保 "git commit " 的尾部空格被正确保留用于触发选项补全。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: 设置页添加自动补全开关(启用/Ghost Text/弹出菜单) 在终端设置页末尾新增「自动补全」区域,包含三个开关: - 启用自动补全:总开关 - 行内建议(Ghost Text):光标后灰色建议文本 - 弹出菜单:浮动补全列表 子开关在总开关关闭时 disabled。中英文 i18n 翻译齐全。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第六轮 3 个问题修复 1. [P1] 光标不在行尾时禁止补全 — 检测 cursorX 后方是否有字符, 有则 clearState 不显示建议,避免 mid-line 插入导致文本重复 2. [P2] Enter 录入历史改为先尝试实时 detectPrompt,失败则 fallback 到 lastPromptRef 缓存,应对高延迟 SSH 下 buffer 未回显的情况 3. [P2] fuzzy 替换在 Windows host 上用退格清行而非 Ctrl+U — cmd.exe/PowerShell 不支持 Ctrl+U,改为发送 \b 退格序列 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第七轮 — commandBuffer 退格处理 + 接受后历史记录 1. [P2] commandBufferRef 处理 \b 退格 — Windows fuzzy 替换用退格 清行时正确移除 buffer 末尾字符,避免记录拼接错误的命令 2. [P3] lastAcceptedCommandRef 追踪接受的补全文本 — Tab/→ 接受后 立即 Enter 时用追踪值录入历史,不依赖可能未回显的 buffer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第八轮 — 历史记录准确性 + 设置同步 1. [P2] 用户继续编辑后清除 lastAcceptedCommandRef — Tab 接受 "git status" 后追加 " --short" 再 Enter 时记录完整编辑后的命令 2. [P2] Ghost text →/Tab 接受路径也设置 lastAcceptedCommandRef — 确保所有接受路径在快速 Enter 时都能准确记录命令 3. [P2] autocomplete 设置加入 SYNCABLE_TERMINAL_KEYS — 跨设备同步时保留自动补全偏好 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第九轮 — REPL 误识别 + 本地终端 OS 检测 1. [P1] local terminal 的 hostOs 改用 navigator.platform 检测实际 OS, 避免 Windows 上 fallback 到 "linux" 导致 Ctrl+U 清行失败 2. [P2] 回退 > 无条件接受改动,恢复要求 > 后跟空格或行尾 — 避免 python >>>、mysql>、sqlite> 等 REPL 被误识别为 shell prompt 3. 新增 REPL NON_PROMPT_PATTERNS:>>>(python)和 word>(mysql/redis) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第十轮 4 个问题修复 1. [P1] cmd.exe prompt C:\path> — 对 > 特判:前面是 \ 或 / 时允许无空格, 避免误匹配 REPL(python>>>、mysql>)的同时支持 Windows cmd prompt 2. [P2] serial lineMode autocomplete 不再 early return — fall through 到 共享的 commandBuffer/broadcast 更新逻辑 3. [P2] serial 字符模式 + localEcho 时 autocomplete 插入文本也本地回显 4. [P3] 运行时关闭 autocomplete 时调用 clearState() 清除已显示的 popup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第十一轮 — option args、PS2 误识别、bridge 缓存 1. [P2] resolveSpecContext 返回 option 的 args — 当光标在 option 参数 位置时(如 git archive --format |),返回该 option 的 args 而非 subcommand 的 args,使 tar/zip 等枚举值能正确补全 2. [P2] 排除 bare > 作为 shell prompt — bash PS2 续行提示 > 加入 NON_PROMPT_PATTERNS,避免在多行命令续行和 REPL 中误触发补全 3. [P3] bridge 不存在时不缓存 null — preload 时 bridge 可能未就绪, 缓存 null 会永久禁用该命令的 spec 补全 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第十二轮 — prompt 检测取最后一个分隔符 Starship/Powerlevel10k 等 prompt 包含多个 prompt 字符 (如 ➜ repo git:(main) $),之前在第一个 ➜ 就停了, 把后续 prompt 文本当成用户输入。 改为收集所有候选 prompt 边界,返回最后一个。确保 "➜ repo git:(main) $ ls" 中 userInput 正确为 "ls"。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Codex review 第十三轮 — prompt 搜索范围限制 + cmd.exe 路径 1. [P2] prompt 扫描限制在行前 60% — 避免 "echo foo > bar" 中的 重定向符 > 被当作 prompt 结束(prompt 不会出现在行尾部分) 2. [P3] cmd.exe 路径检测扩展 — 除了 \ / 前缀,也检测行首是否有 驱动器号 (X:) 模式,支持 C:\Users\me> 等标准 Windows prompt P1 (高延迟 SSH buffer 滞后) 和 P2 (Enter 时 stale prompt) 属于 prompt 检测方案的固有局限,根本解决需要 OSC 133 Shell Integration, 不在本 PR 范围内。已有 lastAcceptedCommandRef fallback 缓解。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
fa29515095 |
feat: SFTP 全局书签支持 (#529) (#530)
* feat: add global SFTP bookmarks shared across all hosts (#529) - Add global bookmark support with separate localStorage storage - Global bookmarks appear on all hosts with a globe icon indicator - "+Global" button in bookmark popover to save path as global - Global bookmarks sorted before host-specific bookmarks - Improve SFTP error display: use Unplug icon, refined styling, auto-expand connection logs on error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: toggle bookmark correctly removes global-only bookmarks When a path is only globally bookmarked, the toggle button now removes the global bookmark instead of creating a duplicate host one. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
39a398aa2b |
SFTP 右键菜单添加「复制文件路径」功能 (#514)
* feat(sftp): add "Copy file path" to right-click context menu (#507) Add a context menu item that copies the full remote file/directory path to clipboard using navigator.clipboard.writeText(). Works for both files and directories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 使用 joinPath 构建复制路径,修复 Windows 路径分隔符问题 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: joinPath 去除 Unix 路径尾部多余斜杠 避免 currentPath 带 trailing slash 时产生双斜杠路径(如 /var/log//syslog)。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
0b7c52523e |
feat: 终端沉浸模式 (#517)
* feat: add terminal immersive mode When enabled, the UI chrome (tab bar, sidebar, status bar) adapts its colors to match the active terminal's theme, creating a visually cohesive experience. Colors are derived from the terminal theme's hex values and converted to HSL for CSS custom property overrides. - Add useImmersiveMode hook with hex-to-HSL conversion and token derivation - Add reapplyCurrentTheme to useSettingsState for restoring original theme - Integrate with App.tsx to resolve active terminal's effective theme - Add immersive mode toggle in Appearance settings with i18n (en/zh-CN) - Add CSS transition class for smooth 300ms color changes - Support cross-window sync via IPC for Settings window toggle - Handle per-host theme overrides and workspace focused sessions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 沉浸模式多项改进与 bug 修复 - 修复 primaryForeground 硬编码白色导致浅色 cursor 对比度不足 - 修复 SettingsPage 直接导入 infrastructure 层违反架构约束 - 修复 TerminalSession 类型未导入导致 TS 编译错误 - 修复 TopTabs memo 缺少 logViews 导致 logView 变化不触发重渲染 - 重构 useImmersiveMode 为纯 effect hook,状态由 useSettingsState 统一管理 - Workspace 多终端主题不一致时禁用沉浸模式 - 排除 logView tab 误触发沉浸模式 - 沉浸模式下禁用 dark/light 切换按钮 - Agent 图标使用 CSS mask 跟随文字颜色 - Agent 下拉菜单 overflow-hidden 修复 hover 溢出 - 退出沉浸模式使用 overlay 淡出避免闪烁 - immersive-transition class 仅在沉浸实际生效时添加 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: 沉浸模式默认开启 新用户默认启用沉浸模式,已有设置的用户不受影响。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * perf: 沉浸模式主题切换性能优化 - 启动时预计算所有内置主题的 CSS 字符串,切换时 O(1) 查表 - 自定义主题懒计算并缓存,后续切换同样 O(1) - useLayoutEffect 替代 useEffect,paint 前完成避免闪烁 - 跳过无效的 dark/light class 切换 - apply 和 restore 逻辑拆分为独立 effect - 去掉主题列表 hover 渐变动画 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复 Codex review 提出的三个问题 - [P1] base UI theme 变化时不再覆盖沉浸模式的 dark/light class - [P2] fingerprint 加入 theme.type,检测自定义主题 dark↔light 编辑 - [P2] 沉浸模式设置接入 sync pipeline (collect/apply/rehydrate) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: focus 模式 workspace 支持沉浸 + settingsVersion 加入 immersiveMode - focus 模式 workspace 使用 focusedSessionId 的主题,不再要求所有 session 一致 - settingsVersion 加入 immersiveMode 依赖,确保 auto-sync 能检测到变化 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 沉浸模式 sync 一致性修复 - 初始化时将默认值写入 localStorage,确保 collectSyncableSettings 能收集到 - rehydrateAllFromStorage 后通过 IPC 广播给其他窗口 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: focus 模式关闭 focused session 后 fallback 到剩余 session 的主题 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 沉浸模式加入 storage event 跨窗口同步 将 immersiveMode 加入 settingsSnapshotRef 和 handleStorageChange, 确保 web/preview 场景下多窗口间沉浸模式状态同步。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 沉浸模式同步 Electron 原生窗口背景色 切换沉浸主题时同步调用 setTheme/setBackgroundColor, 使 Windows 上的窗口边框颜色与沉浸主题一致。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
b7082ab198 |
feat: add native file picker for local key file selection
Replace the manual-only text input with a file picker button that opens the system file dialog (showOpenDialog with showHiddenFiles enabled so ~/.ssh/ keys are visible). Users can still type a path manually or use the browse button. Changes: - electron/main.cjs: add netcatty:selectFile IPC handler - electron/preload.cjs: expose selectFile on bridge - global.d.ts: add selectFile type - HostDetailsPanel.tsx: add FolderOpen browse button next to path input Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
9369495e22 |
feat: add local key file path UI in host editor
Add "Local Key File" option in the host credential type selector. Users can specify local SSH key file paths (e.g. ~/.ssh/id_ed25519) as an alternative to selecting a key from the vault. This is the primary UI for keys imported via SSH config's IdentityFile directive. UI behavior: - Credential selector now shows three options: Key, Certificate, Local Key File - Local key file paths are displayed as a list with delete buttons - Text input with Enter/Add support for adding new paths - Selecting a vault key clears local key paths (and vice versa) - Paths are stored as host.identityFilePaths and resolved at connection time Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
196b1f8dbb |
feat: add terminal smooth scrolling setting (#471)
- Add smoothScrolling boolean to TerminalSettings (default: true) - Wire setting to xterm.js smoothScrollDuration (120ms when on, 0 when off) - Add toggle in terminal settings UI - Include in sync payload and i18n strings (en, zh-CN) Inspired by #467 (@crawt). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
bc6c0a2ef6 |
feat: add crash log capture and viewer in Settings > System
Capture main-process errors (uncaughtException, unhandledRejection, render-process-gone) to JSONL log files in userData/crash-logs/ with 30-day auto-rotation. Users can view, expand, and clear crash logs from Settings > System to help diagnose issues like #452. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a40e2f1ca7 |
fix: add i18n for transfer preparing state
Some checks failed
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
Add 'sftp.transfer.preparing' key to en.ts and zh-CN.ts so the indeterminate transfer state shows localized text instead of the raw i18n key. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
5b52413d97 | Add manual Linux distro override for hosts | ||
|
|
874a2b19df | Polish terminal connection dialog layout | ||
|
|
a9c862fe96 | Refine terminal connection dialog visuals | ||
|
|
cbd53ed2a3 | Add dismiss option for disconnected terminal dialog | ||
|
|
c2b94ea3bd |
fix: respect global terminal appearance settings (#429)
* fix: respect global terminal appearance settings * feat: add reset to global terminal appearance * fix: preserve legacy host appearance overrides * fix: show legacy appearance reset controls * refactor: reorder terminal global reset actions * refactor: present global theme as theme option * refactor: present global font as font option |
||
|
|
a0dce5d4a6 |
feat: support downloading SFTP folders from the new view (#427)
* feat: support SFTP folder downloads in the new view * refactor: remove unused legacy SFTP modal * fix: use directory picker for SFTP folder downloads * fix: wire folder downloads through SFTP side panel * fix: pre-scan SFTP folders for stable download progress * feat: show hybrid progress for SFTP folder downloads * feat: parallelize SFTP folder downloads * feat: adapt SFTP folder download concurrency by file size * feat: pool isolated channels for fast SFTP downloads * fix: address SFTP download review findings * fix: wait for in-flight fast download channels * fix: unblock fast channel waiters on cancel |
||
|
|
dcaf25ae57 |
feat: inline approval gate for tool execution (#423)
* feat: inline approval gate for tool execution
Replace SDK-level needsApproval with Promise-based approval gate inside
tool execute functions. The SDK stream stays alive while the UI shows
inline approve/reject buttons on ToolCall blocks.
Changes:
- Add approvalGate.ts: Promise-based approval system with event listeners
- tools.ts: requestApproval() inside execute for confirm mode
- tool-call.tsx: inline approval buttons and keyboard shortcuts
- ChatMessageList.tsx: subscribe to approval events, render approval UI
- useAIChatStreaming.ts: remove old useToolApproval hook integration
- AIChatSidePanel.tsx: remove old approval hook, clean up unused destructuring
- systemPrompt.ts: update confirm mode to not ask for text confirmation
- preload.cjs: filter pager env var prefixes from terminal display
- mcpServerBridge.cjs: add approval gate for ACP/MCP write operations
- aiBridge.cjs: wire IPC for MCP approval response and main window getter
- preload.cjs: add onMcpApprovalRequest/respondMcpApproval APIs
* fix: scope approval gate by chatSessionId and replay for late subscribers
Address Codex PR review comments:
- Add chatSessionId to ApprovalRequest for session isolation
- Scope clearAllPendingApprovals(chatSessionId?) to only clear
approvals belonging to the target session
- Add replayPendingApprovals() so late-mounting ChatMessageList
picks up approvals that fired while unmounted
- Scope MCP clearPendingApprovals in aiBridge cancel handler to
effectiveChatSessionId instead of clearing all
- Pass chatSessionId through MCP approval IPC flow
* chore: remove old approval flow code
- Delete useToolApproval.ts (unused hook)
- Delete InlineApprovalCard.tsx (replaced by ToolCall inline buttons)
- Remove stale comments referencing old hook in AIChatSidePanel
- Remove unused ai.chat.toolApprovalTitle i18n key from en/zh-CN
* fix: session-scoped approval gate and MCP replay survival
- handleStop passes activeSessionId to clearAllPendingApprovals
- setupMcpApprovalBridge stores MCP approvals in pendingApprovals map
so they survive ChatMessageList unmount/remount cycles
- ChatMessageList accepts activeSessionId prop and filters standalone
MCP approval blocks to the current session only
- AIChatSidePanel passes activeSessionId to ChatMessageList
* fix: filter PTY exec marker echoes and exit code lines from terminal
Extend filterMcpMarkers in preload.cjs to strip all shell-visible
artifacts from AI command execution:
- Echoed printf start marker: printf '%s\n' '__NCMCP_..._S'
- Echoed exit code restoration: (exit $__nc)
- PowerShell: Write-Output, $global:LASTEXITCODE, $__nc assignment
- Fish: set __nc $status
- Cmd: echo __NCMCP_...
- Widen guard to also trigger on __nc and PAGER=cat strings
* fix: scope SDK approvals, deny MCP on no renderer, fix memo comparator
- createCattyTools accepts chatSessionId and passes it to
requestApproval so SDK approvals can be matched by
clearAllPendingApprovals(activeSessionId) on stop
- useAIChatStreaming passes sessionId to createCattyTools
- mcpServerBridge: deny (resolve false) when no renderer window is
available instead of auto-approving, preserving confirm mode safety
- ChatMessageList: add activeSessionId to React.memo comparator so
switching sessions triggers re-render for correct MCP approval filter
* fix: MCP listener lifecycle, approval timeout, and UI sync on stop
- Move setupMcpApprovalBridge from ChatMessageList to AIChatSidePanel
so the IPC listener survives tab/panel switches
- Add 5-minute auto-deny timeout to requestApproval to prevent
indefinite isStreaming hangs when user walks away
- Add onApprovalCleared listener system: clearAllPendingApprovals now
notifies UI subscribers so ChatMessageList removes stale cards
- ChatMessageList subscribes to onApprovalCleared to sync local state
* fix: main-process approval timeout and full tool args in payload
- Add 5-minute auto-deny timeout to requestApprovalFromRenderer
matching the renderer-side requestApproval behavior
- Forward all tool params (excluding chatSessionId) to approval UI
instead of cherry-picking command/input/path, so sftpRename
oldPath/newPath and other tool-specific args are visible
* fix: move MCP bridge to TerminalLayer, narrow terminal filter guard
- Move setupMcpApprovalBridge from AIChatSidePanel to TerminalLayer
so the IPC listener stays alive regardless of side panel tab.
AIChatSidePanel only mounts when activeSidePanelTab==='ai'.
- Narrow preload.cjs filter guard back to __NCMCP_ only, preventing
false-positive stripping of user scripts containing __nc or PAGER=cat
* fix: eliminate PTY wrapper echo leakage and duplicate prompts
- Posix wrapper now emits 2 lines instead of 4: start marker + command
on line 1 (joined with ;), end marker + exit on line 2. This
eliminates the duplicate prompt echo from the separate start marker.
- Rename __nc to __NCMCP_rc in all shell variants (posix/fish/powershell)
so every wrapper variable contains the __NCMCP_ prefix. The preload
guard `data.includes("__NCMCP_")` now reliably catches ALL wrapper
artifacts regardless of chunk boundaries.
- Update all filterMcpMarkers regex patterns to match the restructured
wrapper format and renamed variable.
* fix: sync main-process approval timeout with renderer UI cleanup
- When requestApprovalFromRenderer times out, send IPC event
netcatty:ai:mcp:approval-cleared to renderer so stale approval
cards are removed
- Add onMcpApprovalCleared preload bridge for the new IPC channel
- setupMcpApprovalBridge now subscribes to cleared events, removes
timed-out entries from pendingApprovals and notifies clearedListeners
so ChatMessageList drops the stale card
* fix: surface denied inline approvals as errors in UI
- Detect error or denial payloads ("error" string or "ok: false")
returned by tools when the user denies an execution
- Set isError: true on the tool-result message so the ToolCall UI
renders it as a failure (red/rejected) instead of a success (green)
|
||
|
|
fc546c2430 |
fix: add aria-labels to image preview controls for accessibility
Add localized aria-label to reset, zoom in, zoom out, and close buttons. Add i18n keys for common.reset, common.zoomIn, common.zoomOut in en and zh-CN locales. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
b17775307f |
fix: bundle claude-code-acp to prevent crash when binary is missing (#404)
* fix: bundle claude-code-acp to prevent crash when binary is missing (#400) When users select Claude Code in the AI module, the app spawns `claude-code-acp` via ACP. Previously only the `claude` CLI was checked during agent discovery, so if `claude-code-acp` was not on PATH the spawn would fail with ENOENT and crash the Electron main process. - Add `@zed-industries/claude-code-acp` as a bundled dependency - Add `resolveClaudeAcpBinaryPath()` that checks PATH first, then falls back to the npm-bundled binary (mirrors Codex pattern) - Use the resolver in both the primary and fallback ACP provider paths - Update agent discovery to detect agents via bundled ACP binary when the standalone CLI is not installed Closes #400 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add claude-code-acp and its deps to asarUnpack In packaged Electron builds, files inside app.asar cannot be executed by child_process.spawn. Add claude-code-acp and its runtime dependencies to asarUnpack so the binary is accessible in production. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: migrate from deprecated claude-code-acp to claude-agent-acp The @zed-industries/claude-code-acp package has been renamed to @zed-industries/claude-agent-acp (bin: claude-agent-acp). Update all references across the codebase: - package.json: replace dep with @zed-industries/claude-agent-acp@0.22.2 - electron-builder.config.cjs: update asarUnpack entries, remove stale deps (diff, minimatch) no longer needed by the new package - shellUtils.cjs: update binary name and require.resolve path - aiBridge.cjs: update acpCommand, ALLOWED_AGENT_COMMANDS, isClaudeAgent - settings types, i18n locales: update command references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
4e2089d7e2 |
feat: add option to auto-open sidebar on host connect (#401)
* feat: add option to auto-open sidebar on host connect Closes #396 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: only auto-open SFTP sidebar for SSH/Mosh connections Use allowlist (ssh, mosh) instead of blocklist so telnet and other non-SSH protocols don't trigger SFTP sidebar which would fail. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: support auto-open SFTP for Quick Connect / temporary sessions Build a minimal Host from session data when hostId is not in the vault, so Quick Connect sessions also trigger auto-open SFTP sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sync SFTP auto-open sidebar setting across windows via IPC Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip local terminals and preserve username for temp sessions - Don't fallback protocol to 'ssh' so local terminals are excluded - Include session.username in synthesized Host for Quick Connect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
835a1231a6 |
feat: add skip TLS verification option for self-hosted AI providers (#369)
* feat: add skip TLS verification option for AI providers Self-hosted AI endpoints (vLLM, text-generation-webui, etc.) often use self-signed TLS certificates which Node.js rejects by default, causing 502 Bad Gateway errors. Add a per-provider "Skip TLS certificate verification" checkbox that sets rejectUnauthorized=false on both streaming and non-streaming requests. Ref #294 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: surface real error message instead of generic 502 Bad Gateway - Pass the actual bridge error message in statusText so Vercel AI SDK shows the real cause (e.g. "HTTP is only allowed for localhost", "URL host is not in the allowed list", TLS errors) - Show real error details for 5xx provider errors instead of generic "The AI provider returned a server error" message Previously all connection-level errors were masked as "Bad Gateway" making it impossible for users to diagnose configuration issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: pass server error body details through to the user - Read HTTP error response body before resolving (was resolving before body was read, losing the error detail) - Parse OpenAI-compatible JSON error format to extract error.message - Return error Response with body+statusText for non-2xx instead of empty stream, so Vercel AI SDK shows the real server error - Now users see e.g. "502 model not loaded" instead of just "Bad Gateway" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: widen link modifier key dropdown to prevent text wrapping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert "fix: widen link modifier key dropdown to prevent text wrapping" This reverts commit 1f756863910d7450c6ffd8c373ef156e90adcce7. * fix: apply skipTLSVerify to model listing requests ModelSelector.aiFetch() didn't pass providerId, so the provider-level skipTLSVerify was not applied when refreshing/listing models. Add skipTLSVerify as a direct parameter alongside the provider lookup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: keep error detail in Response body, not statusText statusText only accepts single-line Latin-1 — multiline or non-ASCII error messages from self-hosted gateways would throw TypeError before the AI SDK could read them. Move detailed error to body instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: return JSON error body for AI SDK compatibility, fix FetchBridge type - Wrap error responses in OpenAI-compatible JSON format so Vercel AI SDK's failedResponseHandler extracts the message correctly instead of showing a blank error - Update FetchBridge type to match the expanded aiFetch parameter list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add ASCII statusText fallback for non-OpenAI SDK providers Anthropic/Google SDKs fall back to Response.statusText when they can't parse the error body. Add safe ASCII statusText alongside the JSON body. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
6727248924 |
feat: add web search & URL fetch tools for AI agent (#365)
* feat: add web search and URL fetch tools for AI agent Add web_search and url_fetch tools to Catty Agent, allowing the AI to search the internet for current information and fetch webpage content. - Support 5 search providers: Tavily, Exa, Bocha, Zhipu, SearXNG - Settings UI with provider selection, API key encryption, and config - web_search is conditional on config; url_fetch is always available - Both tools are read-only and work in all permission modes (incl. observer) - aiFetch skipHostCheck for AI tool requests to arbitrary URLs - System prompt guidelines for when to use search/fetch - i18n support (en + zh-CN) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings (SSRF, key exposure, state race) - P1: Restore SSRF protection when skipHostCheck is true — still block localhost, RFC1918, link-local, and cloud metadata endpoints; only skip the domain allowlist for public HTTPS hosts - P2: Move web search API key decryption to main process via dedicated IPC handler, matching the existing provider key security model - P2: Use configRef to avoid stale closure in async settings callbacks that could overwrite newer user changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address second review — DNS rebinding, url_fetch approval, maxResults - P1: url_fetch now requires approval in confirm mode (outbound GET is a side effect that could exfiltrate data via query strings) - P1: Add DNS resolution check when skipHostCheck is set — resolve hostname and reject if any IP is private/loopback/link-local, blocking DNS rebinding attacks against internal services - P2: Slice search results after provider call to enforce maxResults consistently (Zhipu and SearXNG ignore the limit parameter) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address third review — localhost/IPv6 SSRF, API key blur race - P1: Block localhost/loopback when skipHostCheck is enabled — restructure isAllowedFetchUrl to check private hosts first in the skipHostCheck path, preventing access to local services on allowlisted ports - P1: Handle IPv6 private ranges (fc00::/7, fe80::/10, ::ffff: mapped), strip brackets from URL.hostname, block [::1] and fd00:: addresses - P2: Guard handleApiKeyBlur against provider change during async encryption — skip stale write if provider switched while encrypting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address fourth review — main-process key isolation, SearXNG compat - P1: Replace aiWebSearchDecryptKey IPC with __WEB_SEARCH_KEY__ placeholder pattern — renderer never sees plaintext keys; main process replaces placeholder in headers before HTTP request, matching provider key flow - P1: Search API requests use normal allowlist path (not skipHostCheck), so SearXNG on localhost/HTTP/private networks works via aiSyncWebSearch; only url_fetch uses skipHostCheck for arbitrary public HTTPS URLs - P2: Remove needsApproval from url_fetch — treat as read-only like sftp_read_file, consistent with observer mode allowlist Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address fifth review — private LAN providers, maxResults default - P1: Allow private-IP hosts that are explicitly in the provider/search allowlist (e.g. https://192.168.x.x model providers or SearXNG) - P2: Remove .default(5) from web_search maxResults schema so the user's configured maxResults setting is used when the model omits the param Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address sixth review — HTTPS scope, config gate, redirects - P2: Scope HTTP exception to private/LAN IPs only — remote allowlisted hosts still require HTTPS to protect API keys in transit - P2: Gate web_search tool on complete config (API key for providers that require it, apiHost for SearXNG) to avoid advertising a broken tool - P2: Add redirect following (up to 5 hops) to aiFetch for url_fetch — handles 301/302/307 for short links, www canonicalization, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address seventh review — redirect SSRF, decrypt race, HTTPS-only - P1: Revalidate each redirect hop against SSRF guards (allowlist check + DNS resolution) before following, preventing open-redirect SSRF - P2: Add sequence counter to API key decryption effect — stale promise results from a previous provider are discarded on provider switch - P3: Restrict url_fetch to HTTPS-only URLs, matching the skipHostCheck policy that already rejects HTTP in the bridge Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address eighth review — OS resolver, allowlisted HTTP hosts - P1: Use dns.lookup (OS resolver) instead of dns.resolve4/6 for private IP checks — matches what http.request actually connects to, respects /etc/hosts, mDNS, and other local resolver sources - P2: Allow HTTP for any explicitly allowlisted host (not just literal private IPs), so self-hosted SearXNG at http://searxng.lan works Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address ninth review — HTTP scope, blur ordering, decrypt flag - P1: Narrow HTTP exception to web search apiHost only — AI provider endpoints remain HTTPS-only to protect credentials in transit - P2: Add blur sequence counter to prevent out-of-order encryption results from overwriting newer API key saves - P2: Reset isDecrypting flag when cancelling decrypt on provider switch, preventing permanently disabled API key input Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address tenth review — DNS pinning, prompt/tool alignment - P1: Pin validated DNS result to the HTTP request via custom lookup function, preventing TOCTOU/DNS-rebinding between validation and actual connection - P2: Extract isWebSearchReady() helper and use it consistently in both tool registration and system prompt, so the model isn't told web search is available when config is incomplete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address eleventh review — single DNS lookup, redirect pinning, CGNAT - P1: Combine DNS validation and pinning into a single lookup call, eliminating the TOCTOU window between hasPrivateResolution and pinnedLookup - P1: Pin DNS for redirect targets too — resolve/validate/pin in one step before following each redirect hop - P2: Add 100.64.0.0/10 (CGNAT) to private IP ranges for Tailscale and similar CGNAT-addressed internal services Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address twelfth review — apiHost validation, sync on enable - P2: Validate apiHost is a well-formed URL in isWebSearchReady(), preventing tool exposure when user enters a malformed host - P2: Add webSearchConfig.enabled to sync effect deps so the main process gets updated immediately when the toggle changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove DNS-level SSRF checks that break fakedns/proxy environments DNS resolution validation (dns.lookup + IP pinning) breaks in proxy environments where fakedns resolves all domains to LAN addresses. Revert to hostname-level checks only (blocking localhost, 127.0.0.1, metadata endpoints, etc.) which are sufficient without false positives. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve empty catch block lint warning Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
06a6a0ac12 |
feat: add 'prompt' mode for OSC-52 clipboard reads
Add a fourth option 'Write + Prompt on Read' that allows clipboard writes but shows a confirmation dialog before granting read access. This lets users benefit from remote copy (tmux/vim) while maintaining control over clipboard reads. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
5a1d279efd |
fix: add OSC-52 settings, UTF-8 support, and clipboard read
- Add osc52Clipboard setting (off/write-only/read-write), default write-only - Fix UTF-8 decoding: use TextDecoder instead of atob for non-ASCII content - Support clipboard read requests when mode is read-write - Add settings UI with Select dropdown and i18n (en + zh-CN) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |