mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-09 15:58:34 +00:00
42b58efc5ce8b90e326331d52ae593e45461d33f
169 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e9e8c35178 | Add terminal and log timestamps | ||
|
|
73fd091b80 | Cover provider dropdown presentation | ||
|
|
5bb4052f3d | Keep preset model suggestions visible | ||
|
|
36e7e3cb7f | Keep add provider menu inside settings panel | ||
|
|
c9216b32ab | Add domestic AI provider presets | ||
|
|
bb67aa77f5 |
[codex] compact long Catty agent context (#1237)
* feat: compact long Catty agent context * fix: tighten context compaction review issues * fix: preserve tool context during compaction * fix: clear stale model metadata on key edits |
||
|
|
3fc56df111 | refactor(ai): migrate agent backends from ACP to official SDKs (claude/codex/copilot) (#1229) | ||
|
|
008890a688 |
feat(terminal): 自定义本地 Shell 支持启动参数 (#1221) (#1225)
* feat(terminal): 支持为自定义本地 Shell 配置启动参数 (#1221) 自定义本地 Shell 此前只能填可执行文件路径,无法指定启动参数, 导致接入 msys2 bash 时缺少 `--login -i`,shell 不经 profile 初始化、 环境变量缺失而无法使用。 - TerminalSettings 新增 localShellArgs(string[],默认 []),随设置自动迁移 - 新增 domain/shellArgs:引号感知的命令行分词/回显格式化 - resolveShellSetting 透传自定义参数;参数为空时回退到 bridge 默认参数, 命中已发现 shell(WSL/Git Bash)时忽略自定义参数,避免串味 - 自定义 Shell 弹窗新增「启动参数」输入,设置行展示完整命令(en/zh-CN/ru) 参数复用已有管线 session.localShellArgs → startLocalSession → pty.spawn, 不涉及云同步(与机器相关的 localShell 一致,均不同步)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): 修复自定义 Shell 参数的两处 review 问题 - 切换到默认/已发现 shell 时清空 localShellArgs:自定义参数仅对自定义 路径有意义,清空可避免在发现列表尚未加载(启动竞态)或所选 shell ID 在本机不可用时,旧参数被当作该 shell 的启动参数而泄漏导致启动失败 - formatShellArgs 对含双引号的参数改用单引号包裹,修复 `-c "..."` 这类参数重新打开弹窗保存时被破坏的往返问题 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): shellArgs 量化处理含两种引号的参数往返 采用单引号优先的引号策略:单引号内全字面(Windows 路径、双引号原样保留, 无需转义);仅当 token 自身含单引号时改用双引号包裹并转义 \ 与 "。 解析端仅在双引号区内将 \" / \\ 视为转义,其余反斜杠保持字面, 确保未加引号的 Windows 路径不被破坏。修复 `echo "it's ok"` 这类同时含 单双引号的参数在弹窗重新保存时被破坏的问题。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): 自定义 Shell 已选中后可重新打开编辑参数 自定义 shell 选中时下拉框值已是 __custom__,再次点选「自定义…」不会触发 onValueChange,导致无法重新打开弹窗修改参数/路径。改为把自定义 shell 概要 做成可点击的编辑入口(铅笔图标),点击即重新填充草稿并打开弹窗; 打开逻辑抽到 openCustomShellModal 复用。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): shellArgs 改用 POSIX 单引号方案,修复尾部反斜杠与空参数 - 解析:两种引号区内均全字面(双引号不再把 \" 当转义),保留 Windows 路径尾部反斜杠等手输入;引号外仅 \' 转义为字面单引号(支撑 '\'' 习语), 其余反斜杠保持字面,未加引号的 Windows 路径不受影响 - 格式化:统一单引号包裹(内容全字面),内嵌单引号用 POSIX '\'' 习语; 显式空参数输出为 '' 以免重新保存时被丢弃 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(terminal): customArgs take precedence over discovered shell defaults when user explicitly sets them --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
afe959835d | Add SSH debug log setting | ||
|
|
1fec5925eb | Refactor large modules and fix runtime errors (#1136) | ||
|
|
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> |
||
|
|
0c4de74c84 |
polish(ai): hint that the provider icon is clickable (#1106)
The icon badge next to the Display Name input opens the icon picker but had no visual cue — users had no reason to suspect it was clickable, and reviewer feedback flagged it as too hidden. Adds a primary-tinted ring on hover/focus plus a small pencil glyph overlaid on the bottom-right corner of the badge (also hover/focus gated). The badge itself doesn't change shape, so the layout stays stable; the affordance is purely additive. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2a4feea40f |
Customizable provider name, icon, and protocol style (#1105)
* feat(ai): customizable provider name, icon, and protocol style
Issue #1101 reported that the only way to wire DeepSeek through the
Catty Agent was to add an "Anthropic" provider with a DeepSeek base URL,
which (a) wasn't relabelable except on the "custom" providerId and
(b) implicitly conflated wire-protocol style with providerId, locking
"custom" to the OpenAI-compatible client.
ProviderConfig now carries three optional fields — `style`
(anthropic/openai/google), `iconId` (built-in brand glyph key), and
`iconDataUrl` (user upload). createModelFromConfig routes on the
resolved style first, falling back to providerId only for per-vendor
quirks (ollama's throwaway apiKey and openrouter's baseURL). Display
name becomes editable for every provider, not just custom.
The icon picker exposes a curated lobe-icons subset (MIT) covering the
common Anthropic/OpenAI-compatible third parties — DeepSeek, Moonshot,
Kimi, Qwen, Zhipu, Doubao, Mistral, Cohere, Grok, Perplexity, Groq,
Hugging Face — plus the existing six built-ins. Uploads are downsampled
to 64×64 WebP on a canvas so localStorage doesn't blow up. See
public/ai/providers/NOTICE.md for attribution.
Closes part of #1101 (problem 2). PR2 will reuse the new name/icon
plumbing in the Catty Agent chat input to surface a provider switcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* polish(ai): bigger labelled icon tiles and auto-fill display name
The first pass shipped the icon picker as an 8-column grid of bare
20px badges. Reviewer feedback: the icons are too small to read at a
glance, every preset should announce its brand name, and a second
click on a selected preset should let the user back out.
Switches the grid to auto-fill 120px tiles with a 32px icon plus a
truncated label so the picker reads like a brand list, not a sprite
sheet. Selecting a preset now also writes the brand's canonical
English display name into the Display Name field — the picker labels
keep their "/ 中文" suffix as a bilingual hint, but a new `name` field
on each catalog entry supplies a clean string for autofill (e.g.
"Qwen / 通义" → "Qwen"). Re-clicking the selected tile clears
iconId/iconDataUrl but leaves the typed name alone, so users who
already edited the name don't lose their edit on accidental toggle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* polish(ai): add explicit close button to icon picker
The picker only opened via the icon badge above the Display Name field,
which left no obvious affordance to collapse it once a preset was
chosen — users either had to scroll past or click back up to the icon.
Drops a ghost Close button in the bottom-right of the picker's action
row (next to Upload/Reset) so dismissing the panel is one click and
visible without hunting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai): wire model discovery to the resolved provider style
Codex review on #1105 flagged that ModelSelector still derives auth
headers from `providerId` (`x-api-key` only when providerId is the
literal "anthropic"). Now that ProviderConfig lets the user override
`style` independently, the form persists the override but the model
discovery call ignored it — so picking an Anthropic providerId pointed
at an OpenAI-compatible backend (or vice versa) would send the wrong
header and fail to list models even though chat routing already used
the override.
Extracts `buildModelDiscoveryHeaders(style, apiKey)` as a pure helper
in infrastructure/ai/. ModelSelector now resolves the protocol family
via `resolveProviderStyle` with the explicit `style` prop taking
precedence, then asks the helper for headers. ProviderConfigForm
passes the resolved style through. The `needsApiKey = providerId !==
"ollama"` shortcut stays as-is — that's a per-vendor concession, not a
style decision.
Refs codex review on #1105.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai): apply per-vendor URL fallbacks across every style override
Codex review on #1105 caught that the openrouter (and ollama) baseURL
fallback was sitting inside the `style === 'openai'` branch, so a user
who picked OpenRouter providerId + Anthropic/Google style with an
empty baseURL would skip the fallback entirely. The SDK then routed
to its own default endpoint (api.anthropic.com /
generativelanguage.googleapis.com) using the user's OpenRouter key —
silent misrouting plus auth failures.
Pulls the per-vendor quirks out into a new pure helper
`resolveProviderEndpoint(config, style, safeApiKey)`. The URL
fallback now fires for every style — the user picked openrouter for a
reason, even if they overrode the wire format. The ollama-only
`'ollama'` literal apiKey swap stays gated on `style === 'openai'`
because Anthropic/Google clients need a real key, not the throwaway
placeholder.
Refs codex review on #1105.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai): send x-goog-api-key for google-style model discovery
Codex review on #1105 flagged that buildModelDiscoveryHeaders bucketed
google-style providers with the OpenAI-compat default, so a model
refresh against any google-routed endpoint was sending `Authorization:
Bearer …`. That's the wrong auth header — Google Generative AI
rejects Bearer entirely and expects `x-goog-api-key` (or `?key=`).
The runtime chat path already uses `createGoogleGenerativeAI`, which
authenticates with the Google header family, so discovery was the
odd one out.
Adds an explicit `google` branch returning `{ "x-goog-api-key":
<apiKey> }` and updates the unit test. The default google providerId
never hits this path today (PROVIDER_PRESETS["google"] has no
modelsEndpoint), but the style override surface created in this PR
lets users compose providerId + style pairs where discovery does
fire — e.g. an openai/anthropic-style providerId pointed at a Google
endpoint via baseURL override.
Refs codex review on #1105.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai): pick discovery endpoint from resolved provider style
Codex review on #1105 pointed out the lopsided fix in
|
||
|
|
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> |
||
|
|
770b06a9ee |
fix(ai): make Claude Code agent diagnosable + configurable (auth) (#1095)
* feat(ai): add Claude auth-presence detection helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ai): surface actionable Claude auth errors and reap stuck agent processes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai): add pure helpers for Claude config dir + env editor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ai): let users set Claude config dir and env vars in settings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * polish(ai): harden Claude config env, de-dupe error text, label a11y - buildClaudeEnv: drop managed keys (CLAUDE_CONFIG_DIR/CLAUDE_CODE_EXECUTABLE) if a user types them into the free-text env editor, so they can't clobber the config-dir field or the discovered executable path (+ regression test). - bridge: only append error data fields not already shown as message/code, so the actionable error text doesn't echo the same code/message twice. - ClaudeCodeCard: associate the new config-dir/env labels with their inputs via htmlFor/id. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(ai): make the Claude auth & config section collapsible The optional "Authentication & config" section now has a collapsible header (chevron toggle). Collapsed by default to keep the card tidy, but auto-expanded when the user already has a config directory or env vars set so existing config isn't hidden. Local UI state, not persisted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): preserve raw env editor text; don't encourage plaintext secrets P1: the env textarea was bound to the persisted value, which is the parsed env re-serialized — so typing a key before its "=" was erased mid-entry (buildClaudeEnv drops lines without "="). Keep the raw typed text in local draft state and only resync from the persisted value on genuine external changes (not our own parse→serialize round-trip). P2: the env editor persists to localStorage in plaintext (no credential encryption). Stop suggesting ANTHROPIC_API_KEY in the placeholder and warn that values are stored in plaintext, steering credentials to the config directory (a `claude` login) — consistent with keeping Claude auth CLI/config-owned (#705). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): expand ~ in Claude config directory before passing to the agent CLAUDE_CONFIG_DIR is handed to the spawned agent as an env var, which is not shell-expanded — so "~/.claude" was treated as a literal "~" directory. Expand a leading ~ at consume time (normalizeAgentEnv + getClaudeConfigDir) rather than on save, so the stored value stays portable across machines (cloud sync) and each device expands to its own home. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai): recreate cached Claude provider when its env config changes Claude ACP providers are cached per chat session and reused unless one of the fingerprinted dimensions changes. authFingerprint was null for Claude, so editing the config directory / env vars in Settings didn't take effect on an already-running session. Fingerprint the Claude agent env so a config change invalidates the cached provider and the next turn respawns with it. 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> |
||
|
|
7bd5d6465a | fix claude system cli detection (#1033) | ||
|
|
3e8965f9a9 | Fix pr987 (#1010) | ||
|
|
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> |
||
|
|
67c5571df5 |
Fix #958: highlight IPv6 + allow editing built-in keyword rules (#962)
Two changes addressing both halves of #958: 1. IPv6 highlighting The built-in 'URL, IP & MAC' rule only shipped URL, IPv4 and MAC patterns, so compressed IPv6 addresses such as 2001:11:22:33::5 or fe80::d2dd:bff:fe79:f2bb were never highlighted. Add an IPv6 regex covering full and compressed forms (including ::1 and leading-/trailing- :: variants) and merge it into the same 'ip-mac' rule's patterns. The normalizer's existing "fill missing defaults" path means existing users pick this up on next start with no migration step. 2. Editable built-in rules Add an optional `customized` flag to KeywordHighlightRule. When false / absent, normalize re-syncs the rule's label/patterns with the shipped defaults (so future default-pattern upgrades reach users automatically). When true, normalize keeps the user's label/patterns/color/enabled verbatim, allowing built-ins like 'ip-mac' to be tailored. SettingsTerminalTab: - Pencil icon now appears on built-ins too. Editing one routes through the same dialog and flips `customized` on save. - The pattern field becomes a Textarea so multi-pattern built-ins (e.g. 'error' ships seven spellings) can all be edited in one go. - A per-rule "↺" reset icon appears on customized built-ins and restores the shipped label/patterns while preserving the user's color/enabled. - The footer's "Reset to default colors" button is broadened into "Reset built-ins to defaults", restoring every built-in to shipped label/patterns/color and clearing `customized`. Tests: New domain/keywordHighlight.test.ts (6 tests) covers IPv6 matches for both #958 examples plus loopback and full-form, IPv4/MAC still match, normalize migrates legacy non-customized 'ip-mac' to include IPv6, normalize preserves customized patterns, and normalize keeps user custom rules verbatim. Full suite: 808/0/3. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ea5320d94a |
Fix #954: unify Tooltip styling + replace native selects (#961)
* Fix #954: unify Tooltip styling + replace native selects Replace native HTML title= tooltips and native <select> dropdowns with the existing Radix-based Tooltip / Select components so they share the app's rounded styling, theme tokens and i18n pipeline. Adds a global TooltipProvider in AppWithProviders so every descendant Tooltip works without a per-file Provider wrapper. Scope (driven by the issue #954 examples and "全部都处理" follow-up): - TerminalLayer toolbar: Add Terminal / Split View / SFTP / Scripts / Theme / AI Chat / Move panel / Close panel. - TopTabs middle bar: quick switcher, more tabs, AI assistant, theme toggle, settings; window-control buttons (min/max/close), tray close and hotkey reset/disable have their native title dropped per the user's explicit opt-out ("可以不用Tooltip,直接全局禁用 原生title 属性"). - AI panels: AIChatSidePanel session history / new chat / delete, ConversationExport, AgentSelector, ChatInput attach / expand / permission, ModelSelector, ProviderCard, ai-elements/tool-call. - SFTP: SftpSidePanel header, SftpBreadcrumb, SftpFileRow, SftpPaneToolbar, SftpTabBar, SftpTransferQueue. - Settings: SettingsPage close, SettingsAppearanceTab theme/accent swatches, SettingsFileAssociationsTab edit/remove, SettingsSystemTab crash-log paths and global hotkey reset. - Host vault: HostDetailsPanel (clear / suggestions / show-password / key path / browse key), GroupDetailsPanel, KnownHostsManager, ConnectionLogsManager, KeychainManager, SyncStatusButton, CloudSyncSettings, LogView, QuickSwitcher, ScriptsSidePanel, Terminal status bar copy-host + broadcast/focus, ZmodemProgressIndicator. - Terminal subcomponents: HostKeywordHighlightPopover, TerminalComposeBar, TerminalConnectionDialog, TerminalSearchBar. - Editor: TextEditorPane (subtitle, search, wrap, promote-to-tab). - TrayPanel session rows and port-forwarding rows. Native <select> migrated to custom Select component: - SerialConnectModal (data bits, stop bits, parity, flow control) - SerialHostDetailsPanel (same four fields) - HostDetailsPanel backspace behavior - GroupDetailsPanel backspace behavior - SettingsTerminalTab local shell picker - terminal/ThemeSidePanel font weight Hardcoded English strings extracted to i18n. New keys for both en and zh-CN: terminal.layer.*, topTabs.*, ai.chat.* (sessionHistory, attach, collapse, expand, enableAgent), zmodem.*, settings.shortcuts. resetToDefault. Inline help text on SnippetsManager package-name input removed because the same hint is already shown in a visible <p> below the input. Existing per-file <TooltipProvider> wrappers (SnippetsManager, ScriptsSidePanel, SelectHostPanel, RuleCard, HostDetailsPanel proxy section) are left in place — they nest harmlessly under the global provider and stay self-sufficient for component tests. Tests: - tsc clean for changed files (pre-existing repo-wide errors unrelated to this PR). - All 802 tests pass (3 skipped pre-existing). - HostDetailsPanel.proxyProfile.test and TextEditorPane.test updated to wrap with TooltipProvider, matching the runtime context now needed by the migrated components. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix #954: wrap Settings + Tray windows with TooltipProvider Settings and the tray panel mount as separate Electron windows with their own React root in index.tsx, so they do not inherit the global TooltipProvider added under AppWithProviders. After the unified Tooltip migration, any settings tab that used a Tooltip (Appearance, Application, FileAssociations, System, Shortcuts, Terminal, AI ProviderCard, AI ModelSelector) — and TrayPanel — threw "Tooltip must be used within TooltipProvider" and rendered nothing. Wrap both branches with TooltipProvider at the same level as ToastProvider in index.tsx. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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 (
|
||
|
|
92dd898eb4 |
Fix #931: let users pick a CJK font + per-font smart pairing (#940)
* feat(fonts): add CJK font pairing composition module Introduces composeFontFamilyStack() which builds the xterm fontFamily CSS string at runtime from: - the user's primary Latin font - an explicit CJK font (TerminalSettings.fallbackFont) if set - otherwise a per-Latin-font recommended CJK pairing - a hardcoded system CJK fallback stack - a Nerd Font icon fallback stack - the universal monospace generic 14 unit tests cover composition order, deduplication, OS defaults, quoting, and recommendation override behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(fonts): expose raw Latin families and add CJK-coverage entries - TERMINAL_FONTS[].family no longer bakes in the CJK fallback stack; composition is deferred to runtime via composeFontFamilyStack(). - Drops withCjkFallback helper from this module and its caller in lib/localFonts.ts. - Adds 6 CJK-coverage primary fonts to the dropdown: Sarasa Mono SC/TC, Maple Mono CN, LXGW WenKai Mono, Microsoft YaHei UI, PingFang SC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(terminal): compose font-family stack with user-configurable CJK fallback resolvedFontFamily now passes through composeFontFamilyStack(), which prepends the user's TerminalSettings.fallbackFont (if set) ahead of the per-Latin-font recommended CJK pairing and the system fallback stack. The platform argument is derived from navigator.platform inside the useMemo, so the same Latin font may pair with PingFang SC on macOS and Microsoft YaHei UI on Windows out of the box. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(settings): add CJK font picker to terminal settings Adds a new "CJK font" select row right under the main font selector in the Terminal settings tab. Bound to TerminalSettings.fallbackFont (an already-existing-but-unused field), so this needs no schema or sync payload change. Default value "Auto" leaves fallbackFont empty, which lets the new per-Latin-font pairing in cjkFonts.ts pick a CJK font automatically. Selecting any explicit option (Sarasa Mono SC, PingFang SC, Microsoft YaHei UI, etc.) takes precedence over the per-font pairing. Includes en + zh-CN i18n strings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(sync): cover fallbackFont round-trip + legacy payload tolerance Four new test cases verify cloud-sync compatibility for the new CJK font setting: - buildSyncPayload includes fallbackFont when set - buildSyncPayload omits fallbackFont when unset - applySyncPayload writes incoming fallbackFont to TERM_SETTINGS - applySyncPayload from a legacy client (no fallbackFont) does NOT wipe the local value — critical for old-to-new upgrades Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fonts): add font availability detection (canvas + document.fonts API) Three-layer detection used by isFontInstalled(family): 1. Known @fontsource-bundled families (e.g. JetBrains Mono) always count as installed. 2. document.fonts.check() — picks up @font-face and system-loaded fonts. 3. Canvas width measurement against serif / sans-serif / monospace fallbacks; only counts if the target font produces a width that differs from ALL three generics for a probe string. detectInstalledWithContext is a pure function taking an injected measurement context, which keeps the canvas / DOM behind a seam and lets the logic be unit-tested without a browser. 11 tests cover quoted-family parsing, the three-generic-fallback rule, bundled short-circuit, and document.fonts.check fast-path. Results are cached per process; clearFontAvailabilityCache() invalidates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fonts): filter dropdowns to fonts actually installed on this machine Layer 3 of #931 added Sarasa Mono SC / Maple Mono CN / Microsoft YaHei UI / PingFang SC etc. to the terminal font dropdown, but users who don't have these installed would still see them and pick them — resulting in "I changed the font and nothing happened" confusion. This commit filters both dropdowns through isFontInstalled(): - TerminalFontSelect: drops any built-in or system-discovered font that detection can't render. If filtering would leave fewer than 4 fonts (detection misfire safety net), shows the full list. - TerminalCjkFontSelect: keeps the "Auto" sentinel always, drops concrete CJK choices that aren't present on this machine. Both selects always keep the currently-selected value visible — even when the underlying font is missing — so users can read and clear their setting without surprise. Also expands `npm test` globs to pick up infrastructure/config/*.test.ts and lib/*.test.ts, which previously matched no patterns and meant the new cjkFonts and fontAvailability suites were silently excluded from CI runs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): never recommend proportional CJK fonts for terminal use The previous PingFang SC / Microsoft YaHei UI / Hiragino Sans GB choices were proportional sans-serif fonts whose CJK glyphs aren't designed to fit a terminal's 2x cell grid — the rendered Chinese ended up visibly wider than its allocated cells, breaking grid alignment (reported on macOS with PingFang SC selected as the CJK font). Changes: - TerminalCjkFontSelect: drops PingFang SC / Microsoft YaHei UI / Hiragino Sans GB from the dropdown. Legacy explicit selections still surface as a synthetic "not recommended" option so users can see and re-pick. - CJK_SYSTEM_FALLBACK_FONTS: monospace-only list. Sarasa Mono SC/TC, Maple Mono CN, LXGW WenKai Mono, Noto Sans Mono CJK SC, Source Han Mono SC, NSimSun, SimSun. Proportional fonts removed. - PER_FONT_CJK_PAIRING: every entry now points at a true monospace CJK font. Cascadia / Consolas / Menlo etc. all recommend Sarasa Mono SC, which the next commit bundles via @font-face. - getDefaultCjkFallback: Windows = SimSun (always installed, monospace); macOS = Sarasa Mono SC (will be bundled); Linux = Noto Sans Mono CJK SC. A regression test enforces that no per-OS default is a known proportional font. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(fonts): bundle Sarasa Mono SC as the universal CJK monospace Previous commit removed proportional CJK fonts (PingFang SC, etc.) from the picker and switched per-OS defaults to true monospace, but macOS ships NO system-installed monospace CJK font — leaving macOS users with a broken default unless they manually install Sarasa or similar. This commit closes that gap by bundling Sarasa Mono SC as an @font-face webfont, so the recommended pairings and macOS default "just work" out of the box. Details: - public/fonts/SarasaMonoSC-Regular.woff2 (~4.8 MB): subsetted from be5invis/Sarasa-Gothic v1.0.37 SarasaMonoSC-Regular.ttf (24 MB). Covers ASCII, Latin-1, common punctuation/symbols, CJK Unified Ideographs main block, Hiragana/Katakana, halfwidth/fullwidth, box-drawing — the everyday-Chinese coverage that matters for a terminal. Rare CJK Ext-A/B/historical chars fall through to the system fallback stack. - public/fonts/SarasaMono-LICENSE.txt: OFL-1.1 verbatim, required by the license. - index.css: @font-face declaration with font-display: swap so the user doesn't see a flash of nothing while the woff2 loads. - KNOWN_BUNDLED_FAMILIES: "Sarasa Mono SC" added so the dropdown availability filter doesn't hide it. Installer impact: ~+4.8 MB (vs current ~100-200 MB Electron baseline). The font replaces what would otherwise have been "Chinese chars look broken in the terminal" for every macOS user without a manually installed CJK monospace font. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): use Local Font Access API as the authoritative install check document.fonts.check() turned out to be unreliable as an installed-font signal in Chromium — it returns true for any syntactically-valid family name regardless of whether the font is actually installed, as a deliberate fingerprinting-mitigation. The previous detector took it as a positive signal and ended up keeping uninstalled fonts in the dropdown (reported by a macOS user seeing dozens of fonts they don't have). This commit pivots the detection chain: - lib/localFonts.ts: getAllSystemFontFamilies() exposes the unfiltered set of installed family names from queryLocalFonts(), reusing the same underlying call as getMonospaceFonts() via a shared cache. - lib/fontAvailability.ts: drops the document.fonts.check fast-path. Adds setSystemFamilies() / hasAuthoritativeData(). When the set has been populated, isFontInstalled answers from membership lookup directly — no canvas guessing. Canvas remains as a fallback for environments where the Local Font Access API is unavailable or permission is denied. - application/state/fontStore.ts: during initialize(), runs the monospace-only query and the full-system-families query together, then pipes the result into fontAvailability. - TerminalFontSelect: with authoritative data, drops the "if filtered list is suspiciously small, show all" safety net. Empty would now really mean empty (highly unlikely since Sarasa Mono SC is bundled). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): drop PingFang SC / Microsoft YaHei UI from primary dropdown Step 1 of this PR removed proportional CJK fonts from the CJK fallback picker but left them in BASE_TERMINAL_FONTS, so PingFang SC and Microsoft YaHei UI were still selectable as the *primary* terminal font. Picking PingFang SC as primary produced visibly bloated Latin character spacing (xterm.js samples cell width from the primary font; the wide proportional 'M' inflates every cell), reported by a macOS user in the same thread that opened #931. Both entries are removed from BASE_TERMINAL_FONTS. A new infrastructure/config/fonts.test.ts asserts that no known proportional CJK font name (including PingFang TC/HK, Microsoft YaHei variants, Hiragino Sans GB, Heiti SC/TC) is ever shipped in TERMINAL_FONTS as a primary choice. Migration for users already saved to one of the removed ids: useSettingsState rewrites STORAGE_KEY_TERM_FONT_FAMILY to the default (Menlo) on read when it sees a deprecated id, so the bad value also stops getting carried into cloud-sync uploads. Per-host fontFamily overrides are NOT migrated automatically — they still gracefully fall through to the dropdown's first entry via the existing getFontById fallback; users can re-pick from the host settings UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): drop Comic Sans MS — it's a proportional handwriting font Same symptom as the PingFang SC / Microsoft YaHei UI removal: Comic Sans MS was historically in the primary font dropdown labeled "Casual, non-traditional terminal font", but Comic Sans is a handwriting-style proportional sans-serif. Picking it as the terminal primary inflates cell width and spaces every Latin character far apart (reported in the same #931 thread). - BASE_TERMINAL_FONTS: comic-sans-ms entry removed. - DEPRECATED_PRIMARY_FONT_IDS: gains comic-sans-ms so existing selections silently migrate to Menlo on read. - fonts.test.ts: the proportional-font ban list now also covers Latin proportional fonts (Comic Sans MS, Arial, Helvetica, Times New Roman, Georgia, Verdana, Trebuchet MS, Tahoma) so the test catches any future mislabeled body-text font from being added to the terminal dropdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): keep monospace ahead of CJK fallbacks in composed stack Addresses codex P1 review comment on PR #940 (https://github.com/binaricat/Netcatty/pull/940#discussion_r3216017737). The previous behavior of withCjkFallback() had monospace immediately after the primary family, before any CJK fallback. composeFontFamilyStack had moved monospace to the very end, which means: when the primary font isn't installed on the user's machine (common for Layer 3 CJK choices that aren't bundled and not present on a given OS, or for any built-in id like cascadia-code on a Linux system without it), CSS per-glyph fallback resolves Latin glyphs from a CJK font's full-width Latin variants before ever reaching monospace generic. That breaks xterm.js's fixed cell-grid alignment. The composed stack now reads: <primary>, monospace, <userFallback>, <recommended-cjk>, <system-cjk-stack>, <nerd-font-stack> Per-glyph CSS fallback behavior: - Latin → primary if installed → monospace generic. Cell width stays consistent. - CJK → primary (no) → monospace (no Chinese glyphs) → walks into CJK fallbacks. - Nerd PUA → falls past all of the above into the Nerd Font stack. Updates the position-invariant tests and adds a regression test that explicitly asserts monospace appears before every CJK family in the output stack. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): dedupe Local Font Access API calls under concurrent init Addresses codex P2 review on PR #940: https://github.com/binaricat/Netcatty/pull/940#discussion_r3216246xxx fontStore.initialize() runs getMonospaceFonts() and getAllSystemFontFamilies() in Promise.all; both internally called queryAllSystemFontsOnce(), whose cache check (`if (cache) return`) was only useful once the result had been written. Concurrent callers both passed the empty-cache check and fired their own queryLocalFonts() request — two real Local Font Access API invocations on cold start, with the risk of one succeeding while the other was denied (leaving the authoritative set unset). Fix: cache the *in-flight promise itself*, so subsequent callers await the same single invocation. The first await populates the family-set cache as a side effect, and the resolved promise keeps returning the same value to every subsequent caller. Adds lib/localFonts.test.ts with three regression tests: - concurrent getMonospaceFonts + getAllSystemFontFamilies = 1 API call - sequential repeats also reuse the resolved promise - missing API returns null authoritative set (canvas fallback signal) Exports __resetLocalFontsCacheForTesting() so each test gets a fresh module-level state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): retry LFA on transient failure + notify on availability changes Two follow-up fixes from codex P2 review on PR #940: 1) queryAllSystemFontsOnce() previously kept its in-flight promise even when queryLocalFonts threw. Subsequent callers reused the cached empty result for the rest of the session, so any transient failure at boot (permission state not ready, AbortError, etc.) permanently blinded the rest of the app to installed fonts. Catch now clears queryPromise so the next caller retries. Regression test added. 2) TerminalCjkFontSelect.visibleOptions and TerminalFontSelect .visibleFonts were memoized on [value] / [fonts, value] only, but the filter calls isFontInstalled() which reads module-level systemFamilies — a value that arrives asynchronously after the initial render. The memos never recomputed when authoritative availability data landed, so the dropdowns could continue showing stale "filtered" results until the user changed selection. fontAvailability now exposes subscribeFontAvailability() and getFontAvailabilityVersion() (monotonic counter bumped on setSystemFamilies / clearFontAvailabilityCache). Both selects subscribe via useSyncExternalStore and include the version in their memo deps; tests cover subscriber notification and version monotonicity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): migrate host/group deprecated font ids + localize CJK labels Two follow-up fixes from codex review on PR #940: P2 — Host/group level font migration ==================================== The earlier deprecated-id migration only rewrote STORAGE_KEY_TERM_FONT_FAMILY, so hosts and group configs that had explicitly opted into a now-removed font id (e.g. pingfang-sc, microsoft-yahei, comic-sans-ms) kept `fontFamily` set with `fontFamilyOverride=true`. After the dropdown entries were dropped in 9f2bd282/c9b622d8, those records silently fell through to the first font in the registry (Menlo) while the override flag still read "true" — users saw a host claiming a custom font but rendering the global default with no way to tell what happened. Fix: - infrastructure/config/fonts.ts gains migrateDeprecatedFontOverride(), a structurally-shared helper that drops fontFamily and clears fontFamilyOverride when the id is deprecated. - sanitizeHost now runs it on every host load. - domain/groupConfig.ts grows sanitizeGroupConfig(); useVaultState applies it both on initial load and on cross-tab storage events. - Existing decrypt → sanitize → encrypt round-trip in useVaultState means the migrated values are persisted back to localStorage and propagate through cloud sync naturally. Tests: two each in domain/host.test.ts and domain/groupConfig.test.ts covering deprecated-id reset and untouched-valid-id preservation. P3 — Localize CJK font option labels ==================================== TerminalCjkFontSelect previously hardcoded Chinese option labels ("Auto · 按主字体智能搭配", "Sarasa Mono SC (更纱黑体 简)", etc.) and the synthetic "not recommended" warning. Non-Chinese locales saw a mixed-language UI despite the rest of the setting going through i18n. OPTIONS now references i18n keys; the component looks them up via useI18n(). Both en and zh-CN locales gain matching keys, including `...option.legacy` with `{font}` interpolation for the synthetic "not recommended" item that surfaces saved-but-removed values. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(fonts): also sanitize group configs on the write/import path Addresses codex P2 review on PR #940: https://github.com/binaricat/Netcatty/pull/940#discussion_r3216314xxx The previous commit ( |
||
|
|
72e305fb7a | Add reusable proxy profiles | ||
|
|
470d9b5aae | [codex] Improve ACP agent error diagnostics (#880) | ||
|
|
d86c5ed05a |
[codex] Remove mosh client path setting (#878)
* fix(terminal): remove mosh client path setting * fix(terminal): remove stale mosh detection bridge |
||
|
|
4dd2465388 | Keep known hosts local during sync (#863) | ||
|
|
b6734b9ef9 | Show auto-detected mosh path (#858) | ||
|
|
74d56cdcb8 |
[codex] Settings: detect & override mosh client path (#849)
* Add Mosh client detection and override in Settings → Terminal Builds on PR #847 (auto-detection across PATH gaps). Power users with non-standard install locations (containers, custom builds, multiple mosh versions) can now point the app at a specific mosh binary; less technical users get a one-click "Detect" button to confirm where mosh was found, with a Browse fallback for clicker-only flows. Backend (electron/bridges/terminalBridge.cjs): - detectMoshClient() returns { platform, found, path, searchedPaths }. Reuses resolvePosixExecutable; surfaces the searched dirs so the UI can tell users where to look when nothing was found. - pickMoshClient() opens a native file picker via dialog.showOpenDialog. - startMoshSession honors options.moshClientPath when provided. Strict failure: a missing/non-executable explicit path produces a clear error instead of falling back to auto-detect, so users notice typos and stale paths instead of getting silent recovery. UI (components/settings/tabs/SettingsTerminalTab.tsx): - New SettingRow under "Connection" with text input + Detect + Browse buttons, mirroring the localShell validation pattern. Shows inline validation (notFound/isDirectory) and the last detect result with searched directories on miss. Plumbing: - TerminalSettings.moshClientPath: string field with default "" so empty == auto-detect (matches existing PR #847 semantics). - preload exposes detectMoshClient + pickMoshClient. - createTerminalSessionStarters passes terminalSettings.moshClientPath into the IPC call, undefined when blank. - en.ts / zh-CN.ts get the 9 new strings. Verified locally: - vite build succeeds; settings tab renders. - detectMoshClient() against the live machine returns /opt/homebrew/bin/mosh with the expected searchedPaths list. - Existing PR #847 auto-detection path is unchanged when the field is empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Skip POSIX execute-bit check for explicit Windows mosh path Address Codex P2 on PR #849 commit |
||
|
|
08e566adb0 |
[codex] Add X11 forwarding support (#835)
* Add X11 forwarding support * Address X11 forwarding review feedback * Handle X11 auth for unix socket display paths * Tighten X11 forwarding compatibility handling |
||
|
|
487b7adf3e |
Add 'Set to disabled' button to individual keybindings (#781) (#782)
The keybinding recorder couldn't assign the 'Disabled' sentinel — pressing Esc just cancels. Add a Ban-icon button next to 'Reset to default' that writes 'Disabled' for the active scheme, and render the button label using the localized 'Disabled' string instead of the raw sentinel. |
||
|
|
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> |
||
|
|
cc2702b825 |
fix: decouple Codex agent auth from netcatty provider list (#705)
Codex agent auth must be determined entirely by ~/.codex/auth.json or ~/.codex/config.toml. Before this change, if the user configured any OpenAI-compatible API provider in netcatty settings (for Catty agent use), useAIChatStreaming would silently hand that provider's apiKey to the Codex agent too, causing aiBridge to spawn codex-acp with authMethodId "codex-api-key" and completely override the user's ChatGPT login. The regression was introduced in PR #702 (v1.0.89) when findManagedAgent Provider started matching generic "custom" providers for Codex. Users who logged into Codex via ChatGPT and also had a netcatty-configured custom provider saw the UI flip to "API mode" on refresh and their ChatGPT session get ignored. Remove the codex branch from the agentProviderId resolver and from findManagedAgentProvider itself. Also drop the now-meaningless hasCompatibleProvider hint on the Codex settings card and its i18n copy. Claude agent behavior is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
af2589e60b |
Merge pull request #704 from tces1/MoreSkills
feat: add Netcatty user skills scanning and chat selection flow |
||
|
|
1f3e531d7b | Fix AI skill selection handling | ||
|
|
1c9c4fcec3 |
fix: address second-round review feedback
- Extract fail-loud check to shared getCodexCustomConfigPreflightError so
the list-models handler (aiBridge.cjs:2149) enforces the same up-front
error as the stream handler. Previously a user whose config.toml
env_key was unexported would get the targeted message on chat send but
a generic "Missing env var" from model-list probes (once the probe was
rewired for Codex in a future change).
- Wire Settings "Refresh Status" to also invalidate the shell-env cache.
New invalidateShellEnvCache() helper in shellUtils; aiCodexGetIntegration
now accepts an optional { refreshShellEnv } flag; the button passes it
so a user who just exported OPENROUTER_API_KEY in their rc file can
click Refresh instead of having to restart netcatty.
- Declare authHash in CodexCustomProviderConfig (types.ts + global.d.ts)
so renderer TS actually sees the field instead of needing a cast.
- DRY the 360 magic number in ChatInput: extract
MODEL_PICKER_MAX_WIDTH, use it in both the className max-width and the
left-clamp math so the two can't drift.
- Move codexCustomConfigResolved useState declaration next to its
companion codexConfigModel, above the effect that invokes its setter,
and drop the duplicate declaration further down. Pure code-organization
cleanup but removes a use-before-declaration nit.
No functional changes beyond the fail-loud parity and the refresh-shell-env
path. ACP behavior when authMethodId is omitted still requires a
real-world OpenRouter config.toml validation, which the user is running.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
fea8e8b305 |
fix: stop probing codex-acp for models; show config.toml model when custom
Two issues the user flagged with the previous round: 1. Probing codex-acp for available models returned the stock ChatGPT catalog (GPT 5.4, Codex 5.x, o3, o4-mini) regardless of the active provider. For a user with a custom model_provider in ~/.codex/config.toml (OpenRouter + Qwen), those IDs are meaningless on their endpoint. Roll back the managed-Codex probe hook and go back to static CODEX_MODEL_PRESETS for the stock / ChatGPT path. 2. The fixed w-[300px] popover left empty space on the right whenever the longest row was narrower than 300px. Instead of the probe, teach readCodexCustomProviderConfig to also return the top-level `model` from config.toml and expose it on the integration response. In AIChatSidePanel, call aiCodexGetIntegration when Codex is the active agent and, if customConfig.model is present, override agentModelPresets with a single-entry list pinned to that model. Otherwise fall back to the static presets as before — so ChatGPT users see GPT 5.x / Codex 5.x etc. exactly like before, while custom-config users see just the model their provider is actually pinned to. Popover switches from fixed width to `w-max min-w-[160px] max-w-[360px]` so it hugs content (great for short single-model lists) while still capping very long rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
18c001e9c5 |
fix: show custom config even when env_key is not exported yet
The first pass required both a custom model_provider in ~/.codex/config.toml
AND the referenced env_key to already be present in the shell environment.
If a user had the config file set up but hadn't (yet) exported the key in
their shell, detection returned null and the UI fell back to "Not
connected" + "Connect ChatGPT" — which is the exact flow they were trying
to avoid.
The config.toml is a strong enough signal of intent on its own. Keep the
integration in the connected_custom_config state regardless of env_key
availability, but expose envKeyPresent on the response so the UI can
explicitly warn "Warning: $MY_KEY is not set in your shell — export it".
Status label and color also flip to amber ("Custom config detected — env
var missing") so the state is easy to spot without dropping back to the
login prompt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
c2c6b265d4 |
feat: detect user's ~/.codex/config.toml custom provider as ready state
Users who hand-configure ~/.codex/config.toml with a custom model_provider and matching [model_providers.<name>] entry are fully functional from the Codex CLI, but netcatty only looked at codex login status — which reports on ~/.codex/auth.json alone — and would therefore push them into the ChatGPT login flow even though the CLI works for them. Add a minimal TOML parser for the narrow subset we need (top-level keys plus [model_providers.<name>] string tables), and readCodexCustomProvider Config() to detect a usable custom-provider setup: an active model_provider that isn't the built-in openai preset, pointing at a provider entry whose env_key is set in the shell env (or api_key is hardcoded). Surface this as a new integration state "connected_custom_config", add a customConfig summary on the IPC response, and tweak the Codex settings card so it shows the custom-provider name, hides the Connect ChatGPT button, and drops the stale "OpenAI-compatible provider" hint when this path is active. At Codex-ACP spawn time, introduce getCodexAuthOverride() so we only pass authMethodId: "chatgpt" when we truly have no other option. When a netcatty-managed API key is present we still use "codex-api-key"; when the user has a custom config we omit authMethodId entirely so codex-acp resolves auth from the shell env / config.toml itself. Fold the detected custom config (provider name, base url, env key presence) into the provider reuse fingerprint so edits to config.toml invalidate cached ACP instances. Fixes the Codex half of #677 for users who skip Settings → AI providers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
1e50b66407 | fix: finish Codex provider follow-up for #677 | ||
|
|
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> |
||
|
|
8b99f2411f | fix vault root host filter sync and empty states | ||
|
|
3089cab88d | add vault root ungrouped host toggle | ||
|
|
50b20eaa05 | chore: triple-pass review and hardening of AI Skills logic | ||
|
|
58bc08a045 | Add user skills injection and picker UI | ||
|
|
fc32b44d8e |
fix: replace missing ToggleRow with SettingRow + Toggle
ToggleRow is a locally-defined component in HostDetailsPanel and GroupDetailsPanel — it is NOT exported or available in the terminal settings tab. Using it caused a white-screen crash. Replaced with the existing SettingRow + Toggle pattern that's already used throughout the terminal settings tab. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |