Commit Graph
300 Commits
Author SHA1 Message Date
rnarany 51d9f715da feat(autocomplete): localize common commands and polish terminal hints (#4172)
* feat(autocomplete): add zh-CN descriptions for zip, unzip, make, nano

Cover 32 missing Fig descriptions, generate zh-TW, add batch coverage tests, and record source and semantic checks.

* fix(autocomplete): correct zip, unzip, and make semantics

Limit zip -9 to the compression level, warn that unzip -0 is a suspect upstream option, and describe make -N as the Apple/NeXT compatibility extension.

* fix(autocomplete): simplify unzip -0 warning and correct NeXT note

Keep the -O gbk version caveat in the research record and describe Next/NeXT as a capitalization difference.

* feat(autocomplete): complete remaining common command descriptions

Add 177 zh-CN descriptions for python, python3, node, vim, sort, awk, and sed; generate zh-TW, cover the batch with real Fig specs, and record one deferred shared node key.

* fix(autocomplete): correct sed n and sort parallel semantics

Add the automatic-print condition to sed n, remove the universal CPU default from sort --parallel, and remove duplicated adopted-entry sections from the source record.

* fix: repair macOS terminal setup and align autocomplete details

- Read OSC 7 setup payloads through base64 stdin so BSD/macOS works
- Keep the FIDO askpass socket path within the Unix sun_path limit
- Align autocomplete descriptions in a shared fixed column
- Fix an invalid optional-parameter declaration and lint warnings
- Harden macOS-sensitive OSC 7, SFTP, temp-dir and privacy tests

* feat(autocomplete): add zh-CN descriptions for file and text utilities

Cover ln, stat, wc, cut, uniq, tee and xargs with 70 localized
descriptions. Add coverage and localization tests, preload the new common
specs, and record the upstream wc/xargs wording corrections.

* fix(autocomplete): keep popup scrollbars draggable

Panel mousedown handlers no longer preventDefault across the whole box, so the right-side detail scrollbar and the suggestion list scrollbar can be dragged while padding clicks still preserve terminal focus.

* fix(autocomplete): keep detail panel open while selecting text

Delay row hover clearing so the pointer can move into the right-side detail panel, and allow native text selection there without stealing terminal focus on ordinary padding clicks.
2026-11-07 20:00:43 +08:00
陈大猫 968e84ff59 feat(plugins): install published plugin packages from GitHub (#4164)
* feat(plugins): install published plugin packages from GitHub

* fix(plugins): prebundle GitHub parser and reset consumed preview trust

* fix(plugins): localize GitHub install errors and trust review
2026-11-07 07:10:08 +08:00
+1 16061720e6 feat(ai): add safe OSC 133 shell readiness (#4110)
* fix(terminal): clear sticky broadcast latch on vim ex (#4107) (#4109)

Fixes #4107.

Merge gate: GitHub Codex clean on tip b7efbe347; local CLEAN residual (unknown colon-prefixed alt-screen challenges) left as documented thrash; CI green (lint-and-test / windows-fido / windows-broadcast-ssh); Electron smoke PASS NC_OUT=/workspace/netcatty-smoke-shots/pr-4109-b7efbe347-20261104-122316 (both panes exited vim via toolbar broadcast :q!).

* perf(ai): stop freezing the app during agent discovery and chat backups (#4116)

Both paths ran synchronous I/O on the Electron main process, which freezes
every window and the terminal keystroke relay while it runs:

- Agent discovery (opening the AI panel or Settings > AI) probed auth with
  execFileSync: `security find-generic-password` (4 s timeout), `gh auth
  status` (network, 5 s), `which cursor-agent` (4 s) and `cursor-agent status`
  (8 s), one after another. The probes now use a non-rejecting async execFile
  helper with the same result shape; discovery awaits them.
- Cursor SDK detection evaluated `import("@cursor/sdk")` (~4 MB of ESM plus
  its transport stack) only to check that it is installed, and kept it
  resident afterwards. It now checks for the ESM entry that the dynamic import
  loads; the SDK itself still loads on first use. The package does not export
  package.json and its CJS build is stripped from releases, so the check walks
  require.resolve.paths() instead of using require.resolve.
- The AI chat backup wrote the whole history (often several MB) with
  writeFileSync on every debounced change. Writes now use fs.promises and are
  serialized so generations cannot interleave.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* perf(ui): stop whole-shell re-renders on clicks, focus, typing and modifiers (#4117)

Several everyday interactions re-rendered the entire shell (AppView, TopTabs,
TerminalLayer and every pane) even though nothing they show had changed:

- Clicking or focusing a split pane always replaced the workspaces array
  (twice per click: onClick + onFocusCapture), cascading through the whole
  app and re-saving the session restore snapshot. setWorkspaceFocusedSession
  and moveFocusInWorkspace now keep the same array when focus is unchanged
  (domain/workspace.ts setWorkspaceFocusedSessionId).
- Every window focus/visibility change resynced app lock and produced a new
  runtime state object even when nothing changed, re-rendering the provider
  tree. Equal states keep the current object and useAppLockState's result is
  memoized.
- Quick switcher keystrokes went through AppSideEffects state and the
  app-local UI store, re-rendering AppSideEffects, every host island, AppShell,
  AppView and TopTabs per character. QuickSwitcherOverlay now owns the query
  and host results; the overlay unmounts on close, which resets the query
  exactly where setQuickSearch('') used to.
- Pressing/releasing Ctrl/Cmd (every ⌘C/⌘V/Ctrl+C) re-rendered the whole tab
  bar to toggle shortcut number badges. The held flag now lives in a small
  store read only by each tab's TabIconSlot.
- AppView passed fresh inline callbacks that defeated TopTabs' memo comparator
  and every open editor tab; TextEditorTabView is now memoized. TopTabs
  memoizes per-workspace session lists and hoists the root tab icons.
- Workspace drag previews only update state when the drop hint actually moves.

React commits / render time for 10 repetitions in the real app (profiling
build, 2 split panes, 300 hosts), main -> this change:
  window focus            42 / 17.1 ms -> 2 / 0.4 ms
  press/release Cmd       50 / 41.9 ms -> 22 / 6.6 ms
  click focused pane      42 / 69.6 ms -> 3 / 0.4 ms
  quick switcher typing   41 / 61.3 ms -> 16 / 34.8 ms

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* perf(terminal): cap live WebGL contexts, release them eagerly, bound note undo history (#4122)

* perf(terminal): cap live WebGL contexts and stop the context-loss cascade

Chromium keeps at most 16 WebGL contexts per renderer and force-loses the
one that drew least recently past that. Hidden terminal panes keep their
WebGL renderer unless hibernation is on, so with more than 16 terminal
tabs every forced loss was rebuilt 50 ms later, evicting another context.
With 20 local tabs, half of all tab switches landed on a lost context
(xterm waits 3 s before falling back), and terminals that lost three
contexts within a minute were parked on the DOM renderer for good.

- Add a shared context budget (12). Past it, renderers of panes hidden for
  at least 3 s are released, least recently seen first. Visible panes are
  never released or refused; a released pane rebuilds when shown.
- A context lost while hidden waits for the reveal instead of rebuilding
  at once, and no longer counts toward the breaker.
- The short-hide CSS reveal path now also asks for the renderer (a no-op
  while it is live).
- Disposing the addon left its context alive until GC. Lose it explicitly
  so GPU memory and the Chromium slot come back right away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(notes): bound the source editor's undo history

Every undo step in the markdown source editor holds a full copy of the
note, and the stack never shrank. A long session on a large note kept
every intermediate version alive (a 100 KB note with 1,000 steps holds
~200 MB of UTF-16 text).

Keep the 200 most recent steps and at most 8M characters across them,
dropping the oldest first. The newest step always survives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* perf(terminal): stop per-second churn while connecting and while idle (#4118)

* perf(terminal): stop per-second churn while connecting and while idle

- Connecting sessions: the 200 ms progress animation and 1 s countdown were
  Terminal state threaded into the TerminalView ctx, so every connecting
  session (hidden tabs and startup restores included) re-rendered Terminal +
  TerminalView up to six times a second. Terminal now only publishes the
  timeout deadline and real progress events; the connection dialog animates
  and counts down locally (connectionTicker.ts). HostConnectionTestDialog
  keeps passing progress/timeLeft as before.
- Monitor runtime: polled every second unconditionally and woke every
  subscriber each time because takenAt always changes. It now polls at 1 s
  only while something is monitored (here or in another window), 10 s
  otherwise, and skips notifying for unchanged idle replies.
- Auto-reconnect progress log: capped at the newest 300 lines (it grew by a
  few lines every 5 s retry for as long as an outage lasted).
- App Lock: report activity at most once a second instead of an IPC per
  keystroke / wheel tick / pointer event.
- Tray/Dock: the renderer resends hosts only when the host list changed, and
  the main process rebuilds the macOS Dock menu (sort + one native item per
  host) only then, not on every session status change.

Real app, profiling build, main -> this change:
  3 SSH sessions connecting (3 s)   14 commits / 35.3 ms -> 8 / 1.7 ms
  idle (10 s)                       13 commits / 3.1 ms  -> 2 / 0.1 ms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(terminal): update observe-popup reconnect deps assertion for capped progress logs

CI failed because #4118 wrapped setProgressLogs in useCallback to apply
capConnectionProgressLogs and added it to scheduleAutoReconnect deps.
The observe-popup source assertion still expected the old deps list.

The attachExistingSession early-return behavior is unchanged; only the
stale dependency regex needed updating.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* perf(state): stop handler churn on session updates and repeated JSON work (#4121)

* perf(state): stop handler churn on session updates and repeated JSON work

- Shell handlers: AppSideEffects registers ~58 glue callbacks for the Host
  islands. Several close over sessions/workspaces/hosts (split/copy/duplicate,
  close tabs, delete host, keyboard-interactive, connect...), so every session
  status change and host update (connect bursts, last-connected times)
  re-notified the bridge and rebuilt all four shell bags, re-rendering AppView
  and TopTabs. They are now registered behind per-key stable wrappers that
  call the latest implementation (stabilizeAppHandlers); local callbacks and
  their effects are unchanged. All registered entries are actions, setters or
  refs, never read during render.
- Session actions: moveFocusInWorkspace, copySession and copyWorkspace read
  workspaces / orphan sessions / log views through refs at call time (the same
  snapshot the closure captured), so they keep a stable identity. Getters read
  during render (isBroadcastEnabled, getOrderedWorkTabs, ...) are untouched.
- AI history: the live store was parsed five times when the AI state root
  mounted; parseSessionsStore parses once per call (two parses total).
- AI history writes: the synced branch serializes once and reuses the string
  for the disk mirror, and rewriting the same immutable sessions array (save
  path, then cloud-sync collection) is skipped while storage still holds that
  exact write in the same mode.
- Command history: saving after each Enter parsed the whole store (up to 10k
  entries) only to read its clear generation, and every other window parsed it
  again on the storage event. The generation is now serialized first and read
  from the raw string (full parse only to adopt a peer clear), and the
  debounced write runs when the renderer is idle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: retrigger CI after cancelled lint-and-test run

Previous lint-and-test was cancelled mid-run.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>

* chore: fix outstanding ESLint warnings (#4112)

* chore: fix outstanding ESLint warnings

- useSftpFollowTerminalCwd: drop activeSessionId/focusedSessionId from the
  syncFollowToTerminalCwd deps; the callback reads them through refs and the
  only consumer effect already re-runs on those ids.
- useSftpPaneDialogs: list the stable setShowDeleteConfirm callback in the
  handleDelete/openDeleteConfirm deps.
- csvProxy: remove the unnecessary `\[` escapes inside character classes
  (same matches under the `u` flag).
- Remove unused test imports/variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: retrigger CI after runner SIGTERM cancel

Previous lint-and-test was cancelled by runner shutdown, not a real failure.

* chore: retrigger CI after second runner cancel

Lint had already passed; plugin-contract step was cancelled by the runner.

* chore: retrigger CI after third runner cancel

Lint and schema steps passed; plugin-contract step was cancelled by
the runner again. Empty commit to re-queue lint-and-test.

* chore: retrigger CI after repeated runner cancel

plugin-contract step keeps being cancelled by the Actions runner
(not a test failure). Empty commit to re-queue; Actions rerun API
denied for this PAT.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>

* perf: add opt-in startup timeline and real-app startup harness (#4114)

* perf: add opt-in startup timeline and real-app startup harness

- electron/perfTrace.cjs records first-occurrence startup marks in the main
  process when NETCATTY_PERF_TRACE=1 (a no-op otherwise), then writes the
  main-window renderer timeline (navigation, paint, netcatty:* marks, JS heap)
  and app.getAppMetrics() to JSON after the first renderer-ready signal.
- Marks: main.cjs entry / modules loaded / whenReady, main window creation,
  registerBridges and plugin-host start/end, loadURL, did-finish-load,
  renderer ready, window shown. The renderer adds three performance.mark()
  calls (entry evaluated, App mounted, renderer ready), also visible in
  DevTools.
- scripts/perf-startup.live.cjs (npm run perf:startup) launches the built app
  N times against a throwaway --user-data-dir profile with ssh:// deep-link
  registration disabled, optionally seeds synthetic hosts over CDP, and
  reports cold plus warm p50/p90.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(harness): add --profile option to reuse a startup profile

Lets the harness keep one profile across invocations, e.g. to compare warm
V8 code caches across builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: retrigger CI after flaky monitorRuntimeIsolation failure

Previous lint-and-test failed on known-preexisting
`useEffect is not defined` in monitorRuntimeIsolation.test.ts.

* chore: retrigger CI after monitorRuntimeIsolation useEffect flake

Known pre-existing flake: monitor controls test fails with
`useEffect is not defined`. Empty commit to re-run once.

* test(app): bind React hooks in monitorRuntimeIsolation VM sandbox

Extracting component declarations drops their React imports, leaving bare hooks such as useEffect undefined in the VM. Inject common React hooks and APIs while preserving explicit binding overrides to prevent intermittent monitor isolation failures in CI.

Stub the startup performance mark through App test bindings so the mount effect can execute without browser globals. Production components remain unchanged.

Validation: monitorRuntimeIsolation passed five consecutive runs. Plain npm run lint found 17 existing errors in git-ignored scratch files; excluding only _t.mjs and sim-4107.mjs passed with zero errors and eight existing warnings.

* chore: retrigger CI after plugin-contract runner cancel

Lint/schema passed; plugin-contract step cancelled by the runner
(not a test failure). Empty commit to re-queue.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>

* perf(main): stop blocking the first window on validator compilation (#4115)

The main process ran ~1.4 s of synchronous work in registerBridges before the
main window could start loading (app:// assets are served from the main
thread too), even for users without plugins:

- contractValidator: every createDefinitionValidator() built a fresh Ajv
  instance and re-registered the 200 KB plugin contract (~17 ms each, ~90 of
  them across the plugin host at startup, plus one per plugin view resource
  request). Share one Ajv per process, compile each definition on first use
  and memoize it; each validator still exposes its own `.errors`.
- distributionSnapshot: compile the three distribution schemas lazily on the
  shared contract Ajv instead of a second Ajv at module load (273 ms -> 37 ms).
- pluginBridge: hoist the PluginViewResourceRequest validator to module scope.
- cloudSyncBridge: load webdav / @aws-sdk/client-s3 / @smithy/node-http-handler
  on first sync instead of at startup (108 ms -> 4 ms).
- app:// scheme: enable codeCache so V8 reuses compiled renderer bundles
  across launches and windows. A same-length in-place edit of the cached entry
  chunk runs the new code, so stale cache is not reused after upgrades.

Startup harness (2000 hosts, 10 runs, warm p50): registerBridges 1421 ->
116 ms, plugin host 871 -> 30 ms, renderer ready 2147 -> 723 ms, total memory
923 -> 823 MB.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ai): keep sessions past 2MB localStorage budget via overflow (#4123)

Urgent fix for #4123: retain AI chat history past the 2MiB primary localStorage budget via overflow chunks + pre-split disk backup, with disk-only recovery markers, aggregate-quota degradation, import live-state retention, and count-cap marker resolution.

Gate: local Codex CLEAN (no P1) on 084ed813; CI green on acabe0bbc; Electron smoke PASS (pr-4124-acabe0bbc). Residual P2s noted (cross-window reconcile; vault-restore marker clear; retention-cap during pending recovery).

No release.

* perf(renderer): cut the startup bundle by a third (#4120)

* perf(renderer): cut the startup bundle by a third

Every window parsed ~6.1 MB of JavaScript before the first render. The entry
chunk carried all five UI locales, and the whole AI SDK chunk was preloaded
even though the AI panel is lazy.

- i18n: only English stays in the entry; other dictionaries load on demand
  (plus their same-base fallback, e.g. zh-TW -> zh-CN). index.tsx awaits the
  saved language before the first render, and I18nProvider re-renders when a
  runtime switch finishes loading.
- AI runtime: aiStateSnapshots cleaned up chats through getAgentRuntime(),
  and the plugin AI/agent hosts created their runtimes on mount, which pulled
  the Catty harness and @ai-sdk providers into startup. Cleanup now goes
  through a light handle that the runtime registers (the reversible privacy
  flag and main-process privacy mapping are still cleared without a runtime),
  and both plugin hosts create their runtime on the first request, re-checking
  liveness after the async load.
- Chunking: manualChunks' object form also moved Vite's dynamic-import
  preload helper into vendor-ai (the AI SDK itself uses import()), so the
  entry imported it from there and vendor-ai (649 KB) was preloaded on every
  launch. Vendor chunks now use the function form and the helper gets its own
  1 KB chunk.
- jsqr is loaded when a QR image is imported instead of with the host editor.
- startupBundle.architecture.test.ts guards these invariants.

Startup JS (index.html): 5.29 MB entry + 649 KB vendor-ai + 138 KB radix ->
3.80 MB entry + 1 KB helper + 174 KB radix (-35%). Startup harness, 2000 hosts,
10 runs, warm p50: loadURL -> renderer entry evaluated 321 -> 207 ms,
loadURL -> renderer ready 481 -> 323 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(i18n): preload locale messages before static rendering

* test(i18n): preload locales in remaining zh-CN UI tests

Non-English dictionaries now load lazily, so I18nProvider can initially render English before its effect finishes loading zh-CN. Explicitly await loadLocaleMessages before rendering these localized UI tests to fix the remaining CI failures without timing-based waits or production changes.

* chore: retrigger CI after locale preload fix

Previous tip 38108ce66 was on the branch but the PR head stayed
stuck on d49ad024e after a GitHub 500 during push. Empty commit
to refresh the PR head and re-queue checks.

* chore: retrigger CI after plugin-contract runner cancel

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* perf(terminal): cache ligature font parses across terminals (#4126)

* fix(pty): forward childProcesses queries to terminal worker (#4127)

Worker-owned PTY sessions register their PIDs in the worker, leaving the main process map empty during close-busy checks.

Forward child-process queries to the terminal worker and register a worker handler for string and object payloads. Fall back to the main process tree if the worker is unavailable or returns a non-array result.

* fix(ui): keep follow-system themeSource from chrome override (#4131)

When Interface Theme is Follow System, chrome refresh called setTheme(light|dark) and locked nativeTheme.themeSource, so OS appearance flips no longer updated the UI. Keep setTheme('system') on system chrome preference.

Fixes #4130

Gate: GH Codex CLEAN on 2c0c1c77fd (6635017901); CI 41860537287 success; Electron smoke PASS NC_OUT=/workspace/netcatty-smoke-shots/pr-4131-37c0f491a (same tree as prior tip).

* fix(transfer): keep SSH alive when canceling mid-transfer (#4134) (#4135)

* fix(transfer): keep SSH alive when canceling mid-transfer (#4134)

Cancel of a large isolated SFTP upload/download used to call sftp.end()
mid-WRITE on a channel that shares the terminal SSH transport. That
CHANNEL_CLOSE with in-flight traffic can make the remote drop the whole
connection, so the shell shows [session closed by remote (no exit status)].

Abort now drains like the shared browse path (no mid-flight end); dispose
only after settle. Cancel returns { cancelled: true } so the pool does not
treat channel-closed as session loss.

* fix(transfer): deferred-dispose soft-cancelled isolated SFTP (#4134)

Codex P2 on 88b4890: softDispose dropped cancelled isolated download
channels from the pool without ending them, leaking remote subsystems.
Schedule sftp.end() after a ~2s quiet settle instead of abandoning forever
or ending mid-flight on the shared SSH transport.

Aligned cheap fix for isolated fastPut cancel: after force-settle, defer
end of the disposable channel the same way. Stopping ssh2 fastXfer
internal scheduling still needs invasive hooks — left as documented
residual; deferred end after quiet is the minimal leak fix.

Tests cover mid-flight isolated upload/download cancel (no end while
held) and eventual deferred end after settle.

* Revert "fix(transfer): deferred-dispose soft-cancelled isolated SFTP (#4134)"

This reverts commit 5b85ee70c0.

* fix(transfer): dispose soft-cancelled SFTP only after request drain (#4134)

Codex 4475686359 on 5b85ee70: fixed 2s deferred sftp.end() still fired while
READs were pending and could recreate the shared-SSH drop. Poll sftp._requests
for quiescence before ending; if still pending past the hard max, skip end
(abandon until session close) rather than ending mid-flight.

Tests: held isolated download READs past 2s must not end until released;
fastPut gate cancel still deferred-ends after drain.

* Revert "fix(transfer): dispose soft-cancelled SFTP only after request drain (#4134)"

This reverts commit 7e9934cd7e.

* chore: retrigger CI after lint-and-test runner cancel (#4135)

---------

Co-authored-by: binaricat <binaricat@users.noreply.github.com>

* fix(terminal-worker): stop EPIPE ignore path from log-feedback looping (#4137)

Fixes #4136.

When the terminal utilityProcess console pipe is already dead, classifyProcessError marks EPIPE / ERR_STREAM_DESTROYED as ignore, but installTerminalWorkerErrorGuards still called logError → console.error. That write failed with EPIPE, re-entered uncaughtException, and busy-looped.

- On action === ignore: only report, never logError.
- Latch consoleBroken on stdout/stderr error so the default console.error path no-ops after the transport dies.
- Harden compressed extract cancel rollback under SIGTERM (ignore HUP/INT/TERM during cleanup, unlink archive first) and stabilize the FIFO cancel test that was flaking CI.

Out of scope: #4128 main-process EIO twin.

* fix(terminal): stop Shift+Insert CSI leaking to broadcast peers (#4142)

Fixes #4142

Squash-merge gate:
- GH Codex CLEAN on tree-identical tip c59ea270e (receipt 6653109730)
- CI SUCCESS https://github.com/binaricat/Netcatty/actions/runs/41995749622
- Electron smoke PASS /workspace/netcatty-smoke-shots/pr-4143-c59ea270e (marker both panes; Insert CSI absent)

* fix(main): stop the process error guards from logging into a dead stdio sink (#4140)

* fix(main): stop the process error guards from logging into a dead stdio sink

#4128 reports the main process freezing with an `EIO` write error raised from
`logError` -> `console.error`. That is the same feedback loop as #4136: the
guard writes diagnostics through the sink that just failed, the failure comes
back as another process-level error, and the handler logs again.

The `ignore` short-circuit that fixed the terminal worker does not cover this
one, because `EIO` is not a benign stream teardown: it is classified as
`suppress`, which logs.

- Add `createDiagnosticLogger()`. It holds an `error` listener on the
  diagnostics streams so a failed stdio write is no longer re-raised as an
  uncaught exception, and it latches closed after an async sink error or a
  synchronous throw, so later diagnostics are dropped instead of re-entering.
- Use it for the main-process logger and warner in `main.cjs`.

Errors are still captured for the crash log through `captureError`.

* fix(error-guards): forward the first stdio failure to the crash log

createDiagnosticLogger holds an `error` listener on stdout/stderr, so it
consumes the stream failure before the process guards can see it. Once the
sink latched closed, the failure was dropped from the crash log entirely:
a closed stdout pipe produced one report before the sink guard landed and
none after.

- Add an `onError` option to `createDiagnosticLogger`. It latches the sink
  closed first, then forwards only the first stream error; a throwing
  reporter cannot escape.
- Main process: report the failure through
  `crashLogBridge.captureError("stdio", err)`.
- Terminal worker: fold the inline `consoleBroken` latch onto
  `createDiagnosticLogger` and forward the first failure through
  `report`/`reportWorkerError`.

Regression coverage spawns a subprocess whose stdout/stderr pipe is closed
by the parent and asserts exactly one report is produced.

* ci: retrigger tests after unrelated plugin SIGKILL flake

PluginLogSettings.test.tsx was SIGKILL'd and the lint-and-test job was
canceled; the failure is unrelated to the stdio guard change.

---------

Co-authored-by: shibo.li <shibo.li@d-robotics.cc>
Co-authored-by: 陈大猫 <16399091+binaricat@users.noreply.github.com>

* fix(ai): persist AI sessions per record in IndexedDB instead of a 2MB localStorage blob (#4125)

* fix(ai): persist AI sessions per record in IndexedDB instead of a 2MB localStorage blob

Closes #4123.

The legacy store serialized every session into one localStorage JSON key
under a 2MB byte budget. Each write re-serialized the whole store, and the
degradation ladder dropped whole oldest sessions when the budget was
exceeded — silently destroying capped chats (~1MB each) on every write.

- add aiSessionIdbStorage: db netcatty_ai_sessions, one record per session
  (keyPath id) plus a meta store (schemaVersion, lastWriteAt, mirrored
  active-session map); BroadcastChannel replaces the localStorage storage
  event for cross-window sync
- one-time boot migration from netcatty_ai_sessions_v1; the legacy key
  stays untouched for one release as a read-only fallback
- writeSessionsForStorage now diffs against the last persisted set and
  writes only changed records through a serialized queue; the 2MB budget
  and its degradation ladder are removed (the global re-serialization cost
  they defended no longer exists)
- retention becomes a pure session-count cap (raised 50 -> 1000); the
  per-session 200-message cap is unchanged
- when the cap evicts sessions, a pre-eviction backup generation is
  written before the records are dropped, closing the #4018 mirror blind
  spot; explicit deletions and cloud-authoritative removals do not
  trigger it
- boot restore chain: IndexedDB -> legacy localStorage (migration) ->
  #4018 disk backup generations; a corrupt/unavailable IndexedDB falls
  back to legacy localStorage writes
- tests: IDB roundtrip/migration/active-map mirror via a minimal fake
  indexedDB, per-record put/delete diffing, count-cap eviction with
  pre-eviction generation, explicit-delete suppression, synced-history
  no-prune, corrupt-IDB fallback; byte-ladder tests removed with the
  ladder

* test(ai): update fork/undo reload tests to the IndexedDB persistence contract

Issue #4123: session persistence moved to IndexedDB per-record writes; the
persisted set no longer lands in the legacy localStorage key (read-only
fallback for one release) and the retention cap is a session count of 1000
instead of 50.

- fork/undo reload tests read the persisted set via
  getPersistedAiSessionsSnapshot() instead of parsing localStorage
- cap-boundary assertions (49/50 newer chats) become preservation
  assertions (newerCount + 2 records survive; the 1000-cap boundary is
  covered by aiStateSnapshots.test.ts)
- the reload step seeds the read-only legacy store with the persisted set,
  the stand-in for what the migration persists on a profile without
  IndexedDB

* feat(ai): make the per-session message cap user-configurable

* fix(ai): sequence session sync after boot hydration

Cloud-sync collection (collectAiSessionsForSync) could run before the
IndexedDB boot hydration settled and writeSessionsForStorage() the stale
legacy localStorage list over newer per-record IndexedDB data. The
collection now skips the sync round until the boot load settles; the next
auto-sync picks up the hydrated store.

The hydration signal lives in aiStateSnapshots (isAiSessionsHydrationComplete
/ markAiSessionsHydrationComplete) and is shared with useAIState's boot
effect and the cross-window refresh handler.

* fix(ai): make the legacy session migration one-shot via a persisted marker

The migration keyed on 'IndexedDB empty + legacy populated'. Because the
legacy key is intentionally kept for one release as a read-only fallback,
any boot whose store was empty re-ran the migration and resurrected chats
the user had deleted (P1②).

A migrationCompleted marker is now persisted in the meta store after the
migration data is durable. Once present, the legacy key is never consulted
again: an empty store is authoritative. The marker also records a store
that was already populated by a pre-marker build, and an uninitialized
empty store, so 'intentionally empty' is distinguishable from 'uninitialized'.

* fix(ai): harden boot persistence failure paths

Four review findings around the boot load's failure modes:

- P2①: when IndexedDB fails at boot and sessions come from the legacy
  store, the diff baseline was seeded even though nothing was persisted,
  so a later edit diff-wrote only the edited record and the rest were
  lost after recovery. The legacy set is now persisted best-effort (one
  re-open retry) before seeding; if that fails the baseline stays unset
  so the next edit rewrites every record.
- P2②: the active-pointer validation effect ran against the
  initially-empty in-memory session array and nulled every pointer
  before the async load finished. It now waits for the hydration signal.
- P2③: when IndexedDB stayed unopenable and the legacy store was
  missing/corrupt, the unconditional recheck threw into the swallowing
  outer catch and the #4018 disk backup was never applied. The recheck
  failure is now contained (one retry already happens inside open()),
  the newest backup generation is applied as the recovery source, and
  the error is surfaced via console.warn.
- P2④: a cross-window empty getAll (another window deleted the last
  chat) hit an early return, kept the deleted session alive locally and
  resurrected it on the next edit. After hydration an empty store is
  adopted; the early-return only covers the pre-hydration window.

Scenario regression tests live in useAIState.bootHydration.test.ts;
fakeIndexedDb gains failOpenTimes for counted open failures.

* fix(ai): gate session maintenance and migrate legacy overflow history

* fix(ai): skip backup restore of empty IDB and resume failed overflow migration

* ci: retrigger lint-and-test after runner shutdown

The previous Actions runner received a shutdown signal during plugin-contract
verification; lint had already passed and unit tests never started.

---------

Co-authored-by: GOUKI9999 <GOUKI9999@users.noreply.github.com>
Co-authored-by: 陈大猫 <16399091+binaricat@users.noreply.github.com>

* fix(ai): keep surrogate pairs whole when truncating model context (#4088)

* fix(ai): keep surrogate pairs whole when truncating model context

Head/tail truncation sliced strings by UTF-16 code unit, so a cut could
split an emoji into a lone surrogate. JSON.stringify then emits a
`\udXXX` escape that strict providers reject ("lone leading surrogate in
hex escape"), and because the same cut is reapplied every step, the chat
stays stuck on that 400 for every later request.

- Add textBoundaries helpers (sliceHead, sliceTail, safeSliceBounds) and
  use them at every truncation site on the model request path.
- Wrap provider models with a middleware that replaces any remaining lone
  surrogate with U+FFFD before the request body is serialized.

* fix(ai): keep own __proto__ keys when normalizing prompt values

toWellFormedValue cloned changed objects by plain assignment, so an own
__proto__ key (e.g. from JSON.parse) hit the inherited setter: the field
was dropped from the request and the clone's prototype changed. Clone
with the source prototype and define own properties instead.

* fix(theme): continue Windows system-theme fix with registrar coverage (#4108)

* fix(theme): broadcast native system theme updates to renderers

nativeTheme.updated only fires for listeners in the main process, so a renderer sitting in system mode never learned that Windows changed the application theme. Forward the resolved preference to every window and let the settings hook re-apply it.

* fix(theme): keep terminal chrome separate from native system policy

refreshActiveChromeThemeSurfaces called setTheme(light|dark), which the main process writes into nativeTheme.themeSource. The stored preference stayed 'system' while Electron was pinned to a fixed color, so Windows application theme changes were ignored.

Only paint the surfaces here. Also re-assert a fixed independent terminal theme when the OS preference changes: the fingerprint short-circuit skipped the refresh, and the hook effect never re-ran while the terminal props stayed stable.

* test(theme): bound the new jsdom chrome tests

A hanging jsdom/React render should fail the test with its name instead of consuming the CI job budget.

* test(theme): provide native theme in bridge registrar fixtures

* test(theme): provide nativeTheme in child-process registrar fixture

The worker tests introduced in #4127 omitted nativeTheme after the system-theme listener registration became part of the production registrar. Provide a real EventEmitter, matching the transfer-limit fixture, so registrar setup succeeds without weakening production behavior.

* ci: retrigger lint-and-test after runner shutdown

The previous ubuntu-latest lint-and-test run was canceled during plugin
contract verification (annotation: "The operation was canceled."). Lint
had already passed; tests never started. Tree is unchanged.

---------

Co-authored-by: HHXXYY123 <117068187+HHXXYY123@users.noreply.github.com>

* feat(terminal): continue Directory Picker with OSC 7 and reconnect fixes (#4113)

* feat(terminal): add Directory Picker context chip

* fix(terminal): bind directory reads to the exact SSH session

* fix(terminal): follow manually changed working directories

* fix(terminal): isolate cwd updates and gate local platform support

* docs(terminal): research Directory Picker cross-platform follow-up

* fix(terminal): confirm OSC 7 navigation and reset picker loading

* fix(terminal): preserve active shell OSC 7 during directory refresh

* test(terminal): retain cross-session OSC 7 submission guard

---------

Co-authored-by: Joseph Hau <hexart@gmail.com>

* feat(ai): support extended reasoning effort with GPT-5.5 xhigh (#4111)

* feat(ai): support xhigh and max reasoning effort levels

The provider-level reasoning default only offered low/medium/high, so models
that document the full graded ladder could not be configured to their top
tiers. DeepSeek V4/V4.1 grade `reasoning_effort` continuously up to `max`, and
Anthropic exposes `max` on 4.6+ plus `xhigh` from 4.7 onward.

Beyond the missing select options, three separate layers silently discarded
the higher values:

- REASONING_RANK stopped at `high`, so the visibility resolver treated
  `xhigh`/`max` as stale and fell back to `low`.
- The OpenAI capability check only matched `deepseek-r1`, which left the
  DeepSeek V4 family unrecognised and hid the thinking chip entirely.
- The pinned `@ai-sdk/openai@4.0.8` validates provider options against a zod
  enum capped at `xhigh`, so `max` on Chat Completions threw
  "invalid openai provider options" before any request was made.

Add a shared PROVIDER_REASONING_EFFORT_OPTIONS list used by both the settings
select and its save guard, extend the rank ladder, and teach the per-model
derivation about DeepSeek V4 grading plus the Anthropic effort generations.
Bump `@ai-sdk/openai` to 4.0.11, the first release whose enum accepts `max`;
it keeps 4.0.8's data-URL image fix.

Models whose vocabulary stops at `high` now clamp these levels onto their own
ceiling instead of dropping the setting, so an explicit user choice is never
silently ignored and no unsupported value reaches a provider.

* fix(ai): keep extended effort for explicit custom relay aliases

A custom OpenAI-compatible provider with an explicit reasoning default is the
escape hatch for relay model IDs that match no capability pattern. That branch
still advertised only low/medium/high, so resolveVisibleCattyThinkingLevel
treated a configured `xhigh` or `max` as stale and downgraded it to `high`
before the request was built.

Advertise the full ladder there: the explicit provider default is already the
user's assertion that the alias accepts reasoning_effort, so it should also
carry the ceiling they picked. Unrecognized aliases without a default keep
advertising nothing.

* test(ai): verify max effort with the pinned OpenAI SDK

---------

Co-authored-by: Arcadia <57169358+RayYiHang@users.noreply.github.com>

* fix(ai): safely gate OSC 133 readiness across input and output

* fix(ai): recover OSC readiness after erased input padding

* fix(ai): distinguish typed spaces from erased terminal cells

* fix(ai): recognize multi-cell readline erase padding

* fix(ai): gate ambiguous blank edits and cover readline kill paths

* fix(ai): preserve mixed blanks and pending prompt redraws

* fix(terminal): count Unicode display cells for shell readiness

* fix(ai): keep ambiguous readline edits gated until a trusted lifecycle

* ci: retrigger checks after rebase onto main

Empty commit; tree identical to the rebased OSC 133 tip.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>
Co-authored-by: expoli <31023767+expoli@users.noreply.github.com>
Co-authored-by: shibo.li <shibo.li@d-robotics.cc>
Co-authored-by: GOUKI9999 <gouki9999@hotmail.com>
Co-authored-by: GOUKI9999 <GOUKI9999@users.noreply.github.com>
Co-authored-by: DeepFal <41166795+DeepFal@users.noreply.github.com>
Co-authored-by: HHXXYY123 <117068187+HHXXYY123@users.noreply.github.com>
Co-authored-by: Joseph Hau <hexart@gmail.com>
Co-authored-by: Arcadia <57169358+RayYiHang@users.noreply.github.com>
2026-11-06 00:33:03 +08:00
陈大猫 e17d7f01b6 perf(terminal): cache ligature font parses across terminals (#4126) 2026-11-04 04:01:36 -08:00
b87360783a perf: add opt-in startup timeline and real-app startup harness (#4114)
* perf: add opt-in startup timeline and real-app startup harness

- electron/perfTrace.cjs records first-occurrence startup marks in the main
  process when NETCATTY_PERF_TRACE=1 (a no-op otherwise), then writes the
  main-window renderer timeline (navigation, paint, netcatty:* marks, JS heap)
  and app.getAppMetrics() to JSON after the first renderer-ready signal.
- Marks: main.cjs entry / modules loaded / whenReady, main window creation,
  registerBridges and plugin-host start/end, loadURL, did-finish-load,
  renderer ready, window shown. The renderer adds three performance.mark()
  calls (entry evaluated, App mounted, renderer ready), also visible in
  DevTools.
- scripts/perf-startup.live.cjs (npm run perf:startup) launches the built app
  N times against a throwaway --user-data-dir profile with ssh:// deep-link
  registration disabled, optionally seeds synthetic hosts over CDP, and
  reports cold plus warm p50/p90.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(harness): add --profile option to reuse a startup profile

Lets the harness keep one profile across invocations, e.g. to compare warm
V8 code caches across builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: retrigger CI after flaky monitorRuntimeIsolation failure

Previous lint-and-test failed on known-preexisting
`useEffect is not defined` in monitorRuntimeIsolation.test.ts.

* chore: retrigger CI after monitorRuntimeIsolation useEffect flake

Known pre-existing flake: monitor controls test fails with
`useEffect is not defined`. Empty commit to re-run once.

* test(app): bind React hooks in monitorRuntimeIsolation VM sandbox

Extracting component declarations drops their React imports, leaving bare hooks such as useEffect undefined in the VM. Inject common React hooks and APIs while preserving explicit binding overrides to prevent intermittent monitor isolation failures in CI.

Stub the startup performance mark through App test bindings so the mount effect can execute without browser globals. Production components remain unchanged.

Validation: monitorRuntimeIsolation passed five consecutive runs. Plain npm run lint found 17 existing errors in git-ignored scratch files; excluding only _t.mjs and sim-4107.mjs passed with zero errors and eight existing warnings.

* chore: retrigger CI after plugin-contract runner cancel

Lint/schema passed; plugin-contract step cancelled by the runner
(not a test failure). Empty commit to re-queue.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: binaricat <binaricat@users.noreply.github.com>
2026-11-04 02:15:34 -08:00
csr588 7683874a4e feat(autocomplete): add zh-CN descriptions for touch (#4084) 2026-11-03 19:09:25 -08:00
陈大猫 d5a03606b7 feat: rebuild plugin UI with host rendering and standard settings (#4053)
* feat: rebuild plugin pages with host rendering and native settings

* fix(plugins): contain host-view errors, isolate editing, and migrate fixtures

* fix(plugins): migrate remaining permission fixture hosts
2026-10-28 23:08:31 -07:00
陈大猫 30a74f7c1d refactor(settings): simplify AI tool access configuration (#3957)
Move infrequent Tool Access / MCP / privacy / HTTP setup into focused dialogs; keep status and clear actions on the main page. Rebased onto current main after locale/settings conflicts.

Codex CLEAN (gpt-6.1-terra). CI lint-and-test green. Focused privacy/MCP/guidance tests passed on rebase tip. No release.
2026-10-27 20:59:11 -07:00
陈奇 25e0c834da fix(plugins): preserve global chat transcript in compact modal 2026-10-27 12:51:07 +00:00
陈奇 56fefdd03f fix(plugin-agent): separate durable conversations from revoked runtime actors 2026-10-27 09:03:40 +00:00
陈奇 f715a0764d feat(plugin-agent): wire core global conversations and minimal Chat sample 2026-10-27 08:49:54 +00:00
陈奇 5d2ff1cb86 Merge reviewed AI startup and themed sample while preserving permission policy assertions 2026-10-27 03:25:05 +00:00
陈奇 2eee561847 fix(plugins): bind scoped Ask receipts and revoke advanced activations 2026-10-27 03:17:53 +00:00
陈奇 e325742abf fix(example-assistant): follow host theme and accent environment 2026-10-27 02:22:20 +00:00
陈奇 24254ed737 Merge verified assistant selected-Note recovery into acceptance 2026-10-26 20:12:05 +00:00
陈奇 ba09461676 test(plugin-ai): verify repeated selected Note expiry recovery and update QA 2026-10-26 19:55:58 +00:00
陈奇 9e7c9df58e fix(example-assistant): explicitly recover expired selected Note references 2026-10-26 19:53:18 +00:00
陈奇 824c3838e9 Merge published AI source-budget and model-session fixes provisionally 2026-10-26 19:50:06 +00:00
陈奇 e1cb95ee87 fix(example-assistant): recover expired model sessions without inference retries 2026-10-26 19:23:22 +00:00
陈奇 2278681787 Merge verified settings and logging UI into acceptance candidate 2026-10-26 18:34:59 +00:00
陈奇 52d4af2a86 Merge verified plugin byte stream integration onto acceptance main 2026-10-26 18:34:02 +00:00
陈奇 dc6e645fab fix(plugins): prebundle permission panel helper in Vite development 2026-10-26 18:18:52 +00:00
陈大猫 8d826594d3 fix(ssh): support ChaCha20 in Electron crypto runtimes (#3992)
* fix(ssh): support ChaCha20 in Electron crypto runtimes

* test(ssh): verify packaged cipher transport across platforms

* test(ssh): isolate unused Electron UI APIs in packaged transport harness

* test(ssh): isolate packaged dependency resolution from the checkout
2026-10-27 01:59:11 +08:00
陈奇 bb2fc729b8 Merge commit 'c4433d4961ecf4a81bea92b0a14f47b4ec12f679' into feat/plugin-ai-context 2026-10-26 15:35:13 +00:00
陈奇 6cf931ac4d feat(plugin-ai): implement trusted renderer selection and model lifecycle 2026-10-26 15:07:38 +00:00
陈大猫 c4433d4961 feat(plugins): integrate source-bound byte streams and additive AI contract 2026-10-26 23:04:54 +08:00
陈奇 fdeeaf74bb feat(plugins): add validated data-only host color themes 2026-10-26 11:46:08 +00:00
陈大猫 efe5d4530d fix(win): kill leftover Netcatty.exe before NSIS install (#3946) (#3962)
NSIS customInit taskkill /F /IM Netcatty.exe (no /T) so orphaned external-MCP Netcatty.exe cannot lock $INSTDIR during in-app update.

Fixes #3946
2026-10-26 16:30:56 +08:00
陈奇 71d455d5de feat(plugins): add host text dialogs and explicit clipboard sharing 2026-10-26 08:05:02 +00:00
Codex f4a6b205ad feat(plugins): add versioned offline brand icons and author guidance 2026-10-26 05:26:25 +00:00
Codex 3efabf2b0f fix(plugins): expose a visible Starter panel and bundle shared browser helpers 2026-10-26 03:41:21 +00:00
陈大猫 3e9ac6a36b fix(mcp): unpack re2js for external MCP bridge (#3911)
Unpack node_modules/re2js so external MCP bridge scripts under app.asar.unpacked can require it.

Fixes #3910
2026-10-25 00:43:20 +08:00
陈大猫 43d714b73c feat(serial): support local Windows named pipe consoles (#3894)
* feat(serial): support local Windows named pipe consoles

* test(serial): synchronize pipe acceptance and cover saved-host form

* fix(serial): preserve named-pipe paths with spaces when editing hosts

* test(serial): verify Windows pipes through native Electron IPC

* test(serial): keep Electron listener callbacks in renderer context

* test(serial): cover busy pipe cancellation and bounded slow-reader writes

* test(serial): construct single-instance fixture pipe path correctly
2026-10-24 12:56:58 +08:00
陈大猫 520c99da77 fix: preserve concurrent connection log updates across windows (#3891)
* fix: preserve concurrent connection log updates across windows

* fix: retire durable log metadata after partial replay writes
2026-10-24 10:35:51 +08:00
陈大猫 9766f21ecf feat(monitor): shared Linux disk I/O and one-second refresh (#3870) 2026-10-23 12:19:10 +08:00
38ff20ca80 docs: lossless WebP README screenshots and Trendshift badges (#3812)
* docs: losslessly shrink README images and mirror Trendshift badge

Recompress local images linked from the READMEs only when the file gets
smaller and decoded pixels stay identical. Copy the Trendshift badge from
README.md into the zh-CN, zh-TW, and ja-JP READMEs.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* docs: replace README screenshots with lossless WebP

Store the ten README screenshots as lossless WebP under docs/assets/readme
and point every language README at those files. Keep them out of app.asar.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-21 21:20:02 +08:00
陈大猫 b91b00ee6b fix: load shared container runtime in Vite renderer (#3809) 2026-10-21 11:19:32 +08:00
ebc1ac7598 Add container runtime switcher and Kubernetes pods in the Docker panel (#3791)
* feat(system-manager): add container runtime switcher and kubectl pods

Keep the existing Docker tab and select docker, podman, nerdctl, or
Kubernetes from runtimes detected on the host. Kubernetes replaces the
switcher with context and namespace selectors in the same panel.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): address container runtime review findings

Move the runtime adapter into the domain layer, keep a context namespace
when namespace listing is forbidden, and emit PowerShell or cmd startup
commands for Kubernetes shell, logs, and describe.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): cover Windows container popups and stale namespaces

Run container shell and logs through the host shell's inspect wrapper on
PowerShell and cmd, and reset the namespace when a remembered Kubernetes
context is no longer present.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): parse kubectl JSON after banners and scope inspect cache

Skip bracketed banners before kubectl JSON, and include the container
runtime in image and container inspect cache keys.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): parse Podman image lists emitted as JSON arrays

Podman applies {{json .}} to the whole image slice. Accept that array,
including Names and byte sizes, and keep Docker's JSON lines.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): parse Podman stats arrays

Accept Podman's stats JSON array and its id/cpu_percent fields, and keep
Docker's one-stat-per-line output.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): list every Podman image tag

Expand each name in a Podman image's Names array into its own row so
extra tags stay visible and searchable.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): quote kubectl list and delete for the session shell

Namespace, pod, and delete commands follow PowerShell or cmd quoting when
the session shell is not POSIX, so apostrophes in a context name survive
local PowerShell.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): drop renamed runtime launch commands from shell history

Container popups now probe with _nc_rt_err and also cover podman, nerdctl,
and kubectl. History sanitizers still required _nc_docker_err, so those
launches stayed in persisted history. Confirm-dialog resets also follow the
selected runtime.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): sum Podman interface stats and quote kubectl for the executor

Podman {{json .}} stats keep per-interface counters on Network, so the
details panel now sums RxBytes and TxBytes. Background kubectl runs in
powershell.exe on local Windows even when the terminal is cmd, so those
commands are quoted for that executor instead of the interactive shell.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): remove a selected image tag instead of the shared id

Podman rows that share one image id were all deleted with rmi <id>, which
either fails while other tags remain or drops every name. Tagged rows now
run rmi repository:tag; dangling images still use the id.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): reject cmd kubectl arguments that contain percent signs

Interactive cmd.exe expands %VAR% inside double quotes, so a context such
as prod-%USERNAME% would be rewritten before kubectl sees it. Cmd popups
now refuse that argument. PowerShell quoting still preserves the percent.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* refactor(system-manager): move runtime selection into an application hook

Persisted source, context, and namespace state plus the kubectl discovery
effects now live in useContainerRuntimeSelection. DockerManagerTab only
renders the switcher and panels.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): offer kubectl shell only for running containers

Succeeded and failed pods have no running container, and a completed init
container cannot accept exec. Shell targets now come from container
statuses that are running; logs still include init containers.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): keep Windows popup commands out of shell history

PowerShell and cmd container popups inspect then run, and kubectl popups
are typed directly. History filtering only matched the POSIX sh -c wrapper,
so those launches were stored as user commands.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): mark Windows popups instead of matching kubectl syntax

Filtering kubectl --context logs and describe also dropped commands the
user typed. Windows popups now carry __NC_POPUP__, and ordinary kubectl
lines stay in history.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): reject cmd arguments that contain delayed-expansion marks

cmd.exe with /V:ON expands !VAR! inside double quotes. Contexts such as
prod!USER! are refused for cmd popups, same as percent signs.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): hide raw nerdctl and unreachable cluster errors

Only offer nerdctl after a short info probe succeeds, or when the failure is a socket permission error that sudo can still recover. Map dead containerd and unreachable kube API errors to short localized messages and keep the CLI text in the log.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): show terminating pods and ephemeral containers

A deletion timestamp is Terminating even while the phase is still Running. Ephemeral containers are included in the logs list, and a running one is a shell target.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): keep the runtime select on Kubernetes and avoid covering it

Kubernetes uses the same compact source menu as Docker, with context and namespace beside it. Shell popups open in the free part of the window so they no longer sit on top of the pod badges.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): restore the saved engine when Kubernetes disappears

A host that was on Podman before Kubernetes keeps Podman if kubectl is gone, instead of falling back to Docker and overwriting that choice.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): avoid the visible system panel when others are parked

A hidden parking-host panel can be the first system-manager match and
has a zero-size rect. Scan every match and pass the first panel that
actually has size so the shell popup still sits beside the open panel.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): keep a runtime choice across probe gaps and show podman ports

A failed nerdctl or kubectl probe was saved as the fallback engine, so the
previous choice never returned. Reconciliation now restores the stored source
when it is detected again, and automatic fallbacks are not written back.
Podman ps port mappings and exposed ports are formatted into the same display
string the container detail strip already shows.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): show a pod's Evicted reason

Pod status.reason is the eviction signal, while the phase stays Failed and
the container often reports Error. Use that reason after a container waiting
state and before the generic phase so the badge stays Evicted.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): keep an avoided shell popup inside the work area

When the source window sits partly off-screen, clamping a slot back onto
the display could cover the system panel, and the fallback then used the
raw slot origin. Place the popup in the on-screen part of the slot instead.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(system-manager): left-align the runtime selector row

The Kubernetes context, namespace, and runtime selects sat on the right of
the header, so they did not line up with the search field. Keep the same
padding, let the context select fill the remaining width, and leave the
runtime and namespace selects at their natural width.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-20 21:06:54 +08:00
cd7f182401 feat(ssh): host TOTP with QR scan and MFA auto-fill (#3323) (#3788)
* feat(ssh): add per-host TOTP with QR scan and MFA auto-fill

Store an optional otpauth TOTP secret on the host, encrypt and sync it
like a password, and auto-submit it once when a keyboard-interactive
prompt matches JumpServer or PAM TOTP text.

Closes #3323
Refs #3415

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): address Codex TOTP review findings

Reset authenticator editor state when the host changes, match custom
prompts with RE2, offer keyboard-interactive during connection tests
when TOTP is configured, and drop the secret when a host becomes a plugin.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): fill TOTP from the active prompt and ship re2js

Attach the connection's otpauth URI to the keyboard-interactive dialog
so Fill current code uses that attempt, including unsaved hosts. Move
re2js into production dependencies so packaged builds can match custom prompts.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): ship the shared TOTP runtime in packaged builds

electron-builder allowlists domain CommonJS modules individually, and
sshAuthHelper requires domain/totp.shared.cjs at load.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): save a pasted TOTP secret and ignore stale QR imports

Host save waits for the authenticator draft to validate before writing
the host. QR import completions from the previous host no longer change
busy state or errors on the newly selected host.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* test(host): ignore TOTP copy in the proxy command summary check

The assertion was matching the word secret in the authenticator UI.
It now checks that the ProxyCommand token itself is not rendered.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* feat(ssh): simplify host TOTP setup and type codes into the terminal

Move authenticator setup into a dialog so the host form keeps a single setting row, show keyboard-interactive prompts as field labels, and add a terminal context-menu action that types the current code without Enter.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): keep TOTP fill inside the auth dialog

Remove the terminal context-menu action. Offer the verification-code button on each non-password prompt, highlight OTP prompts, and never write the code into the password field.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): keep MFA instructions and scan every screen

Show keyboard-interactive name and instructions as plain text when they are not already the field label, and scan each unmatched desktop source once instead of repeating the first display.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): keep multiline MFA prompt directions

Show lines before the last keyboard-interactive prompt as plain-text guidance, and keep that last line as the field label.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): scan TOTP QR codes at each display's full size

Request a thumbnail as wide as the widest display and as tall as the tallest, so a portrait screen is not scaled into a landscape frame.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* fix(ssh): keep TOTP fill off menu prompts

Judge the fill action from the last prompt line and the shared menu, SMS, and confirmation exclusion list. Use a secondary button on the label row, and simplify the host authenticator row.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-20 19:22:05 +08:00
陈大猫 04d71be651 feat(autocomplete): show zh-TW descriptions for common commands (#3780)
Follow-up to #3773: Traditional Chinese (Taiwan) autocomplete description dictionary via OpenCC s2twp, lazy-loaded like zh-CN.

Closes #3157.

Real-app smoke PASS on 4d2bd32e.
2026-10-20 12:41:43 +08:00
a3083a4ac2 feat(autocomplete): show zh-CN descriptions for common commands (#3773)
Overlay bundled Fig description strings when the UI locale is zh-CN.
Cached specs stay in English, and missing entries fall back to the original text.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-20 11:59:42 +08:00
陈大猫 a622d953ad fix(terminal): preserve IIP sequence type with external metadata (#3762) 2026-10-20 01:17:10 +08:00
陈大猫 2ba045c8e7 fix: preserve inline image tile stride across resize 2026-10-19 21:51:46 +08:00
aa597135b1 ci: narrow spam-comment filter and relay comment edits (#3752)
* ci: hide only risky download links in issue comments

Stop deleting comments that merely mention an archive or executable
name. Match http(s) URLs whose path contains a risky extension, skip
the issue author as well as maintainers and bots, and minimize matches
as spam instead of deleting them.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

* ci: relay edited issue comments with truncated bodies

Forward issue_comment edited events and include comment identity,
association, timestamps, and bodies clipped to 4000 characters.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-19 16:05:29 +08:00
陈大猫 f9230e76c3 ci: remove GitHub Actions AI automation (#3734) 2026-10-18 19:17:27 +08:00
陈大猫 9ce0b59583 docs(release): prepare v1.1.84 highlights (#3729) 2026-10-18 16:40:54 +08:00
陈大猫 ef4caf8b08 chore(packaging): omit duplicate renderer and unused native files (#3726)
Keep renderer-only libraries available as development dependencies so Vite
bundles their runtime code without shipping their dependency trees again.
Exclude native compilation helpers from packaged resources, and prune
node-pty/serialport prebuilds for other platforms and architectures while
preserving matching universal binaries and rebuilt runtime files.

Validation:
- npm run lint; 33 packaging tests pass.
- npm run pack:dir, real packaged application UI, and 29 packaged runtime
  smoke checks pass (SDK imports, PTY, serial port listing, SQLite query).
- All 999 UI files retain their code/content; differences are generated
  asset references and the embedded dependency classification metadata.
- Dependency versions are unchanged.
- macOS arm64 app: 394,959,135 -> 360,740,625 bytes.
- macOS arm64 ZIP: 150,847,369 -> 143,151,622 bytes; ZIP integrity passes.
- Full suite: 14,035 pass, 36 skipped, 5 fail. The four FIDO helper failures
  and transfer FileHandle-GC failure reproduce on unchanged main with Node 26.
2026-10-18 15:26:58 +08:00
陈大猫 f63be66ede fix(sftp): keep large file panes responsive when opening and hiding (#3724)
* fix(sftp): bound file panes before measurement and while hidden

* fix(sftp): clamp retained windows after background listing updates
2026-10-18 14:01:26 +08:00
陈大猫 7c371372a3 fix(vault): skip redundant ASCII host-field pinyin matching (#3721) 2026-10-18 13:20:46 +08:00
陈大猫 f26bd5fa68 fix(terminal): avoid ASCII pinyin work during argument completion (#3717)
* fix(terminal): skip ASCII pinyin work during argument completion

* fix(terminal): retain compact ASCII completion prefixes
2026-10-18 12:58:12 +08:00