mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-09 15:58:34 +00:00
1d91c8fbf4e22d48fe2ff0130784fadfe2062853
44 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
90b6e514ef | Address terminal side panel auto-open review feedback | ||
|
|
05231d3fba | Add terminal side panel auto-open setting | ||
|
|
0c8370477c | Unify AI permission auto mode (#1688) | ||
|
|
f191a9b449 |
fix terminal dynamic tab title settings
Add a global dynamic tab title mode, default to agent-only titles, remove the per-host disable control, and keep terminal UI refresh paths in sync with the setting. |
||
|
|
cff74b6e49 | polish terminal and settings UI | ||
|
|
b74b5a442a |
[codex] Add vault notes markdown workspace (#1558)
* Add vault notes markdown workspace * Improve notes tree drag interactions * Harden notes tree move behavior * Refine vault notes interactions * Address vault notes review feedback * Tighten notes tree review fixes |
||
|
|
982569532c |
Sync AI API keys across devices
Sync AI API keys through cloud sync while keeping device-local external tool paths local. Also preserves local AI keys in backups and stops sync if a local encrypted key cannot be read. |
||
|
|
05b713ab18 |
[codex] Add configurable middle-click terminal behavior (#1443)
* Add configurable middle-click terminal behavior * Fix middle-click terminal behavior edge cases |
||
|
|
4459aa4ef3 |
Add terminal zoom disable setting
Adds a Shortcuts setting to disable terminal zoom shortcuts. |
||
|
|
64a6986d01 |
Sync terminal selection AI preference (#1441)
Syncs the terminal selection Add to Conversation preference with AI settings so cloud sync, local backups, restores, and settings-only auto-sync detect the value. Follow-up for #1436. |
||
|
|
1ebcd017bd | Make terminal timestamps per-host | ||
|
|
36267717ac |
feat(terminal): add system manager side panel for processes, tmux, and Docker
Introduce workspace-aware System side panel with remote process/tmux/Docker management, terminal popup for interactive attach, capability warmup, review-hardened IPC, performance optimizations, toast action errors, and SSH channel recovery on reconnect. |
||
|
|
c0efc9d5c1 |
feat(ai): add quick messages with slash command picker (#691)
Closes #691 |
||
|
|
14fe1e3ecb |
feat(shortcuts): add option to skip pinned tabs for number keys
Fixes #1375 by letting Cmd/Ctrl+[1...9] target only work tabs when enabled, while keeping the existing Terminus-style default. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
037b85bd66 |
fix(ui): smooth work-tab chrome transitions and split-pane autocomplete
Replace immersive instant-switch with animated active chrome theme sync so top tabs match terminal sessions immediately on tab click, and clamp autocomplete popups to the active pane so they stay anchored to the cursor in split workspaces. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
4b5993cad6 | fix host sidebar behavior for editor tabs (#1348) | ||
|
|
b1f930a995 |
feat(sftp): 侧栏 SFTP 增加追随终端目录模式 (#1266) (#1310)
* feat(sftp): add follow terminal directory mode for sidebar (#1266) Add a toolbar toggle that keeps the side-panel SFTP browser synced with the linked SSH terminal cwd, inspired by MobaXterm's follow-folder behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sftp): probe pwd after commands when follow mode lacks OSC 7 Add a deferred getSessionPwd fallback after terminal commands when follow-terminal-cwd is enabled and the shell did not report OSC 7, and fix settings sync hook dependencies. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sftp): repair follow toggle UI and backend cwd sync fallback Fix SftpPaneView memo skipping follow prop updates, probe fresh pwd when OSC 7 is missing, and broaden linked terminal session resolution for sidebar follow mode. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(sftp): harden follow sync after review findings Reuse shouldFollowTerminalCwdNavigate in production path, re-read connection after async cwd probe, skip redundant navigate on toggle, and only probe pwd when the SFTP side panel is open. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
7d30b19421 | Harden timestamp edge cases | ||
|
|
e3f68e1a3f |
[codex] fix cloud sync master key refresh (#1175)
* fix cloud sync master key refresh * Protect first sync from default settings * Avoid recovery prompt for settings-only sync * Cancel stale syncs after master key changes |
||
|
|
53d3e05bb4 |
Per-agent provider switcher chip in the Catty Agent chat input (#1107)
* feat(ai): provider switcher chip in the Catty Agent chat input Catty Agent currently has no model chip in the chat input — modelPresets is empty for the catty agentId so `hasModelPicker` is false. The provider/model the chat ends up using comes from the global `activeProviderId` / `activeModelId`, which means switching providers requires opening Settings → AI → Providers. Closes the gap surfaced in the original feedback on #1101 (problem 2) and partially addresses #986 (per-context model switching) by going per-agent. State layer adds an `agentProviderMap` (`Record<agentId, providerId>`) alongside the existing `agentModelMap`. Both are written together when the user picks from the new dropdown; together they form a per-agent override that beats the global active provider/model when set. Cross- window sync mirrors the agentModelMap pattern. ChatInput grows a `providerSwitcher` prop carrying the enabled ProviderConfigs, the selected (providerId, modelId), and an onSelect callback. When supplied, the model chip switches from the Cpu glyph to the provider's ProviderIconBadge + `providerName · modelId`, and the popover renders a two-column layout — providers on the left (icon + name + default model caption), that provider's known model(s) on the right. ACP agents (Claude/Codex) are untouched because they plumb their provider through the CLI, not the Vercel AI SDK. AIChatSidePanel resolves `effectiveActiveProvider` / `effectiveActiveModelId` for the catty agent from the per-agent map, falling back to the global selection. handleSend now passes those through to sendToCattyAgent so a provider picked in one Catty session applies everywhere Catty runs. For v1, each provider's right column shows just its configured `defaultModel`. The two-level structure is in place to absorb richer listings (cached /models, manual additions) later without UI surgery — left a note in the popover code. Refs #1101, #986. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * polish(ai): flatten provider picker to a single list Two-level dropdown was theatre — each ProviderConfig exposes exactly one model (its `defaultModel`), so the right column ended up showing the same string already captioned on the left row. Picking a provider implicitly picks its model; one click is enough. Also drops the `p.enabled !== false` filter on the picker list. The user's expectation, confirmed by feedback on the first cut, is that the chat-input list mirrors what Settings → AI → Providers shows. The per-provider `enabled` toggle is an "active-ish" flag, not a visibility gate; hiding disabled providers in the picker silently made everything but the one currently active disappear, which is what the reviewer hit. Pares the popover down to a vertical list — provider icon + name + default-model caption (mono if set, italic "configure default model" hint if not) + a Check on the bound row. Removes the `hoveredProviderId` state along with the right-column rendering since it's no longer driving anything. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * polish(ai): tune provider icon sizes in the chat input Chip icon was sitting at sm (20px badge) and crowded the h-6 toolbar chip alongside the truncated label; popover rows used the same sm, so nothing differentiated the picker from the chip visually. Adds an xs size to ProviderIconBadge (16px badge, 10px glyph, 9px letter fallback) and uses it on the chip. Popover rows step up to md (32px badge, 16px glyph) and the row padding grows accordingly so the larger brand mark has room to breathe — the picker now reads like a list of choices, the chip reads like a status line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): keep per-agent override honest in model fallback + chip label Two codex P1/P2 findings on #1107: P1 — When Catty has a per-agent provider override but no per-agent model (agentModelMap['catty'] empty), cattyAgentModelId fell through to the global `activeModelId`. That id belongs to whichever provider was globally active, not the one Catty is now bound to, so e.g. overriding to DeepSeek without a defaultModel happily sent gpt-4o (global) to DeepSeek and produced a wrong-model error. The fallback is now: stored agent model → override provider's defaultModel → empty. Only the no-override path keeps the activeModelId tail; the no-provider send guard catches the empty case for everyone else. P2 — ChatInput's selectedSwitcherProvider used `?? providers[0]` so the chip always displayed *some* provider even when none was actually bound (selectedProviderId missing). The rest of the pipeline (send guard, agentProviderMap) treats that as "no provider", so the chip was lying. Dropped the fallback; when nothing is bound the chip now shows a generic Cpu glyph + "Select provider" label until the user picks one from the popover. Adds `ai.chat.selectProvider` in en / zh-CN / ru. Refs codex review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): purge per-agent binding when the bound provider is deleted Codex local review (round 2) flagged that a saved Catty model id could outlive its provider. The chain: - catty bound to provider A (DeepSeek): agentProviderMap['catty']='A', agentModelMap['catty']='deepseek-v4-flash' - user deletes provider A - cattyAgentProvider falls back to the global active provider (B, OpenAI) - cattyAgentModelId still returned the saved 'deepseek-v4-flash' - send dispatched the DeepSeek model id to OpenAI → wrong-model error Two layers of fix: 1. **Defensive resolution** (AIChatSidePanel) — cattyAgentModelId now looks at the override provider before trusting the stored model id. If the override is stale (provider deleted), the stored model id is treated as orphan and the resolution falls back to the global active selection just like cattyAgentProvider already does. 2. **Cleanup on remove** (useAIState.removeProvider) — when a provider is deleted, any agent whose agentProviderMap entry pointed at it gets both maps cleared (provider override + saved model). Same rationale: that model id is now meaningless against every other provider. Mirrors the existing activeProviderId cleanup. Adds a small agentProviderMapRef so removeProvider can snapshot which agents to clean up without relying on the captured-at-callback-create state (which would be stale). Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): include agentProviderMap in settings sync + block empty-model send Codex local review (round 3) found two issues: P2 — `agentProviderMap` was missing from the cross-device sync surface. Added it to the sync key list, the build path, the apply path, and the SyncPayload type alongside the existing `agentModelMap`. Sync tests in `syncPayload.test.ts` now cover the new field on both the build and apply sides. P2 — Provider rows with no `defaultModel` were still clickable in the chat-input picker, so selecting one would save a binding with empty model id; the send path then dispatched an empty model name and the SDK would surface a vague backend error. Two changes: 1. ChatInput disables the row (\`disabled\`, \`aria-disabled\`, dimmed styling, tooltip pointing the user to Settings) when the provider has no \`defaultModel\`. Click is suppressed. 2. AIChatSidePanel \`handleSend\` adds a model-required guard mirroring the existing no-provider guard — surfaces \`ai.chat.noProviderModel\` as an assistant message. Catches stale bindings (e.g. user edited the provider's \`defaultModel\` to empty after binding) before they reach the SDK. Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): trim whitespace-only model ids + reconcile orphan bindings on sync Codex local review (round 4) found two more leaks: P2 — Whitespace-only `defaultModel` passed the picker's `disabled` gate (which trims) but every other layer (cattyAgentModelId resolution, the send-guard `!sendActiveModelId` check, the SDK call) compared the raw string. A provider whose default model was " " could still reach the SDK and surface a vague backend error. Normalized: trim at the resolution boundary (catty model fallback chain) and trim before the send-guard's existence check. P2 — Sync apply did not reconcile per-agent bindings against the incoming provider set. A payload could change `providers` without shipping a fresh `agentProviderMap`, leaving local overrides pointing to provider ids the synced set no longer includes — the same ghost- binding bug that `removeProvider` already handles for explicit user deletes. Added `pruneOrphanPerAgentBindings()` that runs after every AI settings apply: it walks `agentProviderMap`, drops any entry whose provider id isn't in the current `providers` list, and clears the saved model id for those agents alongside it (mirroring removeProvider cleanup). A new test in `syncPayload.test.ts` exercises the ghost- binding case end-to-end. Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): notify open AI state of cross-device sync apply in the same window Codex local review (round 4) flagged that `applySyncPayload` writes straight to localStorage but `useAIState` only re-reads on browser `storage` events. Those events only fire in *other* windows — the window doing the apply (the one with the active chat panel) keeps showing pre-sync providers and per-agent bindings until reload. Concrete leak: cloud sync swaps in a new provider set, pruning runs, localStorage is correct, but the open Catty chip still references the ghost provider binding until the user reloads. Extracts the existing `AI_STATE_CHANGED_EVENT` constant + `emitAIStateChanged` helper out of `useAIState` and into a new `application/state/aiStateEvents.ts` so non-React call sites (sync apply, future IPC handlers) can fire it without pulling in the hook. After AI settings apply, `syncPayload.ts` walks every AI key it touched (including the agentProviderMap / agentModelMap that the reconcile step may have mutated even when the payload didn't ship them) and emits a same-window nudge. `useAIState`'s existing local listener routes unknown keys back through its storage handler, so each affected piece of React state rehydrates from localStorage. Adds a regression test in `syncPayload.test.ts` that asserts the expected `netcatty:ai-state-changed` keys are dispatched for the providers + per-agent map keys. Refs codex local review on #1107. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
67448cea65 |
feat(terminal): configurable startup-command delay + multi-line sequencing (#1096)
* feat(terminal): add global startupCommandDelayMs setting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(terminal): add startup-command line-split and delay-clamp helpers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(terminal): run multi-line startup commands in sequence with configurable delay Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(terminal): expose startup command delay in Terminal settings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(terminal): keep startup-command line content verbatim splitStartupCommandLines now only drops blank/whitespace-only lines and normalizes CRLF, but no longer trims each line's content. This keeps a single-line startup command byte-identical to what the user typed (e.g. a leading space for HISTCONTROL=ignorespace is preserved), while still supporting multi-line sequencing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sync): include startupCommandDelayMs in synced terminal settings Terminal settings sync via the SYNCABLE_TERMINAL_KEYS allowlist; the new startupCommandDelayMs preference was missing, so it wouldn't propagate across devices. Add it (it's a user preference like keepaliveInterval, not device-specific). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
1d50b2c4a1 |
feat(terminal): choose dark/light terminal theme when following app theme (#1094)
* feat(terminal): add per-mode follow-theme resolver and storage keys Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(terminal): persist per-mode follow-theme selections in settings state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(sync): include per-mode follow terminal themes in cloud sync Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * i18n(terminal): add per-mode follow-theme picker strings * feat(terminal): add type filter and auto option to theme picker * feat(terminal): pick dark/light terminal theme when following app theme Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(terminal): don't flag the auto sentinel as a missing theme in the picker The per-mode follow-theme pickers default to the 'auto' sentinel, which is not a real theme id, so ThemeList's deletedSelectedTheme check classified it as a deleted custom theme and rendered a spurious "Missing Theme" banner above the Auto entry on first open. Exclude TERMINAL_THEME_AUTO from that check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * i18n(terminal): align zh-CN dark/light wording with app convention Use 深色/浅色 (matching the theme picker section headers and global appearance settings) instead of 暗色/亮色 for the per-mode terminal theme labels, so the picker modal reads consistently. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(terminal): match follow-theme preview fallback to runtime resolution The per-mode preview memos fell back straight to TERMINAL_THEMES[0] when a selection resolved to a deleted theme, while the runtime currentTerminalTheme memo falls back to the manual terminalThemeId first. Mirror the runtime chain so the Settings preview matches the actual terminal for users with a non-default manual theme. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
bf1c95500a |
feat #826: optional Option+←/→ word jump on macOS (#1082)
* feat #826: optional Option+←/→ word jump on macOS Adds a Terminal → Keyboard toggle "Option+←/→ jumps by word" (off by default, synced). When on, a bare Option+Left/Right sends Meta-b / Meta-f instead of xterm's default ^[[1;3D / ^[[1;3C, so readline/zle moves by word without per-host bindkey setup (Termius-style). The key→sequence mapping is a tested pure function; the handler reads the setting live (no reconnect) and runs after kitty mode + autocomplete so it doesn't override them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix #826: gate Option+←/→ word jump to macOS The setting is syncable, so without a platform gate, enabling it on a Mac would also rewrite Alt+←/→ to Meta-b/f on synced Linux/Windows devices, breaking apps/shells that expect the default ^[[1;3D / ^[[1;3C. Pass isMacPlatform() into the mapping so it only applies on macOS; add a test for the non-macOS case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b30696c98b | Clean up dead code and duplicated helpers (#1001) | ||
|
|
88732040aa |
fix(terminal): separate prompt after unterminated command output (#987)
* fix(terminal): separate prompt after unterminated command output Add a display-layer prompt line break handler so recognized shell prompts move to the next visual line when the final command output line is not newline terminated. Also add a terminal setting to toggle the behavior, sync support, i18n copy, and focused tests for prompt insertion. * fix review issue * Fix prompt cache initialization * Serialize terminal output writes for prompt breaks * Keep terminal status lines ordered with output * Fix prompt arming without command callback * Keep prompt display breaks out of session logs * Avoid prompt breaks for output suffix matches --------- Co-authored-by: yuzifu <yuzifu@TB16PGen5.Info> Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com> |
||
|
|
92ecd84edf |
Fix #939: per-host SSH keepalive override + cloud-friendly defaults (#947)
* fix(ssh): per-host keepalive override + cloud-friendly defaults (#939, #581)
Issues #939 (cloud / Aliyun sessions silently freezing after 15-20 min idle
because no SSH keepalive packets are sent) and #581 (older routers like
NOKIA / ALCATEL being killed by ssh2 after a few unanswered keepalives) are
in direct tension at the global-setting level: cloud users want keepalive
ON, embedded-device users want it OFF, and any single global default hurts
the other group.
Resolves the conflict by moving keepalive to a per-host setting (mirroring
the existing `legacyAlgorithms` per-host pattern), with cloud-friendly
global defaults:
Domain:
- Host gains `keepaliveOverride?: boolean` + `keepaliveInterval?: number`
+ `keepaliveCountMax?: number`. When override is true, the host's
values are used; otherwise the global TerminalSettings values apply.
Per-field fallback so a host can override interval only or countMax only.
- TerminalSettings gains `keepaliveCountMax: number` so the second knob
(number of unanswered keepalives before declaring dead) is no longer
hardcoded at 3 in the bridge.
- DEFAULT_TERMINAL_SETTINGS: keepaliveInterval bumped from 0 to 30, and
keepaliveCountMax = 10. Cloud LBs / NAT tables stay populated; brief
network glitches don't trip the dead-connection check; an actually
dead session is detected within ~5 minutes. Existing users with 0
saved keep their value (no migration) — they were the #581 router
cohort and their setup still works untouched.
Plumbing:
- domain/host.ts adds resolveHostKeepalive(host, globalSettings) with
five unit tests covering both directions of the override flag and
per-field fallback.
- components/terminal/runtime/createTerminalSessionStarters.ts uses the
resolver when building startSSHSession options.
- electron/bridges/sshBridge.cjs reads keepaliveCountMax from options
(defaulting to 10) at both connection sites (direct + jump host) and
still routes interval=0 through to a fully disabled keepalive
(preserving #581's escape hatch).
UI:
- Settings → Terminal → Connection grows a second input next to the
existing interval: "Max unanswered keepalives".
- Host details panel gains a Keepalive section with a "Override global
keepalive" toggle that, when on, exposes per-host interval +
countMax inputs and an inline hint when interval = 0 (explaining
the implications). Same visual pattern as the existing Legacy
Algorithms section.
Sync:
- keepaliveCountMax added to SYNCABLE_TERMINAL_KEYS so the new global
field rides existing sync infrastructure. Per-host fields ride the
hosts array passthrough automatically (older clients receiving them
ignore unknown fields, per the existing lenient sync contract).
i18n: en + zh-CN strings for the new settings row, the host section
header, and the override toggle / inputs / disabled hint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): resolve keepalive per jump host, not just the final target
Addresses codex review on PR #947:
https://github.com/binaricat/Netcatty/pull/947#discussion_r3217027xxx
The first cut only resolved keepalive for the final target host and
forwarded a single interval/countMax pair across the whole start-SSH
call. connectThroughChain in sshBridge.cjs then applied that one pair
to every hop, so a chain like:
router (bastion, needs keepalive=0) → cloud target (needs 30s)
would either kill the router (with cloud-friendly defaults) or fail
to keep the target alive (with router-friendly 0). The per-host
override was effectively useless for bastion hosts.
Fix:
- NetcattyJumpHost gains optional keepaliveInterval / keepaliveCountMax.
- createTerminalSessionStarters runs resolveHostKeepalive() per
jumpHost when building the chain, so each hop carries its own
resolved pair.
- sshBridge.cjs's chain connector reads jump.keepaliveInterval /
jump.keepaliveCountMax for each hop, falling back to the call's
target-level options for backward compatibility with older
serializers that don't yet populate the per-hop fields.
The final target's keepalive path is unchanged — it still reads
options.keepaliveInterval / options.keepaliveCountMax that the
session starter resolves from the target host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): per-host keepalive for SFTP + port forwarding too
Follow-up to the maintainer review on PR #947 — terminal SSH was the
only path that honored per-host keepalive overrides. SFTP and port
forwarding share the same NetcattyJumpHost type but their builders
weren't resolving keepalive per-hop, and their bridges hardcoded the
old 10s/3 defaults. Net result: a router-as-bastion in a chain still
got killed when reached via the SFTP file panel or a port-forwarding
tunnel, even though the user had toggled per-host override.
Plumbing:
- useSftpHostCredentials / buildSftpHostCredentials: accept optional
terminalSettings; call resolveHostKeepalive() for the target and
each jump entry; emit keepaliveInterval / keepaliveCountMax in the
returned NetcattySSHOptions.
- useSftpConnections + useSftpState + SftpStateOptions thread the
setting down. SftpSidePanel passes the global terminalSettings prop
it already has from TerminalLayer.
- portForwardingService.startPortForward: accepts terminalSettings
as an 8th argument, resolves per-host (target + each jump), and
populates the bridge payload.
- usePortForwardingState.startTunnel and usePortForwardingAutoStart
forward the new parameter; App.tsx supplies terminalSettings (via
a ref in the once-on-launch auto-start effect so changing global
keepalive later doesn't re-fire it).
Bridges:
- sftpBridge.cjs target connect: now also reads keepaliveCountMax
from options (was hardcoded 3). 10s/3 stays as the bridge-level
fallback to preserve the #669 protection when the renderer hasn't
supplied a value.
- sftpBridge.cjs jump hop: reads jump.keepaliveInterval /
jump.keepaliveCountMax, then falls back to the target-call options
(matches the symmetric SSH bridge change).
- portForwardingBridge.cjs: reads keepaliveInterval /
keepaliveCountMax from the IPC payload; same 10s/3 fallback.
Types:
- NetcattyJumpHost already grew keepalive fields earlier; this
commit also adds them to PortForwardOptions so the IPC contract
is explicit.
End-to-end: a chain `[router-as-bastion, cloud-host]` with the
router host's keepaliveOverride=true / interval=0 now correctly
disables keepalive on the router hop for terminal SSH AND SFTP AND
port forwarding, while the cloud target still gets the resolved
30s/10 default for each path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ssh): honor explicit keepalive=0 in SFTP + port forwarding bridges
Addresses codex review on PR #947:
- https://github.com/binaricat/Netcatty/pull/947#discussion_r3217448xxx
- https://github.com/binaricat/Netcatty/pull/947#discussion_r3217449xxx
The previous follow-up commit (
|
||
|
|
684c094d40 |
Drop externalAgents from cloud sync (device-local config)
ExternalAgentConfig.command/acpCommand/args/env are OS- and machine-specific (binary paths, .exe suffixes, platform-dependent environment values). Pushing them to other devices either fails to resolve or silently runs the wrong thing. Stop collecting/applying STORAGE_KEY_AI_EXTERNAL_AGENTS and remove the field from the SyncPayload type. apply silently ignores the field on legacy snapshots that still carry it, so existing remote data is safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d84c2cc902 |
Preserve local AI apiKeys when applying synced settings
`collectSyncableSettings` strips device-bound encrypted apiKeys from provider entries and webSearchConfig before upload, but `applySyncableSettings` was writing them back wholesale, silently wiping local credentials whenever any other setting changed on a second device. Merge by id (providers) and by providerId (web search) so a synced payload only overrides the apiKey when it explicitly carries one. Also include `application/*.test.ts` in the npm test glob so the syncPayload tests added in this PR actually run in CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c9da2a5893 |
Sync AI/UI settings and fix multi-display settings window
Extend cloud sync to cover AI provider config, external agents, permission/tool modes, command policy, web search settings, workspace focus style, terminal follow-app theme, SFTP default view, and additional terminal options. Device-bound encrypted apiKey placeholders are stripped from providers and webSearchConfig before upload. Auto-sync now reacts to syncable localStorage changes via a new adapter-level event. Center the Settings window on the display of the window that opened it instead of always using the main window, fixing issue #920. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
72e305fb7a | Add reusable proxy profiles | ||
|
|
155463f77c | add reusable proxy profiles | ||
|
|
4dd2465388 | Keep known hosts local during sync (#863) | ||
|
|
c30d872852 |
fix(settings): guard customKeyBindings sync against echo loop (closes #818) (#821)
* fix(settings): guard customKeyBindings cross-window sync against echo loop (closes #818) customKeyBindings was the only synced setting whose two cross-window handlers (DOM storage event + IPC onSettingsChanged) called setCustomKeyBindings unconditionally. Every broadcast landed with a fresh parsed object reference, so React re-rendered and the persist effect re-broadcast, echoing across windows indefinitely. While the echoes carry the same content, a rapid second click from the user can arrive between the outbound broadcast and an older in-flight echo — the echo's setState then clobbers the latest click and the UI "bounces" from Disabled back to the original binding. This matches the report in #818 (disable and reset operations flicker between values when clicked in quick succession). Fix: mirror the equality guards used by every other synced field. Compare the incoming payload (stringified for objects) against the current value from settingsSnapshotRef, and skip setCustomKeyBindings when they match. Add customKeyBindings to settingsSnapshotRef so the IPC handler has access without pulling it into the effect's closure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(settings): stop shortcut sync bounce flicker * fix(settings): harden shortcut sync ordering --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a1648adf12 |
Add opt-in setting to preserve mouse selection across keystrokes (#755) (#763)
* Add opt-in setting to preserve mouse selection across keystrokes Closes #755. xterm.js hardcodes a "clear selection on user input" listener (SelectionService.ts: coreService.onUserInput → clearSelection) with no public option to disable. The user-reported workflow this breaks: select a path with the mouse, type a command prefix like `sz `, then middle-click-paste the still-live selection — but the very first keystroke wipes the selection, so there's nothing left to paste. Modern terminals (iTerm2, GNOME Terminal, Windows Terminal) preserve the selection across input by default. We expose this as an opt-in toggle for now since the visual semantics are a behavior change. Implementation is capture-and-restore via xterm.js public APIs (getSelectionPosition / select); xterm clears the selection synchronously, then a queueMicrotask reapplies it on the next tick. A ref (isRestoringSelectionRef) gates copy-on-select so the restore doesn't redundantly rewrite the clipboard and clobber whatever the user copied elsewhere in between. Defaults to false (opt-in); can flip to default-on later if reception is positive. Selection still clears on: - Mouse click in empty space (xterm's mouse-driven path is untouched) - Terminal scroll past the selected rows (existing buffer-trim logic) - Programmatic clearSelection() callers Files: - domain/models.ts — new field, default false - application/syncPayload.ts — added to SYNCABLE_TERMINAL_KEYS - components/terminal/runtime/createXTermRuntime.ts — capture in attachCustomKeyEventHandler, restore via queueMicrotask - components/Terminal.tsx — owns isRestoringSelectionRef, passes it through context, checks in copy-on-select listener - components/settings/tabs/SettingsTerminalTab.tsx — UI toggle - application/i18n/locales/{en,zh-CN}.ts — labels Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trim verbose i18n descriptions to match neighboring rows Both clearWipesScrollback and preserveSelectionOnInput descriptions were too long. Cut to one sentence each, matching the brevity of adjacent rows like Bracketed paste and OSC-52. Historical context and edge-case caveats belong in the changelog/PR, not the settings UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
484ac5f463 |
Honor CSI 3 J by default; add toggle to preserve scrollback on clear (#761)
* Honor CSI 3 J by default; add toggle to preserve scrollback on `clear` Default `clear` (ncurses ≥ 2013) emits CSI 2 J + CSI 3 J to wipe both visible screen and scrollback. PR #633 unconditionally intercepted CSI 3 J to keep history across `clear`, which broke POSIX semantics — users running standard `clear` could not wipe scrollback at all (#757). Restore the standard behavior as the default and expose a toggle for the iTerm2-style "preserve history" preference (matches what #622 asked for): - domain/models.ts: add `clearWipesScrollback: boolean` (default true) - createXTermRuntime.ts: CSI 3 J handler now reads the setting and only intercepts when the user opts out - SettingsTerminalTab.tsx + i18n: expose the toggle with a description explaining the tradeoff - The right-click "Clear Buffer" menu action keeps its independent semantics (always preserves scrollback) regardless of this setting, since it goes through `clearTerminalViewport`, not the CSI path Closes #757 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: include clearWipesScrollback in cloud-sync terminal keys Codex review on PR #761 caught that the new toggle was added to TerminalSettings but not to SYNCABLE_TERMINAL_KEYS, so it would never travel across devices via cloud sync — users disabling it on one device would silently get the default back on another after sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
db69d5ac39 |
[codex] Harden sync overwrite protection and add local restore history (#720)
* fix: harden sync overwrite recovery * refactor: separate backup retention settings * refactor: align backup retention controls * refactor: simplify backup retention card * fix: address PR #720 deep-review findings - Close the cross-window restore race by holding a time-bounded barrier in localStorage during every destructive apply; useAutoSync skips pushes while it's set, preventing a pre-restore snapshot from clobbering just-restored cloud data. - Round-trip startup three-way merges so merged-in local additions actually reach the cloud instead of living only on the device that ran the merge until the next edit. - Upgrade sync signatures from a 64-char ciphertext prefix to full SHA-256 (v3), closing the tail-mutation replay weakness. - Harden the vault-backup IPC: payload size cap, enum-validated reason, sanitized version strings, strict maxCount, concurrent-call mutex, monotonic createdAt to avoid same-ms ordering ties. - Extract the anchor-change decision into a pure module with unit tests covering no-anchor, resource-id drift, and signature mismatch paths. - Capture the protective backup from the pre-apply closure snapshot so it reflects what's being replaced rather than what was imported. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR #720 follow-up review findings Make protective backup abort-on-failure (was best-effort console.error), preserve nested syncedAt in fingerprint, use UTF-8 byte length for size guard, throw on conflict-inspect failure so stale uploads can't leak through, treat unreadable remote as changed, canonical-JSON signature meta, and hold the version stamp on transient backup failures so the retry path still fires. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address second-pass review findings on PR #720 - Hold version-change stamp when payload is non-meaningful (covers the startup vault-rehydrate race where a transient empty snapshot would permanently skip the upgrade backup). - readBackupRecord stat-checks before readFile so an oversized file in the backup dir cannot OOM the renderer on enumeration. - Reject maxBackups input outside 1..100 instead of silently clamping (matches the i18n error copy and the main-process sanitizer bound). - Wrap USE_LOCAL conflict-resolution push in withRestoreBarrier so a concurrent auto-sync in another window cannot interleave. - sha256Hex throws SyncSignatureUnavailableError on missing WebCrypto subtle; createSyncedFileSignature returns null, forcing the unreadable-remote → three-way-merge path instead of a weak length-only pseudo-signature. - Document that array order in normalizePayloadForHash is an invariant enforced by producers, not the hash function. - Drop three-way-merge completion logs from console.log to console.info. - Comment the implicit restore → store-listener refresh chain so future refactors don't silently break the UI reload path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address third-pass review findings on PR #720 Resolves I-3 through I-8 and related cleanup items identified in the deep review. Highlights: - replace setTimeout(0) post-merge round-trip with a direct syncAllProviders call using the already-computed merged payload, removing the React-commit race - resolve the empty-vault confirmation promise on unmount so a mid-dialog window teardown doesn't leak the resolver - retry the version-change backup as hosts/keys hydrate, instead of latching on the first (possibly empty) snapshot - heartbeat-refresh the cross-window restore barrier so long applies cannot expose a post-60s window to concurrent auto-sync - add a diagnostic warning when connected providers hold divergent bases (multi-account configurations) - surface a user-visible "Sync paused" toast when startup inspect fails, replacing the previous silent gate-open - tie-break backup list sort by id when createdAt collides - extract applyProtectedSyncPayload so the main and settings windows cannot drift on restore-barrier / protective-backup handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address deep-review findings on PR #720 Deep re-review surfaced six Important issues that survived the prior four review rounds. All are hardened here: - I1: fsync the protective backup file AND its directory before the rename completes, so a system crash between backup creation and the restore it guards cannot leave a torn/zero-length safety net. - I3: persist an apply-in-progress sentinel across the non-atomic localStorage writes in applySyncPayload. A crash mid-apply now surfaces on the next startup (toast + refuse auto-push) instead of silently pushing the half-applied state over an intact cloud copy. - I2: only open the auto-sync gate (remoteCheckDoneRef) when the startup inspect validated cleanly. Add a bounded exponential-backoff retry so a transient inspect failure self-heals instead of wedging auto-sync until restart. - I5: save the sync base BEFORE advancing the per-provider anchor inside uploadToProvider. A renderer crash between the two writes now degrades to "stale anchor forces re-inspect on next run," which re-merges against the fresh base — eliminating the silent base-drift window where a 3rd-device race could misclassify entries. - I6: main process broadcasts a vaultBackups:changed IPC event on every mutation; useLocalVaultBackups subscribes so protective backups created from the main window show up in the Settings backup list without manual refresh. - I4: update PR description + code comment to match the actual (safer) design: auto-sync gate opens on vault init, with hasMeaningfulSyncData + restore barrier preventing empty-push; the version-change backup is best-effort and retries as data hydrates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: serialize startup checkRemoteVersion and stabilize its deps Re-review flagged that checkRemoteVersion's useCallback depended on `config` — a fresh object literal from App.tsx on every render — so the retry effect restarted with attempt=0 on every vault edit and could spawn overlapping in-flight inspect+apply runs. Two concurrent commitRemoteInspection + onApplyPayload calls could race on the apply-in-progress sentinel around interleaved writes. Route `buildPayload`, `config.onApplyPayload`, and `config.startupReady` through refs so checkRemoteVersion's identity no longer churns with unrelated App state. Add an in-flight guard that returns early when a previous invocation is still awaiting the network, closing the same-window re-entry gap that withRestoreBarrier intentionally doesn't cover. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: release in-flight lock on no-connected-provider early return Third-pass review caught that `checkRemoteInFlightRef` was acquired before the `!connectedProvider` check, so that early return leaked the lock and every subsequent retry-timer tick silently no-op'd. Move the acquisition past the early return so the only path that takes the lock reaches the finally-release. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
f89afc0e05 |
feat: add setting to hide the standalone SFTP top tab
Adds a "Show SFTP tab" toggle in Settings → Appearance (under the Vault section) that controls visibility of the standalone SFTP view in the top tab bar. When disabled: - The SFTP tab is removed from the top tab strip. - The openSftp hotkey (Ctrl+Shift+O / ⌘⇧O) becomes a no-op. - If the user is currently on the SFTP tab, the active tab auto- switches to Vaults. The in-session SFTP side panel (opened from the terminal toolbar) is unaffected — that is the surface users keep when they hide the top-level tab. Setting persists via localStorage, syncs across windows, and is included in the cloud SyncPayload alongside the existing Vault visibility toggles (showRecentHosts, showOnlyUngroupedHostsInRoot). Default: on. Addresses the first ask in #690. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
3089cab88d | add vault root ungrouped host toggle | ||
|
|
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> |
||
|
|
aeacd913f5 |
feat: sync global SFTP bookmarks via cloud sync (#553)
* feat: sync global SFTP bookmarks via cloud sync Global SFTP path bookmarks were stored only in localStorage and not included in the cloud sync payload, so they could not be synced across devices. Add them to the sync settings, with auto-sync detection via a custom event and in-memory snapshot rehydration on import. Local bookmarks remain device-specific by design. Closes #548 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: deduplicate global SFTP bookmarks by path during merge When the same path is bookmarked independently on two devices, each generates a different random ID. The entity-array merge preserves both, creating duplicates. Add path-based deduplication after settings merge, following the same pattern used for known hosts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sync global bookmarks across renderer windows via storage event When cloud sync imports bookmarks in the Settings window, the main window's in-memory snapshot stays stale. Listen for cross-window storage events on the bookmark key to auto-rehydrate. 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> |
||
|
|
34f9d2a663 |
chore: 死代码清理与架构分层修复 (#524)
* chore: 移除死代码并修复架构分层违规 - 删除未使用的 ACP 模块 (infrastructure/ai/acp/) - 删除未使用的 AI 组件 (ExecutionPlan, PermissionDialog) - 将 syncPayload.ts 从 domain 移至 application 层,修复分层违规 - 移除未使用的导出 (useSecurityState, useProviderStatus, GitHubAuthState, getAgentCommandLabel, ImageAttachment, HotkeyActions) - 收窄 Electron bridge module.exports,移除未使用的导出函数 - 将仅内部使用的函数/类型取消导出 (isSupportedLocale, SyncDashboard) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: 二次审查清理 — 移除更多死代码和架构违规 - 移除未使用的 ConversationEmptyState 组件和类型 - 移除未使用的 PromptInputSelect 系列组件 (5 个导出) - 移除 global.d.ts 中残留的 SMBConfig 类型和 cloudSyncSmb* 方法声明 - 移除 useAutoSync.ts 中未使用的 toast 导入 (同时修复 application→components 反向依赖) - 清理因删除而产生的多余 import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: 消除直接 localStorage 访问,提取 safeSend 共享工具 localStorage 集中化: - 新增 storageKeys 常量: SIDE_PANEL_WIDTH, PF_RECONNECT_CANCEL, DEBUG_HOTKEYS, DEBUG_UPDATE_DEMO - TerminalLayer/SettingsApplicationTab/App.tsx/useUpdateCheck 改用 localStorageAdapter - CloudSyncManager 内部方法改用 localStorageAdapter - portForwardingService 改用 localStorageAdapter + 集中 key safeSend 去重: - 新增 electron/bridges/ipcUtils.cjs 共享模块 - sshBridge/sftpBridge/portForwardingBridge/sshAuthHelper/aiBridge 统一引用 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: 终审清理 — 移除未使用的 require 和废弃类型别名 - 移除 sftpBridge.cjs 中未使用的 require("node:net") - 移除 aiBridge.cjs 中未使用的 require("node:path") - 移除 types.ts 中已废弃的 ChatMessageImage 类型别名 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: 修复 ESLint 错误 — 组件不再直接导入 infrastructure - 新增 useStoredNumber hook,TerminalLayer 通过 hook 访问侧边栏宽度 - SettingsApplicationTab 的 isUpdateDemoMode 改为从 useUpdateCheck hook 传入 - 移除 useCloudSync.ts 中未使用的 CloudSyncManager 导入和 GitHubAuthState 接口 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: 提取 notification port,消除 application 层对 components 的依赖 将 toast 通知抽象为 application/notification.ts 端口, UI 层通过 setNotify 注入实现,useAutoSync 改用 notify 接口。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |