synabun 2026.4.25-beta.2 → 2026.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -175
- package/mcp-server/package.json +8 -2
- package/neural-interface/package.json +5 -1
- package/neural-interface/public/shared/cdx/cdx-panel.js +56 -48
- package/neural-interface/public/shared/cdx/cdx-styles.js +58 -26
- package/neural-interface/public/shared/cdx/cdx-tabs.js +3 -0
- package/neural-interface/public/shared/ocp/ocp-panel.js +59 -48
- package/neural-interface/public/shared/ocp/ocp-styles.js +55 -25
- package/neural-interface/public/shared/ui-claude-panel.js +114 -73
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,178 +2,128 @@
|
|
|
2
2
|
|
|
3
3
|
Raw internal changelogs are archived in [changelog/](changelog/).
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- **
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **Banner
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
|
|
60
|
-
##
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- **
|
|
64
|
-
- **Codex** —
|
|
65
|
-
- **
|
|
66
|
-
- **
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- **
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- **
|
|
107
|
-
- **
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
- **
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
- **
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- **
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- **
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
- **`/api/mcp/install/github` soft-fails local dep errors** — In `neural-interface/server.js`, the install endpoint returned HTTP 500 and aborted the whole flow when `pip install -r requirements.txt` or `npm install` errored, even though the clone + detection already succeeded. Reproducible with `AminForou/mcp-gsc`: launch command was `uvx mcp-search-console` (self-fetching) but `pip` missing on PATH made the endpoint bail before registry write. Now install failures attach an `installWarning` field to the 200 response — the server still registers, the user sees a `⚠` line in the GitHub install form, and deps can be fixed manually.
|
|
131
|
-
- **Self-fetcher runtimes skip local install** — Added `commandSelfFetches(detected)` in `neural-interface/lib/mcp-installer.js` that matches `uvx` / `uv run` / `npx` / `pipx` / `bunx` / `pnpm dlx` / `yarn dlx`. When true, `installDependencies` is never called — the server is registered with `install.skipped = true, reason: "command '<cmd>' self-fetches at runtime"` and the UI shows a `⚙ Skipped local deps` hint.
|
|
132
|
-
|
|
133
|
-
### Added — Cross-OS Preflight for Self-Fetching MCP Runtimes
|
|
134
|
-
- **`checkCommandOnPath(cmd)` in `neural-interface/lib/mcp-installer.js`** — OS-agnostic PATH probe. Uses `where <cmd>` on Windows (which resolves `.exe` via PATHEXT), `sh -c 'command -v <cmd>'` on Unix. 5s timeout guard. Returns a boolean.
|
|
135
|
-
- **`checkSelfFetcherAvailable(detected)` preflight** — Called by the install endpoint when `commandSelfFetches` is true. Returns `{ available, command, installHint }`. Missing binary attaches an OS-specific install command to `installWarning` (e.g. *"'uvx' not found on PATH. Install: brew install uv. Server registered anyway — will fail at launch until installed."*).
|
|
136
|
-
- **`SELF_FETCHER_INSTALL_HINTS` map** — Per-OS install commands for `uvx`/`uv`/`pipx`/`npx`/`bunx`/`bun`/`pnpm`/`yarn`. Mac uses `brew`, Linux uses official curl installers, Windows uses `winget` or PowerShell `irm`. `installHintFor(cmd)` picks the row based on `process.platform`. Ensures fresh SynaBun installs on Windows/Mac/Linux get actionable guidance instead of a silent launch failure.
|
|
137
|
-
|
|
138
|
-
### Changed — Codex Sidepanel Reasoning and Response Controls
|
|
139
|
-
- **Expanded Codex reasoning effort support** — Updated `neural-interface/public/shared/cdx/cdx-icons.js`, `cdx-panel.js`, `cdx-styles.js`, and `cdx-tabs.js` so the Codex footer effort toggle now supports the full current set of sidepanel overrides: `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`. The toolbar indicator now renders five effort dots, includes visual states for `minimal` and `xhigh`, and `/effort` validates input while treating `default` and `none` as no per-turn override.
|
|
140
|
-
- **Exposed response-control config keys** — Added `model_reasoning_summary` and `model_verbosity` to `CODEX_CONFIG_KEYS` in `neural-interface/server.js`, then surfaced both in the Codex settings form and `/status` output in `neural-interface/public/shared/cdx/cdx-tabs.js`. Settings now support reasoning summary choices `auto`, `concise`, `detailed`, and `none`, plus response verbosity choices `low`, `medium`, and `high`.
|
|
141
|
-
|
|
142
|
-
### Fixed — Codex Sidepanel Session Cross-Talk
|
|
143
|
-
- **Per-tab WebSocket identity** — `sendSocket()` in `neural-interface/public/shared/cdx/cdx-tabs.js` now stamps every Codex sidepanel WebSocket message with the bound tab's `sessionId`, so bootstrap, query, reattach, model, interrupt, and compact traffic is routed to the owning tab instead of only the shared `windowId`.
|
|
144
|
-
- **Strict orphan reattach keys** — `_codexOrphanKey()` in `neural-interface/server.js` now requires both `windowId` and `sessionId`, and the reattach handler returns `reattach_result: false` when the tab id is missing. The close handler refuses to create window-wide orphans and kills sessionless processes, preventing a newly opened tab from replaying another tab's buffered tool-call events.
|
|
145
|
-
- **Reconnect state cleanup** — `onClose()` sets `pendingReattach` only for abnormal Codex socket closes and `onReattachResult()` clears it, preserving correct orphan recovery for the original tab without leaking state to other sessions.
|
|
146
|
-
|
|
147
|
-
### Fixed — Codex Sidepanel Active-Tab Stop Scope
|
|
148
|
-
- **Stop button aborted every running Codex tab** — The shared composer stop state in `syncInputEnabled()` was keyed off `_tabs.some((t) => t?.running)`, and `cdx-panel.js` wired click/Escape to `interruptAllTabs()`, so stopping one visible tab sent `interrupt` and delayed `force_kill` to every running tab WebSocket. The handlers now call `interruptTurn()` only when `activeTab()?.running`, the stop icon is scoped to the active tab, and `interruptTab()` sends session-scoped stop messages for the bound tab.
|
|
149
|
-
- **Stale force-kill retries could outlive successful interrupts** — `onInterruptAck()` now clears each tab's `_abortRetry`, preventing the fallback timer from killing a Codex app-server after the server already acknowledged an interrupt.
|
|
150
|
-
- **Server accepted unscoped stop messages** — `validateCodexControlMessage()` in `server.js` rejects `interrupt`/`force_kill` without a matching `sessionId` or with a mismatched `threadId`, while `forceKillCodexChild()` logs pid/session/thread/reason before terminating the app-server.
|
|
151
|
-
|
|
152
|
-
### Fixed — Codex Plan Mode Handoff
|
|
153
|
-
- **Post-plan actions no longer get skipped** — `dispatchPrompt()` in `neural-interface/public/shared/cdx/cdx-tabs.js` now marks sent plan turns with `planMode`, `planTurnActive`, and `lastPlanTurnId`, so `handleNotify('turn/completed')` captures the completed plan even if the toolbar `planMode` flag has already changed by the time the turn finishes.
|
|
154
|
-
- **Approval gate persists across tab state** — Added `planApprovalPending` / `planTurnActive` persistence through `snapshotBoundState()`, `restoreBoundState()`, `commitBoundState()`, `bindTabState()`, `createTab()`, and `saveTabs()`, and blocks `sendPrompt()` / `advanceQueue()` until the user chooses Continue, Compact, or Edit.
|
|
155
|
-
- **Continue/Edit paths clear or restore the handoff correctly** — `renderPostPlanActions()` in `cdx-render.js` clears pending plan state before dispatching the non-plan implementation prompt and restores it if send fails, while `cdx-panel.js` marks edited plan saves as pending approval again.
|
|
156
|
-
- **Plan turns run read-only server-side** — `server.js` passes the client `planMode` flag into `startTurn()`, detects explicit `[PLAN MODE]` prompts with `isCodexPlanModePrompt()`, and forces `buildCodexSandboxPolicy(..., 'read-only')` for plan turns.
|
|
157
|
-
- **Prompt text now enforces the sidepanel handoff** — `CODEX_PLAN_MODE_PREFIXES` in `cdx-icons.js` and `server.js` now tells Codex to stop after producing the plan and wait for the sidepanel’s Edit, Compact, or Continue choice.
|
|
158
|
-
|
|
159
|
-
### Fixed — CLI/TUI Rendering Corruption (Terminal)
|
|
160
|
-
- **Stable xterm renderer path** — `loadXterm()` in `neural-interface/public/shared/ui-terminal.js` now intentionally skips `@xterm/addon-webgl` and `@xterm/addon-canvas`, leaving CLI/TUI windows on xterm's DOM renderer. This avoids WebGL glyph atlas/context corruption seen after multiple long-lived floating CLI sessions.
|
|
161
|
-
- **Serialized terminal writes** — Added `_createTerminalWriter()` with 64KB chunking and `term.write()` callback backpressure, then routed fresh session, reconnect, and WebSocket reconnect output through it. This prevents large PTY bursts from piling up unbounded parser/render work during long Codex/Claude/Gemini sessions.
|
|
162
|
-
- **Snapshot-based reconnects** — `createTerminalSession()` in `neural-interface/server.js` now maintains a capped `VTermBuffer` per PTY, updates it on output and resize, and `/ws/terminal/:id` sends a synthesized `snapshot` payload instead of replaying an arbitrary raw ANSI tail. The snapshot includes a plain-text companion so `_sendOnceReady()` can still detect ready prompts without waiting for its fallback timer.
|
|
163
|
-
- **Virtual terminal snapshot support** — `neural-interface/public/shared/vterm-buffer.js` now exposes `useAltBuffer` and accepts a per-instance scrollback cap, letting live PTY sessions keep only the rows needed for reconnect recovery while preserving the larger default for existing capture use cases.
|
|
164
|
-
|
|
165
|
-
## 2026-04-22
|
|
166
|
-
|
|
167
|
-
### Added — Smart "Install from GitHub" (MCP + Claude Code Plugins Auto-Route)
|
|
168
|
-
- **Repo classifier** — New `classifyRepo(repoPath)` in `neural-interface/lib/mcp-installer.js` runs BEFORE `detectServer()` and returns `{ kind: 'mcp' | 'claude-plugin' | 'unknown' }`. Prefers MCP when both markers exist, emits `alsoClaudePlugin` hint for mixed repos, scans for codex/gemini/cursor/windsurf adjacency signals.
|
|
169
|
-
- **Claude Code plugin installer** — New `neural-interface/lib/plugin-installer.js` with `installClaudePlugin()`, `uninstallClaudePlugin()`, `listSynabunPlugins()`. Symlinks the cloned repo into `~/.claude/plugins/marketplaces/<name>` and `~/.claude/plugins/cache/<marketplace>/<plugin>/unknown`, appends SynaBun-tagged entries to `known_marketplaces.json` + `installed_plugins.json` (dedupe by `scope:'user' + installPath`), captures `gitCommitSha` via `git rev-parse HEAD`.
|
|
170
|
-
- **`POST /api/mcp/install/github` branches on classification** — After clone, `kind: 'claude-plugin'` skips MCP detection/deps install and calls `installClaudePlugin`; `kind: 'unknown'` returns 422 with hints list; MCP path unchanged. New endpoints `GET /api/plugins/list` and `DELETE /api/plugins/:marketplace?pluginName=&purgeFiles=1`.
|
|
171
|
-
- **Settings UI rename + plugin list** — "+ Add Server" button renamed to "+ Install from GitHub" in `neural-interface/public/shared/ui-settings.js`. New "Claude Code Plugins" subsection with per-row Uninstall. Install handler swaps the form into "plugin mode" (hides Type/Command/Args/env/platforms/Save) when response is `kind: 'claude-plugin'` since the plugin is already activated server-side.
|
|
172
|
-
|
|
173
|
-
### Added — Installed MCPs Sub-Tab (Cross-CLI Unified View)
|
|
174
|
-
- **`GET /api/mcp/installed-all` endpoint** — Aggregates every externally-installed MCP across SynaBun registry (non-builtin), `~/.claude.json`, `~/.gemini/settings.json`, `~/.codex/config.toml` (regex `^\[mcp_servers\.([^\].]+)\]$` excludes nested `.tools.` sections), and OpenCode's `mcp.*` config. Merges by server name; `registeredWith` is a sorted array of CLI keys.
|
|
175
|
-
- **Sub-tab navigation inside Settings → MCP** — New `.stg-mcp-page-tabs` at top of the page swaps between **Profiles** (existing matrix + always-on + per-profile External Servers toggles) and **Installed** (new cross-CLI list + Claude Code Plugins + Install form). SynaBun's built-in tool groups are excluded from the Installed view.
|
|
176
|
-
- **Per-CLI badges** — Each row in the Installed list shows compact `SB/CC/OC/CX/GE` badges indicating which CLIs have the server registered. Styles added for `.stg-mcp-page-tabs`, `.stg-mcp-page-tab.active`, `.stg-mx-ext-cli-badge`.
|
|
177
|
-
|
|
178
|
-
### Added — Cross-CLI Uninstall Button on Installed-Tab Rows
|
|
179
|
-
- **One-click uninstall across all registered CLIs** — Each row in the Installed sub-tab carries a `×` button with `data-registered` containing its precise CLI list. Click handler confirms using the badge labels (e.g., "Remove foo from CC, OC, CX?"), then: if `registeredWith` includes `synabun` → `DELETE /api/mcp/registry/servers/:name`; for every remaining CLI key → `DELETE /api/mcp/sync` with `{ name, platforms: cliList }`. Refreshes both Installed list and the Profiles-tab External Servers list afterward. Avoids pointless 404 churn by only hitting CLIs that actually have the server.
|
|
5
|
+
## New Features
|
|
6
|
+
|
|
7
|
+
- **Dedicated Schedules studio** — Standalone window with Cron, Timers, Groups, and Activity tabs. Pulls schedule management out of the Automation Studio detail pane and adds cron presets, timezone, day selection, group assignment, and per-schedule launch overrides (profile, model, effort, MCP profile, browser).
|
|
8
|
+
- **Schedule groups** — Group recurring schedules with full CRUD endpoints and ordering. Group membership and launch metadata persist with each schedule.
|
|
9
|
+
- **Claude Code plugin install from MCP UI** — Settings > MCP > "+ Install from GitHub" auto-detects whether a repo is an MCP server or a Claude Code plugin and installs via the `claude plugin marketplace add` + `claude plugin install` flow. Plugin slash commands surface in the sidepanel `/` autocomplete with a `(plugin: <name>)` suffix.
|
|
10
|
+
- **Installed MCPs cross-CLI sub-tab** — Settings > MCP now has a unified "Installed" view aggregating servers across SynaBun, Claude Code, Gemini, Codex, and OpenCode with per-CLI badges (SB/CC/OC/CX/GE) and one-click cross-CLI uninstall.
|
|
11
|
+
- **CLI-not-installed banner across sidepanels** — Claude, Codex, and OpenCode panels detect missing or broken CLI binaries (including spawn-failure patterns like `ENOENT` or `app-server exited`) and render a sticky orange banner with the install command, docs link, and Re-check button. Send button is gated until the CLI is detected.
|
|
12
|
+
- **Workspace trust flow for Windows** — Amber **Trust workspace** prompt in both the file explorer and onboarding wizard when Windows flags a repo with "dubious ownership."
|
|
13
|
+
- **One-click CLI install toggles** — Onboarding Step 5 now exposes live install toggles for Claude Code, Gemini CLI, Codex CLI, and OpenCode. Cursor/Windsurf live in a collapsible JSON section.
|
|
14
|
+
- **OpenCode panel greeting** — Fully isolated greeting config for OpenCode, separate from Claude and Codex state.
|
|
15
|
+
- **Multi-CLI Resume with provider picker** — Resume dropdown switches between Claude, Codex, Gemini, and OpenCode sessions via a pill bar.
|
|
16
|
+
- **Hybrid session content search** — SQLite FTS5 full-body search combined with semantic embeddings (Claude). Sidepanel session menus surface match-kind pills ("strong match," "related") with highlighted snippets.
|
|
17
|
+
- **GitHub MCP installer** — Settings > MCP > "From GitHub" clones a repo, auto-detects MCP config, installs dependencies, and pre-fills the form. Includes central env vault, auto profile registration, and sensitive-key masking.
|
|
18
|
+
- **Multi-runtime skills** — `/synabun` and `/leonardo` install independently to Claude, Codex, and OpenCode via per-target chips in Settings.
|
|
19
|
+
|
|
20
|
+
## Schedules
|
|
21
|
+
|
|
22
|
+
- **Live ticking countdown on schedule cards** — Replaced static "Next: 4h" with a 1-second-tick adaptive countdown (`2d 3h`, `4h 23m`, `15m 42s`, `Overdue`). Tabular-nums styling prevents layout jitter.
|
|
23
|
+
- **Edit prompt shortcut on schedule cards** — New script icon between Run Now and Edit Schedule jumps directly to the linked automation template in detail view.
|
|
24
|
+
- **Quick timer controls** — Quick timer creation, run-now actions, active timer countdowns, saved schedule timers, plus pause/resume, trigger, edit, and delete from inside the schedules window.
|
|
25
|
+
- **Automations submenu expanded** — Added Schedules, New Cron Schedule, Quick Timer, and Schedule Groups menu items that open the dedicated schedules window directly to the requested flow.
|
|
26
|
+
|
|
27
|
+
## Front End / Sidepanel UI
|
|
28
|
+
|
|
29
|
+
- **Sidepanel tray placeholder pills** — Saved Claude / Codex / OpenCode tabs render as minimized pills before the provider panel has been lazy-loaded; clicking opens the matching provider with the selected tab preserved.
|
|
30
|
+
- **Codex effort and response controls expanded** — Codex footer effort toggle now supports `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`. Settings expose `model_reasoning_summary` (auto/concise/detailed/none) and `model_verbosity` (low/medium/high).
|
|
31
|
+
- **Slash-hints dropdown polish** — Larger 320 px max-height, no longer clipped by the input wrapper, and `overscroll-behavior: contain` so wheel scroll inside the dropdown doesn't bubble to the message list. First `/` keystroke now reliably shows the dropdown even before the skills cache loads.
|
|
32
|
+
- **Unified context gauge** — Single-fill bar across Claude, Codex, and OpenCode with threshold-based color (gray → amber >60% → red >80%). OpenCode gauge now uses model-specific context windows with full token breakdown.
|
|
33
|
+
- **OpenCode agents drawer redesign** — Gradient backdrop, color-coded subagent type chips, shimmering init placeholder, per-agent ✕ abort button, and amber stop button with pulse. Expandable inline step view with live in-place elapsed timer.
|
|
34
|
+
- **OpenCode tool activity dock** — Floating left-edge dock lists every tool call; click a row to scroll to the card. `Ctrl/Cmd+Shift+A` toggles.
|
|
35
|
+
- **OpenCode permission cards** — Now show the actual tool (bash/edit/write/read/webfetch), command or path, matched patterns, and expandable diff instead of a generic "Permission request" chip.
|
|
36
|
+
- **Claude sidepanel view mode picker** — Normal / Transcript / Focus now visible on the project bar instead of only via `Ctrl+O`.
|
|
37
|
+
- **TODO widget → left-edge slide-out drawer (Claude)** — Tag docks on the edge when todos exist; click to expand. Per-tab persistence; `Ctrl+T` toggles.
|
|
38
|
+
- **New session modal rebuilt** — Centered overlay on "+" with Project, Branch, and Name fields. Branch checkout defers to Save. Applies to Claude, Codex, and OpenCode.
|
|
39
|
+
- **Codex sidepanel feature parity** — Model selector, effort toggle, plan mode, auto-accept, image attachments, file-path chips, hold-to-talk voice, slash hints, FIFO prompt queue with drag-reorder, copy buttons, thread fork/archive, settings overlay, stall detection, auto-compact. Fully isolated from the Claude panel.
|
|
40
|
+
- **Session menu overhaul (Claude + Codex)** — Sticky search, "Hide empty" / "Archived" filters, per-item archive/unarchive, Claude branch dropdown, and infinite scroll.
|
|
41
|
+
- **Multi-question cards** — All questions from one `AskUserQuestion` call render in a single card with multi-select, "Other" custom-answer inputs, and `Submit (x/y)` progress (OpenCode + Codex + Claude).
|
|
42
|
+
- **Syntax highlighting, file-path linkification, language labels, hover-only copy buttons** — All across OpenCode code blocks.
|
|
43
|
+
- **Throttled streaming + live thinking** — 32ms render throttle during streaming; thinking blocks update in real time.
|
|
44
|
+
- **MCP Settings toggle matrix** — Replaced drag-and-drop pill system with a clickable permission grid: tool groups as rows, profiles as columns.
|
|
45
|
+
- **Universal MCP add form** — Smart-paste textarea auto-detects 5 config formats; multi-platform sync registers a server to every selected CLI in one action.
|
|
46
|
+
- **Capability badges & filters (OpenCode)** — 9 colored pills (Reason, Tools, Vision, Audio, Video, PDF, ImgGen, TTS, Files) per model, with filter pills in chat dropdown, Models, and Providers settings.
|
|
47
|
+
- **AskUserQuestion polish** — Consistent card spacing, always-available custom-answer input, multi-select square checkboxes, and "Select all that apply" hint text.
|
|
48
|
+
|
|
49
|
+
## Terminal & Rendering
|
|
50
|
+
|
|
51
|
+
- **Stable xterm DOM renderer for long CLI sessions** — Skips `addon-webgl` / `addon-canvas` to avoid WebGL atlas/context corruption seen across multiple long-lived floating CLI sessions. Output writes are serialized into 64 KB chunks with backpressure.
|
|
52
|
+
- **Snapshot-based reconnects** — Per-PTY virtual-terminal buffer (with alt-buffer support) replaces arbitrary raw-ANSI tail replay on reconnect, so reconnects render correctly without replaying mid-frame escape sequences.
|
|
53
|
+
- **Single xterm renderer** — Collapsed the legacy HTML terminal renderer (~2,400 lines removed). All CLI profiles route through xterm + WebGL with Canvas fallback.
|
|
54
|
+
- **Long-session rendering glitches fixed** — Periodic texture-atlas pruning, coalesced resize fits, 64 KB output-burst cap, ordered WebGL dispose on close, Windows ConPTY hint, and post-open fit+refresh for fresh and resumed sessions.
|
|
55
|
+
- **Terminal tab close/detach reliability** — Enlarged hit targets and suppressed redundant re-renders so the × and detach icons land during CLI streaming.
|
|
56
|
+
- **Banner no longer doubles on reconnect** — PTY buffer replay now resets the xterm before paint. Ink splash screens render once.
|
|
57
|
+
- **Boot flicker eliminated** — PTY launches at the real viewport size instead of 120×30, removing the SIGWINCH redraw and stray zsh `%`.
|
|
58
|
+
- **Floating terminal Escape forwarding** — Raw `\x1b` now reaches OpenCode when the floating window is focused, restoring cancel/close behavior.
|
|
59
|
+
|
|
60
|
+
## Automation Studio
|
|
61
|
+
|
|
62
|
+
- **Loop launch no longer hangs on orphan Chrome** — Pre-launch sweep kills stale `user-data-dir` Chromiums (Unix `pgrep -f` + `SIGKILL`, Windows PowerShell + `taskkill`) before relaunching. A 45 s `launchPersistentContext` timeout acts as a final safety net.
|
|
63
|
+
- **Loop tab isolation hardened** — `SYNABUN_BROWSER_TAB` env now wins over caller-supplied `tabId` so an agent or model can't accidentally leak into another loop's tab. Removed the active-tab churn from concurrent loop launches.
|
|
64
|
+
- **Codex loop fast-fail detection** — Iteration sentinel now captures exit code and per-iter duration. Three consecutive non-zero exits or sub-30 s iterations stop the loop with `fast-fail-rc` / `fast-fail-duration` instead of running ghost iterations.
|
|
65
|
+
- **Zombie loop file fix** — PTY exit handler now marks the on-disk loop file `active: false` with `stoppedReason: 'pty-exit-<code>'`, ending phantom-running loop UI state.
|
|
66
|
+
- **Diagnostic alerts when toast host is offscreen** — Loop attach and confirm-launch failures fall back to a browser `alert()` when the terminal panel is hidden, so the error reaches the user instead of disappearing into an invisible toast.
|
|
67
|
+
- **User-created folders in sidebar** — Collapsible folders with name, color, and icon. Drag-and-drop to move templates, right-click for rename/recolor/delete. Included in backup/restore and export/import.
|
|
68
|
+
- **Per-template launch defaults** — Each template remembers CLI, model, thinking effort, and MCP profile. Launch modal pre-selects the preset.
|
|
69
|
+
- **OpenCode CLI as fourth option** — Alongside Claude, Codex, Gemini with a curated model list.
|
|
70
|
+
- **Runtime card cleanup** — CLI, Model, MCP Profile moved from chips to native selects in a 2-column "Runtime" layout. Labels no longer truncate.
|
|
71
|
+
- **Per-loop stop** — Stop button targets a specific loop instead of killing every running automation.
|
|
72
|
+
|
|
73
|
+
## CLI Integrations & MCP
|
|
74
|
+
|
|
75
|
+
- **Auto-repair of legacy plugin installs on startup** — Purges old `synabun-symlink` marketplace entries and reinstalls via the Claude Code CLI so installs survive CC restarts. `POST /api/plugins/repair` triggers the same flow manually.
|
|
76
|
+
- **Self-fetching runtime preflight** — `uvx`, `pipx`, `npx`, `bunx`, `pnpm dlx`, `yarn dlx`, etc. no longer trigger local dep installs. Missing binaries surface OS-specific install hints (`brew install uv`, winget, etc.) instead of failing silently at launch.
|
|
77
|
+
- **GitHub MCP installer no longer discards servers on dep failure** — Local `pip install` / `npm install` errors are now soft warnings; the server still registers and the user sees a `⚠` line in the install form.
|
|
78
|
+
- **Codex MCP approval respects per-tab Auto state** — The silent MCP accept path is now gated on the visible tab's Auto toggle and travels with each query, preventing background accept of `mcp__SynaBun__remember` and similar tools when Auto is off.
|
|
79
|
+
- **Unified update commands** — OpenCode, Claude Code, Codex, and Gemini CLI all update via `npm install -g <package>@latest`, plus OpenCode's built-in `opencode upgrade` fallback.
|
|
80
|
+
- **Codex Settings wired to SDK** — Config read/write via correct RPCs; model, reasoning effort, approval policy, sandbox mode, and web search read from a cached effective config. Layer badges (`user` / `project` / `default`) per field.
|
|
81
|
+
- **Codex MCP env fix** — Added `DOTENV_PATH`, `SYNABUN_DATA_HOME`, `MEMORY_DATA_DIR` so the server finds its data directory.
|
|
82
|
+
- **OpenCode OAuth flow repaired** — Authorize body now sends `{ method: idx }`, auth buttons only render when methods actually exist, save/failure toasts added.
|
|
83
|
+
- **Native skills cross-runtime** — `/synabun` and `/leonardo` no longer hard-depend on `AskUserQuestion` or Claude-only tool names.
|
|
84
|
+
- **MCP server add on Windows** — `claude mcp add` command fixed: removed `--` separator, properly quotes `-e "KEY=VALUE"` pairs across PowerShell shims.
|
|
85
|
+
|
|
86
|
+
## Memory / Plan / Session
|
|
87
|
+
|
|
88
|
+
- **"Continue planning" action across OpenCode, Codex, and Claude** — Post-plan card now offers Continue planning alongside Continue / Edit / Compact, allowing iterative plan refinement without exiting plan mode.
|
|
89
|
+
- **Codex plan mode handoff hardened** — Post-plan actions persist across tab state, plan turns run read-only server-side, and the prompt prefix instructs Codex to stop after producing the plan and wait for the sidepanel choice.
|
|
90
|
+
- **Plan files reorganized** — Plans now live in `data/plans/YYYY-MM-DD/slug.md` with descriptive kebab-case names. Legacy flat plans auto-migrate. Root `PLAN.md` is cleaned up on ExitPlanMode.
|
|
91
|
+
- **Recall output always shows full metadata** — Tags, related files, and source always appear (with `none` fallback instead of being omitted).
|
|
92
|
+
- **Session cache & cross-provider discovery** — Codex and OpenCode session discovery endpoints added; Claude paginates server-side, Codex/OpenCode virtual-paginate from SQLite.
|
|
93
|
+
- **Claude sidepanel cross-talk fix** — Orphan registry now keys by `windowId:sessionId` so multi-tab disconnects can't hand the wrong process to a reconnecting tab.
|
|
94
|
+
|
|
95
|
+
## Backup & Restore
|
|
96
|
+
|
|
97
|
+
- **Backup coverage expanded** — Auto and manual backups now include `stored-plans.json`, `synabun-plugins.json`, `mcp-registry.json`, `active-profile.json`, and the recursive `data/plans/YYYY-MM-DD/*.md` plan tree. Backward compatible with old backups (`version 1 || 2`).
|
|
98
|
+
|
|
99
|
+
## Automation & Browser
|
|
100
|
+
|
|
101
|
+
- **Browser session ownership model** — `_loopOwned` / `_agentOwned` flags with registry cross-checks prevent loops and sidepanel Claude from fighting over the same tab. Pinned sessions auto-recover if destroyed.
|
|
102
|
+
- **Browser tooling upgrades** — New `browser_cheatsheet` tool replaces bloated per-platform descriptions. `browser_snapshot` adds `interactive` / `landmarks` modes and viewport filtering. `browser_navigate` / `click` / `scroll` can return a snapshot in one call. Failing selectors auto-heal via a `textHint` param.
|
|
103
|
+
|
|
104
|
+
## Stability & Fixes
|
|
105
|
+
|
|
106
|
+
- **High and moderate npm audit CVEs patched** — Bumped `playwright` to ^1.59.1 (SSL cert verification CVE) and `uuid` to ^14 (buffer bounds), and added `overrides` for `lodash`, `protobufjs`, `hono`, `@hono/node-server`, `postcss`, and `vite` across both Neural Interface and MCP server packages. `npm audit` now clean.
|
|
107
|
+
- **Codex sidepanel cross-talk fix** — Per-tab WebSocket identity (every message stamped with `sessionId`), strict orphan reattach keys (`windowId + sessionId`), and reconnect-state cleanup prevent buffered tool-call events from one tab replaying into another.
|
|
108
|
+
- **Codex sidepanel stop scope** — Stop button now aborts only the active tab instead of every running Codex tab; server validates `sessionId` / `threadId` on `interrupt` / `force_kill` and refuses unscoped stop messages.
|
|
109
|
+
- **OpenCode post-plan card no longer resurrects** — `setTabMode()` clears the post-plan flag on `plan → non-plan` transitions and the renderer guards on `tab.mode === 'plan'`, so a leaked flag can't re-render the PLAN COMPLETE card after the user moved to implementation.
|
|
110
|
+
- **Codex edit-plan cancel handoff** — Closing the file editor without saving now re-renders a fresh clickable post-plan card; saving reopens the PLAN UPDATED handoff correctly.
|
|
111
|
+
- **Right-side toolbar reservation tracks all owners** — Per-owner reservation map prevents one closing sidepanel from clearing layout while another right panel is still active.
|
|
112
|
+
- **Restored Claude tabs keep stable IDs** — Sidepanel tray placeholder click-through targets the restored tab instead of losing selection because the panel generated a fresh UUID during bootstrap.
|
|
113
|
+
- **Stop button + ESC reliability** — Abort works from anywhere in the Claude, Codex, and OpenCode panels, cancels in-flight fetches server-side via AbortController, and covers every running tab.
|
|
114
|
+
- **Orphan grace extended to 30 min** — Claude and Codex processes no longer die during brief WebSocket drops (sleep, network, tab throttling). Added server-side ping/pong keepalive.
|
|
115
|
+
- **1M context window activation** — The `[1m]` model-ID suffix is now applied correctly so Opus/Sonnet 1M variants actually use the 1M beta window.
|
|
116
|
+
- **Plan mode flow fixes** — Answers to `AskUserQuestion` in plan mode are now re-injected per-question instead of collapsed into one comma-joined string. Plan-approval card fires at detection, not after implementation streams. Process is killed on ExitPlanMode so buffered tool calls can't execute before the user approves.
|
|
117
|
+
- **Blank assistant row pivot fix** — Claude no longer leaves an empty bubble when an assistant message contains only tool calls or whitespace-only text.
|
|
118
|
+
- **"DONE" toast no longer fires mid-turn (OpenCode)** — Completion notifications wait for the real `session.idle` event.
|
|
119
|
+
- **Git UI on large-status repos** — `git status` no longer throws on repos with tens of thousands of changes; branch button and git popover render even when parsing fails.
|
|
120
|
+
- **Notifications permission status** — Updates live when the user unblocks browser notifications, with a manual Re-check button.
|
|
121
|
+
- **CLI notification spam suppressed** — Repeated OS notifications during long AskUserQuestion / permission prompts dedupe until the session returns to idle.
|
|
122
|
+
- **Concurrent agent/loop browser sessions** — Ownership flags set immediately on creation; grace timer uses in-memory ownership checks instead of racy file reads.
|
|
123
|
+
- **Neural Interface crash on Playwright dialog race** — `No dialog is showing` ProtocolError now ignored alongside frame-detached errors.
|
|
124
|
+
|
|
125
|
+
## Windows Support
|
|
126
|
+
|
|
127
|
+
- **Git UI reliability** — Resolved silent failures from PATH shims, old Git versions, and cmd.exe quoting. Works across Scoop, winget, and older Git for Windows, with a startup probe that surfaces problems.
|
|
128
|
+
- **CLI auto-detect after reinstall** — Detection commands now run with an augmented PATH including `~/.local/bin`, `/usr/local/bin`, `~/.npm-global/bin`.
|
|
129
|
+
- **Native dropdowns in dark mode** — Added `color-scheme: dark` so Windows Chrome/Edge stop rendering native `<select>` popups with white backgrounds.
|
package/mcp-server/package.json
CHANGED
|
@@ -24,15 +24,21 @@
|
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
25
25
|
"chalk": "^5.6.2",
|
|
26
26
|
"express": "^4.21.0",
|
|
27
|
-
"uuid": "^
|
|
27
|
+
"uuid": "^14.0.0",
|
|
28
28
|
"zod": "^3.24.2"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/express": "^5.0.0",
|
|
32
32
|
"@types/node": "^22.12.0",
|
|
33
|
-
"@types/uuid": "^10.0.0",
|
|
34
33
|
"tsx": "^4.19.0",
|
|
35
34
|
"typescript": "^5.9.3",
|
|
36
35
|
"vitest": "^4.1.0"
|
|
36
|
+
},
|
|
37
|
+
"overrides": {
|
|
38
|
+
"protobufjs": "^7.5.5",
|
|
39
|
+
"hono": "^4.12.14",
|
|
40
|
+
"@hono/node-server": "^1.19.13",
|
|
41
|
+
"postcss": "^8.5.10",
|
|
42
|
+
"vite": "^8.0.10"
|
|
37
43
|
}
|
|
38
44
|
}
|
|
@@ -21,8 +21,12 @@
|
|
|
21
21
|
"express": "^4.21.2",
|
|
22
22
|
"node-html-markdown": "^2.0.0",
|
|
23
23
|
"node-pty": "^1.1.0",
|
|
24
|
-
"playwright": "1.
|
|
24
|
+
"playwright": "^1.59.1",
|
|
25
25
|
"undici": "^8.1.0",
|
|
26
26
|
"ws": "^8.19.0"
|
|
27
|
+
},
|
|
28
|
+
"overrides": {
|
|
29
|
+
"lodash": "^4.18.1",
|
|
30
|
+
"protobufjs": "^7.5.5"
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -394,71 +394,79 @@ function startupEmptyText() {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
// ── CLI installation status (banner + send-block) ──
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
let _cliInstallFailureForced = false;
|
|
398
|
+
|
|
399
|
+
function ensureCliBannerEl() {
|
|
400
|
+
if (!_panel) return null;
|
|
401
|
+
const container = panelEl('#cxp-messages-container');
|
|
402
|
+
if (!container) return null;
|
|
403
|
+
let banner = container.querySelector(':scope > .cxp-cli-banner');
|
|
404
|
+
if (banner) return banner;
|
|
399
405
|
const label = getCliLabel('codex');
|
|
400
406
|
const cmd = getCliInstallCommand('codex');
|
|
401
407
|
const url = getCliDocUrl('codex');
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
<
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
btn
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
btn.textContent = 'Re-check';
|
|
426
|
-
}
|
|
408
|
+
banner = document.createElement('div');
|
|
409
|
+
banner.className = 'cxp-cli-banner';
|
|
410
|
+
banner.innerHTML = `
|
|
411
|
+
<div class="cxp-cli-banner-icon">!</div>
|
|
412
|
+
<div class="cxp-cli-banner-text">
|
|
413
|
+
<div class="cxp-cli-banner-title">${label} CLI not installed</div>
|
|
414
|
+
<div class="cxp-cli-banner-body">Run <code>${cmd}</code> or follow the install guide.</div>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="cxp-cli-banner-actions">
|
|
417
|
+
<a class="cxp-cli-banner-link" href="${url}" target="_blank" rel="noopener noreferrer">Install guide</a>
|
|
418
|
+
<button class="cxp-cli-banner-recheck" type="button">Re-check</button>
|
|
419
|
+
</div>
|
|
420
|
+
`;
|
|
421
|
+
container.insertBefore(banner, container.firstChild);
|
|
422
|
+
const btn = banner.querySelector('.cxp-cli-banner-recheck');
|
|
423
|
+
btn?.addEventListener('click', async () => {
|
|
424
|
+
btn.disabled = true;
|
|
425
|
+
btn.textContent = 'Checking…';
|
|
426
|
+
try { await recheckCliStatus('codex'); }
|
|
427
|
+
finally {
|
|
428
|
+
if (btn.isConnected) {
|
|
429
|
+
btn.disabled = false;
|
|
430
|
+
btn.textContent = 'Re-check';
|
|
427
431
|
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
return banner;
|
|
430
435
|
}
|
|
431
436
|
|
|
432
|
-
function
|
|
437
|
+
function removeCliBannerEl() {
|
|
433
438
|
if (!_panel) return;
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
// Re-evaluate send button state with new CLI flag
|
|
439
|
+
const container = panelEl('#cxp-messages-container');
|
|
440
|
+
const banner = container?.querySelector(':scope > .cxp-cli-banner');
|
|
441
|
+
banner?.remove();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function refreshCliBanner() {
|
|
445
|
+
if (!_panel) return;
|
|
446
|
+
const missing = _cliInstalled === null || _cliInstallFailureForced;
|
|
447
|
+
_panel.classList.toggle('cxp-cli-blocked', missing);
|
|
448
|
+
if (missing) ensureCliBannerEl();
|
|
449
|
+
else removeCliBannerEl();
|
|
448
450
|
try { syncInputEnabled(); } catch {}
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
export function isCdxCliInstalled() {
|
|
452
|
-
return _cliInstalled !== null;
|
|
454
|
+
return _cliInstalled !== null && !_cliInstallFailureForced;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function flagCdxCliInstallFailure() {
|
|
458
|
+
_cliInstallFailureForced = true;
|
|
459
|
+
refreshCliBanner();
|
|
460
|
+
recheckCliStatus('codex').catch(() => {});
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
function ensureCliSubscription() {
|
|
456
464
|
if (_cliUnsub) return;
|
|
457
465
|
_cliUnsub = subscribeCliStatus('codex', (info) => {
|
|
458
466
|
_cliInstalled = info?.installed || null;
|
|
459
|
-
|
|
467
|
+
if (_cliInstalled) _cliInstallFailureForced = false;
|
|
468
|
+
refreshCliBanner();
|
|
460
469
|
});
|
|
461
|
-
document.addEventListener('cdx-empty-rebuilt', () => refreshCliBanners());
|
|
462
470
|
}
|
|
463
471
|
|
|
464
472
|
// ═══════════════════════════════════════════
|
|
@@ -497,7 +505,7 @@ function setVisible(nextVisible) {
|
|
|
497
505
|
loadCurrentProfile();
|
|
498
506
|
loadRecallProfile();
|
|
499
507
|
panelEl('#cxp-input')?.focus();
|
|
500
|
-
|
|
508
|
+
refreshCliBanner();
|
|
501
509
|
} else {
|
|
502
510
|
document.removeEventListener('keydown', _cdxDocEscHandler, { capture: true });
|
|
503
511
|
}
|
|
@@ -984,36 +984,68 @@ export function injectStyles() {
|
|
|
984
984
|
.cxp-empty-logo svg { width: 32px; height: 32px; opacity: 0.25; }
|
|
985
985
|
.cxp-empty-name { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.35); letter-spacing: 0.02em; }
|
|
986
986
|
.cxp-empty-status { font-size: 11px; color: rgba(255,255,255,0.28); line-height: 1.5; min-height: 1em; }
|
|
987
|
-
.cxp-cli-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
987
|
+
.cxp-cli-banner {
|
|
988
|
+
flex-shrink: 0;
|
|
989
|
+
display: grid;
|
|
990
|
+
grid-template-columns: auto 1fr auto;
|
|
991
|
+
gap: 10px;
|
|
992
|
+
align-items: center;
|
|
993
|
+
margin: 8px 12px 0;
|
|
994
|
+
padding: 10px 12px;
|
|
995
|
+
border: 1px solid rgba(255,180,80,0.45);
|
|
996
|
+
border-radius: 8px;
|
|
997
|
+
background: rgba(255,180,80,0.08);
|
|
998
|
+
color: rgba(255,235,200,0.92);
|
|
999
|
+
font-size: 11px;
|
|
1000
|
+
line-height: 1.4;
|
|
1001
|
+
position: relative;
|
|
1002
|
+
z-index: 5;
|
|
1003
|
+
}
|
|
1004
|
+
.cxp-cli-banner-icon {
|
|
1005
|
+
width: 22px; height: 22px;
|
|
1006
|
+
display: flex; align-items: center; justify-content: center;
|
|
1007
|
+
border-radius: 50%;
|
|
1008
|
+
background: rgba(255,180,80,0.25);
|
|
1009
|
+
color: rgba(255,210,140,1);
|
|
1010
|
+
font-weight: 700;
|
|
1011
|
+
}
|
|
1012
|
+
.cxp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1013
|
+
.cxp-cli-banner-title {
|
|
1014
|
+
font-size: 12px; font-weight: 600;
|
|
1015
|
+
color: rgba(255,210,140,0.95);
|
|
1016
|
+
}
|
|
1017
|
+
.cxp-cli-banner-body {
|
|
1018
|
+
font-size: 11px;
|
|
1019
|
+
color: rgba(255,255,255,0.7);
|
|
1020
|
+
word-break: break-word;
|
|
1021
|
+
}
|
|
1022
|
+
.cxp-cli-banner-body code {
|
|
1023
|
+
padding: 1px 5px;
|
|
1024
|
+
background: rgba(0,0,0,0.35);
|
|
1025
|
+
border-radius: 3px;
|
|
1026
|
+
font-size: 10.5px;
|
|
1027
|
+
color: rgba(255,255,255,0.85);
|
|
1004
1028
|
}
|
|
1005
|
-
.cxp-cli-
|
|
1006
|
-
.cxp-cli-
|
|
1029
|
+
.cxp-cli-banner-actions { display: flex; gap: 6px; }
|
|
1030
|
+
.cxp-cli-banner-link, .cxp-cli-banner-recheck {
|
|
1007
1031
|
display: inline-flex; align-items: center; gap: 4px;
|
|
1008
|
-
padding:
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1032
|
+
padding: 4px 9px;
|
|
1033
|
+
font-size: 11px;
|
|
1034
|
+
font-weight: 500;
|
|
1035
|
+
border-radius: 4px;
|
|
1036
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
1037
|
+
background: rgba(255,255,255,0.05);
|
|
1038
|
+
color: rgba(255,255,255,0.9);
|
|
1039
|
+
text-decoration: none;
|
|
1040
|
+
cursor: pointer;
|
|
1041
|
+
user-select: none;
|
|
1042
|
+
font-family: inherit;
|
|
1012
1043
|
}
|
|
1013
|
-
.cxp-cli-
|
|
1014
|
-
background: rgba(255,255,255,0.
|
|
1044
|
+
.cxp-cli-banner-link:hover, .cxp-cli-banner-recheck:hover {
|
|
1045
|
+
background: rgba(255,255,255,0.12);
|
|
1046
|
+
border-color: rgba(255,255,255,0.4);
|
|
1015
1047
|
}
|
|
1016
|
-
.cxp-cli-
|
|
1048
|
+
.cxp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
|
|
1017
1049
|
.codex-panel.cxp-cli-blocked .cxp-send { opacity: 0.4 !important; cursor: not-allowed !important; }
|
|
1018
1050
|
.codex-panel.cxp-cli-blocked .cxp-input { opacity: 0.7; }
|
|
1019
1051
|
.cxp-system {
|
|
@@ -552,6 +552,9 @@ function applySocketError(msg) {
|
|
|
552
552
|
if (_compacting) setCompactingUI(false);
|
|
553
553
|
appendSystem(text, 'error');
|
|
554
554
|
setStatus(text, 'error');
|
|
555
|
+
if (/Codex CLI not found|ENOENT|app-server exited before responding|app-server spawn/i.test(text)) {
|
|
556
|
+
import('./cdx-panel.js').then((m) => m.flagCdxCliInstallFailure?.()).catch(() => {});
|
|
557
|
+
}
|
|
555
558
|
if (msg.requestId) {
|
|
556
559
|
if (_sessionListRequest?.requestId === msg.requestId) {
|
|
557
560
|
_sessionListRequest.reject?.(new Error(text));
|
|
@@ -910,7 +910,7 @@ async function handleSend() {
|
|
|
910
910
|
if (!input) return;
|
|
911
911
|
const tab = activeTab();
|
|
912
912
|
if (!tab) return;
|
|
913
|
-
if (_cliInstalled === null) {
|
|
913
|
+
if (_cliInstalled === null || _cliInstallFailureForced) {
|
|
914
914
|
setStatus('offline', 'OpenCode CLI not installed. Install it first, then click Re-check.');
|
|
915
915
|
return;
|
|
916
916
|
}
|
|
@@ -1013,7 +1013,7 @@ function syncSendButton() {
|
|
|
1013
1013
|
const tab = activeTab();
|
|
1014
1014
|
if (!sendBtn) return;
|
|
1015
1015
|
|
|
1016
|
-
const cliBlocked = _cliInstalled === null;
|
|
1016
|
+
const cliBlocked = _cliInstalled === null || _cliInstallFailureForced;
|
|
1017
1017
|
|
|
1018
1018
|
if (cliBlocked) {
|
|
1019
1019
|
sendBtn.classList.remove('running');
|
|
@@ -1521,66 +1521,74 @@ function _ocpDocEscHandler(event) {
|
|
|
1521
1521
|
}
|
|
1522
1522
|
|
|
1523
1523
|
// ── CLI installation status (banner + send-block) ──
|
|
1524
|
-
|
|
1525
|
-
|
|
1524
|
+
let _cliInstallFailureForced = false;
|
|
1525
|
+
|
|
1526
|
+
function ensureCliBannerEl() {
|
|
1527
|
+
if (!_panel) return null;
|
|
1528
|
+
const container = _panel.querySelector('.ocp-messages-container');
|
|
1529
|
+
if (!container) return null;
|
|
1530
|
+
let banner = container.querySelector(':scope > .ocp-cli-banner');
|
|
1531
|
+
if (banner) return banner;
|
|
1526
1532
|
const label = getCliLabel('opencode');
|
|
1527
1533
|
const cmd = getCliInstallCommand('opencode');
|
|
1528
1534
|
const url = getCliDocUrl('opencode');
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
<
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
btn
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
btn.textContent = 'Re-check';
|
|
1553
|
-
}
|
|
1535
|
+
banner = document.createElement('div');
|
|
1536
|
+
banner.className = 'ocp-cli-banner';
|
|
1537
|
+
banner.innerHTML = `
|
|
1538
|
+
<div class="ocp-cli-banner-icon">!</div>
|
|
1539
|
+
<div class="ocp-cli-banner-text">
|
|
1540
|
+
<div class="ocp-cli-banner-title">${label} CLI not installed</div>
|
|
1541
|
+
<div class="ocp-cli-banner-body">Run <code>${cmd}</code> or follow the install guide.</div>
|
|
1542
|
+
</div>
|
|
1543
|
+
<div class="ocp-cli-banner-actions">
|
|
1544
|
+
<a class="ocp-cli-banner-link" href="${url}" target="_blank" rel="noopener noreferrer">Install guide</a>
|
|
1545
|
+
<button class="ocp-cli-banner-recheck" type="button">Re-check</button>
|
|
1546
|
+
</div>
|
|
1547
|
+
`;
|
|
1548
|
+
container.insertBefore(banner, container.firstChild);
|
|
1549
|
+
const btn = banner.querySelector('.ocp-cli-banner-recheck');
|
|
1550
|
+
btn?.addEventListener('click', async () => {
|
|
1551
|
+
btn.disabled = true;
|
|
1552
|
+
btn.textContent = 'Checking…';
|
|
1553
|
+
try { await recheckCliStatus('opencode'); }
|
|
1554
|
+
finally {
|
|
1555
|
+
if (btn.isConnected) {
|
|
1556
|
+
btn.disabled = false;
|
|
1557
|
+
btn.textContent = 'Re-check';
|
|
1554
1558
|
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
return banner;
|
|
1557
1562
|
}
|
|
1558
1563
|
|
|
1559
|
-
function
|
|
1564
|
+
function removeCliBannerEl() {
|
|
1560
1565
|
if (!_panel) return;
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
if (hintEl) hintEl.style.display = '';
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1566
|
+
const banner = _panel.querySelector('.ocp-messages-container > .ocp-cli-banner');
|
|
1567
|
+
banner?.remove();
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
function refreshCliBanner() {
|
|
1571
|
+
if (!_panel) return;
|
|
1572
|
+
const missing = _cliInstalled === null || _cliInstallFailureForced;
|
|
1573
|
+
_panel.classList.toggle('ocp-cli-blocked', missing);
|
|
1574
|
+
if (missing) ensureCliBannerEl();
|
|
1575
|
+
else removeCliBannerEl();
|
|
1574
1576
|
syncSendButton();
|
|
1575
1577
|
}
|
|
1576
1578
|
|
|
1579
|
+
export function flagOcpCliInstallFailure() {
|
|
1580
|
+
_cliInstallFailureForced = true;
|
|
1581
|
+
refreshCliBanner();
|
|
1582
|
+
recheckCliStatus('opencode').catch(() => {});
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1577
1585
|
function ensureCliSubscription() {
|
|
1578
1586
|
if (_cliUnsub) return;
|
|
1579
1587
|
_cliUnsub = subscribeCliStatus('opencode', (info) => {
|
|
1580
1588
|
_cliInstalled = info?.installed || null;
|
|
1581
|
-
|
|
1589
|
+
if (_cliInstalled) _cliInstallFailureForced = false;
|
|
1590
|
+
refreshCliBanner();
|
|
1582
1591
|
});
|
|
1583
|
-
document.addEventListener('ocp-empty-rebuilt', () => refreshCliBanners());
|
|
1584
1592
|
}
|
|
1585
1593
|
|
|
1586
1594
|
function setVisible(nextVisible) {
|
|
@@ -1608,7 +1616,7 @@ function setVisible(nextVisible) {
|
|
|
1608
1616
|
if (_curProject) loadBranches(_curProject);
|
|
1609
1617
|
loadCurrentProfile();
|
|
1610
1618
|
loadRecallProfile();
|
|
1611
|
-
|
|
1619
|
+
refreshCliBanner();
|
|
1612
1620
|
} else {
|
|
1613
1621
|
document.removeEventListener('keydown', _ocpDocEscHandler, { capture: true });
|
|
1614
1622
|
}
|
|
@@ -1664,6 +1672,9 @@ function wsCallbacks() {
|
|
|
1664
1672
|
},
|
|
1665
1673
|
onError(msg) {
|
|
1666
1674
|
console.error('[ocp] Error:', msg.message);
|
|
1675
|
+
if (typeof msg.message === 'string' && /opencode.*(not.*found|ENOENT|spawn)|opencode CLI/i.test(msg.message)) {
|
|
1676
|
+
flagOcpCliInstallFailure();
|
|
1677
|
+
}
|
|
1667
1678
|
},
|
|
1668
1679
|
};
|
|
1669
1680
|
}
|
|
@@ -1521,38 +1521,68 @@ export function injectStyles() {
|
|
|
1521
1521
|
stroke: currentColor; fill: none;
|
|
1522
1522
|
stroke-width: 1; opacity: 0.3;
|
|
1523
1523
|
}
|
|
1524
|
-
.ocp-cli-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1524
|
+
.ocp-cli-banner {
|
|
1525
|
+
flex-shrink: 0;
|
|
1526
|
+
display: grid;
|
|
1527
|
+
grid-template-columns: auto 1fr auto;
|
|
1528
|
+
gap: 10px;
|
|
1529
|
+
align-items: center;
|
|
1530
|
+
margin: 8px 12px 0;
|
|
1531
|
+
padding: 10px 12px;
|
|
1532
|
+
border: 1px solid rgba(255,180,80,0.45);
|
|
1533
|
+
border-radius: 8px;
|
|
1534
|
+
background: rgba(255,180,80,0.08);
|
|
1535
|
+
color: rgba(255,235,200,0.92);
|
|
1536
|
+
font-size: 11px;
|
|
1537
|
+
line-height: 1.4;
|
|
1538
|
+
position: relative;
|
|
1539
|
+
z-index: 5;
|
|
1534
1540
|
}
|
|
1535
|
-
.ocp-cli-
|
|
1536
|
-
|
|
1541
|
+
.ocp-cli-banner-icon {
|
|
1542
|
+
width: 22px; height: 22px;
|
|
1543
|
+
display: flex; align-items: center; justify-content: center;
|
|
1544
|
+
border-radius: 50%;
|
|
1545
|
+
background: rgba(255,180,80,0.25);
|
|
1546
|
+
color: rgba(255,210,140,1);
|
|
1547
|
+
font-weight: 700;
|
|
1537
1548
|
}
|
|
1538
|
-
.ocp-cli-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
color: rgba(255,
|
|
1549
|
+
.ocp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1550
|
+
.ocp-cli-banner-title {
|
|
1551
|
+
font-size: 12px; font-weight: 600;
|
|
1552
|
+
color: rgba(255,210,140,0.95);
|
|
1542
1553
|
}
|
|
1543
|
-
.ocp-cli-
|
|
1544
|
-
|
|
1554
|
+
.ocp-cli-banner-body {
|
|
1555
|
+
font-size: 11px;
|
|
1556
|
+
color: rgba(255,255,255,0.7);
|
|
1557
|
+
word-break: break-word;
|
|
1558
|
+
}
|
|
1559
|
+
.ocp-cli-banner-body code {
|
|
1560
|
+
padding: 1px 5px;
|
|
1561
|
+
background: rgba(0,0,0,0.35);
|
|
1562
|
+
border-radius: 3px;
|
|
1563
|
+
font-size: 10.5px;
|
|
1564
|
+
color: rgba(255,255,255,0.85);
|
|
1565
|
+
}
|
|
1566
|
+
.ocp-cli-banner-actions { display: flex; gap: 6px; }
|
|
1567
|
+
.ocp-cli-banner-link, .ocp-cli-banner-recheck {
|
|
1545
1568
|
display: inline-flex; align-items: center; gap: 4px;
|
|
1546
|
-
padding:
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1569
|
+
padding: 4px 9px;
|
|
1570
|
+
font-size: 11px;
|
|
1571
|
+
font-weight: 500;
|
|
1572
|
+
border-radius: 4px;
|
|
1573
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
1574
|
+
background: rgba(255,255,255,0.05);
|
|
1575
|
+
color: rgba(255,255,255,0.9);
|
|
1576
|
+
text-decoration: none;
|
|
1577
|
+
cursor: pointer;
|
|
1578
|
+
user-select: none;
|
|
1550
1579
|
font-family: inherit;
|
|
1551
1580
|
}
|
|
1552
|
-
.ocp-cli-
|
|
1553
|
-
background: rgba(255,255,255,0.
|
|
1581
|
+
.ocp-cli-banner-link:hover, .ocp-cli-banner-recheck:hover {
|
|
1582
|
+
background: rgba(255,255,255,0.12);
|
|
1583
|
+
border-color: rgba(255,255,255,0.4);
|
|
1554
1584
|
}
|
|
1555
|
-
.ocp-cli-
|
|
1585
|
+
.ocp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
|
|
1556
1586
|
.ocp-panel.ocp-cli-blocked .ocp-input { opacity: 0.7; }
|
|
1557
1587
|
|
|
1558
1588
|
/* ── Thinking indicator ── */
|
|
@@ -432,38 +432,70 @@ function injectStyles() {
|
|
|
432
432
|
.cp-messages:has(.msg) .cp-empty,
|
|
433
433
|
.cp-messages:has(.msg-status) .cp-empty { display: none; }
|
|
434
434
|
|
|
435
|
-
/* ── CLI-not-installed banner (
|
|
436
|
-
.cp-cli-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
435
|
+
/* ── CLI-not-installed banner (sticky top of messages container) ── */
|
|
436
|
+
.cp-cli-banner {
|
|
437
|
+
flex-shrink: 0;
|
|
438
|
+
display: grid;
|
|
439
|
+
grid-template-columns: auto 1fr auto;
|
|
440
|
+
gap: 10px;
|
|
441
|
+
align-items: center;
|
|
442
|
+
margin: 8px 12px 0;
|
|
443
|
+
padding: 10px 12px;
|
|
444
|
+
border: 1px solid rgba(255,180,80,0.45);
|
|
445
|
+
border-radius: 8px;
|
|
446
|
+
background: rgba(255,180,80,0.08);
|
|
447
|
+
color: rgba(255,235,200,0.92);
|
|
448
|
+
font-size: 11px;
|
|
449
|
+
line-height: 1.4;
|
|
450
|
+
pointer-events: auto;
|
|
451
|
+
position: relative;
|
|
452
|
+
z-index: 5;
|
|
445
453
|
}
|
|
446
|
-
.cp-cli-
|
|
447
|
-
|
|
454
|
+
.cp-cli-banner-icon {
|
|
455
|
+
width: 22px; height: 22px;
|
|
456
|
+
display: flex; align-items: center; justify-content: center;
|
|
457
|
+
border-radius: 50%;
|
|
458
|
+
background: rgba(255,180,80,0.25);
|
|
459
|
+
color: rgba(255,210,140,1);
|
|
460
|
+
font-weight: 700;
|
|
448
461
|
}
|
|
449
|
-
.cp-cli-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
color: rgba(255,
|
|
462
|
+
.cp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
463
|
+
.cp-cli-banner-title {
|
|
464
|
+
font-size: 12px; font-weight: 600;
|
|
465
|
+
color: rgba(255,210,140,0.95);
|
|
453
466
|
}
|
|
454
|
-
.cp-cli-
|
|
455
|
-
|
|
467
|
+
.cp-cli-banner-body {
|
|
468
|
+
font-size: 11px;
|
|
469
|
+
color: rgba(255,255,255,0.7);
|
|
470
|
+
word-break: break-word;
|
|
471
|
+
}
|
|
472
|
+
.cp-cli-banner-body code {
|
|
473
|
+
padding: 1px 5px;
|
|
474
|
+
background: rgba(0,0,0,0.35);
|
|
475
|
+
border-radius: 3px;
|
|
476
|
+
font-size: 10.5px;
|
|
477
|
+
color: rgba(255,255,255,0.85);
|
|
478
|
+
}
|
|
479
|
+
.cp-cli-banner-actions { display: flex; gap: 6px; }
|
|
480
|
+
.cp-cli-banner-link, .cp-cli-banner-recheck {
|
|
456
481
|
display: inline-flex; align-items: center; gap: 4px;
|
|
457
|
-
padding:
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
482
|
+
padding: 4px 9px;
|
|
483
|
+
font-size: 11px;
|
|
484
|
+
font-weight: 500;
|
|
485
|
+
border-radius: 4px;
|
|
486
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
487
|
+
background: rgba(255,255,255,0.05);
|
|
488
|
+
color: rgba(255,255,255,0.9);
|
|
489
|
+
text-decoration: none;
|
|
490
|
+
cursor: pointer;
|
|
491
|
+
user-select: none;
|
|
492
|
+
font-family: inherit;
|
|
461
493
|
}
|
|
462
|
-
.cp-cli-
|
|
463
|
-
background: rgba(255,255,255,0.
|
|
494
|
+
.cp-cli-banner-link:hover, .cp-cli-banner-recheck:hover {
|
|
495
|
+
background: rgba(255,255,255,0.12);
|
|
496
|
+
border-color: rgba(255,255,255,0.4);
|
|
464
497
|
}
|
|
465
|
-
.cp-cli-
|
|
466
|
-
/* Block send button visually + functionally when CLI missing */
|
|
498
|
+
.cp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
|
|
467
499
|
.claude-panel.cp-cli-blocked .cp-send { opacity: 0.4 !important; cursor: not-allowed !important; }
|
|
468
500
|
.claude-panel.cp-cli-blocked .cp-input { opacity: 0.7; }
|
|
469
501
|
|
|
@@ -2752,7 +2784,6 @@ function createTab(sessionId = null, label = 'New chat', { autoSwitch = true, id
|
|
|
2752
2784
|
msgDiv.innerHTML = `<div class="cp-empty"><div class="cp-empty-logo">${CLAUDE_ICON}</div><span class="cp-empty-name">Claude</span><span class="cp-empty-hint">Send a message to start</span></div>`;
|
|
2753
2785
|
container.appendChild(msgDiv);
|
|
2754
2786
|
tab.messagesEl = msgDiv;
|
|
2755
|
-
if (_cliInstalled === null) _renderCliMissingBanner(msgDiv.querySelector('.cp-empty'));
|
|
2756
2787
|
// Connect WebSocket
|
|
2757
2788
|
connectTab(tab);
|
|
2758
2789
|
// Create pill (rendered in shared tray, not inside panel)
|
|
@@ -3576,7 +3607,6 @@ async function selectSession(sid, label) {
|
|
|
3576
3607
|
const btn = _panel?.querySelector('.cp-session-label');
|
|
3577
3608
|
if (btn) btn.textContent = tab.label;
|
|
3578
3609
|
tab.messagesEl.innerHTML = `<div class="cp-empty"><div class="cp-empty-logo">${CLAUDE_ICON}</div><span class="cp-empty-name">Claude</span><span class="cp-empty-hint">Send a message to start</span></div>`;
|
|
3579
|
-
if (_cliInstalled === null) _renderCliMissingBanner(tab.messagesEl.querySelector('.cp-empty'));
|
|
3580
3610
|
if (sid) {
|
|
3581
3611
|
loadSessionHistory(sid, tab.messagesEl);
|
|
3582
3612
|
// Restore session cost from server
|
|
@@ -4371,6 +4401,9 @@ function _processTabMsg(tab, msg) {
|
|
|
4371
4401
|
if (tab.compacting) { tab.compacting = false; if (tab === activeTab()) _setCompactingUI(false); }
|
|
4372
4402
|
appendError(tab, msg.message);
|
|
4373
4403
|
notify('panel', NOTIF_TYPE.ERROR, tab.label || 'Claude Code', { tabId: tab.id });
|
|
4404
|
+
if (typeof msg.message === 'string' && /Claude CLI not found|ENOENT/i.test(msg.message)) {
|
|
4405
|
+
flagClaudeCliInstallFailure();
|
|
4406
|
+
}
|
|
4374
4407
|
break;
|
|
4375
4408
|
}
|
|
4376
4409
|
}
|
|
@@ -6586,7 +6619,7 @@ function runSlashCommand(tab, raw) {
|
|
|
6586
6619
|
}
|
|
6587
6620
|
|
|
6588
6621
|
function send({ shift = false } = {}) {
|
|
6589
|
-
if (_cliInstalled === null) {
|
|
6622
|
+
if (_cliInstalled === null || _cliInstallFailureForced) {
|
|
6590
6623
|
notify({ type: NOTIF_TYPE.WARN, title: 'Claude CLI not installed', body: 'Install the Claude Code CLI before sending a message.' });
|
|
6591
6624
|
return;
|
|
6592
6625
|
}
|
|
@@ -7293,65 +7326,73 @@ function syncReservedWidth() {
|
|
|
7293
7326
|
}
|
|
7294
7327
|
|
|
7295
7328
|
// ── CLI installation status (banner + send-block) ──
|
|
7296
|
-
|
|
7297
|
-
|
|
7329
|
+
let _cliInstallFailureForced = false;
|
|
7330
|
+
|
|
7331
|
+
function _ensureCliBannerEl() {
|
|
7332
|
+
if (!_panel) return null;
|
|
7333
|
+
const container = _panel.querySelector('#cp-messages-container');
|
|
7334
|
+
if (!container) return null;
|
|
7335
|
+
let banner = container.querySelector(':scope > .cp-cli-banner');
|
|
7336
|
+
if (banner) return banner;
|
|
7298
7337
|
const label = getCliLabel('claude-code');
|
|
7299
7338
|
const cmd = getCliInstallCommand('claude-code');
|
|
7300
7339
|
const url = getCliDocUrl('claude-code');
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
<
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
btn
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
btn.textContent = 'Re-check';
|
|
7325
|
-
}
|
|
7340
|
+
banner = document.createElement('div');
|
|
7341
|
+
banner.className = 'cp-cli-banner';
|
|
7342
|
+
banner.innerHTML = `
|
|
7343
|
+
<div class="cp-cli-banner-icon">!</div>
|
|
7344
|
+
<div class="cp-cli-banner-text">
|
|
7345
|
+
<div class="cp-cli-banner-title">${label} CLI not installed</div>
|
|
7346
|
+
<div class="cp-cli-banner-body">Run <code>${cmd}</code> or follow the install guide.</div>
|
|
7347
|
+
</div>
|
|
7348
|
+
<div class="cp-cli-banner-actions">
|
|
7349
|
+
<a class="cp-cli-banner-link" href="${url}" target="_blank" rel="noopener noreferrer">Install guide</a>
|
|
7350
|
+
<button class="cp-cli-banner-recheck" type="button">Re-check</button>
|
|
7351
|
+
</div>
|
|
7352
|
+
`;
|
|
7353
|
+
container.insertBefore(banner, container.firstChild);
|
|
7354
|
+
const btn = banner.querySelector('.cp-cli-banner-recheck');
|
|
7355
|
+
btn?.addEventListener('click', async () => {
|
|
7356
|
+
btn.disabled = true;
|
|
7357
|
+
btn.textContent = 'Checking…';
|
|
7358
|
+
try { await recheckCliStatus('claude-code'); }
|
|
7359
|
+
finally {
|
|
7360
|
+
if (btn.isConnected) {
|
|
7361
|
+
btn.disabled = false;
|
|
7362
|
+
btn.textContent = 'Re-check';
|
|
7326
7363
|
}
|
|
7327
|
-
}
|
|
7328
|
-
}
|
|
7364
|
+
}
|
|
7365
|
+
});
|
|
7366
|
+
return banner;
|
|
7367
|
+
}
|
|
7368
|
+
|
|
7369
|
+
function _removeCliBannerEl() {
|
|
7370
|
+
if (!_panel) return;
|
|
7371
|
+
const banner = _panel.querySelector('#cp-messages-container > .cp-cli-banner');
|
|
7372
|
+
banner?.remove();
|
|
7329
7373
|
}
|
|
7330
7374
|
|
|
7331
7375
|
function _refreshCliBanners() {
|
|
7332
7376
|
if (!_panel) return;
|
|
7333
|
-
const
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
} else {
|
|
7338
|
-
_panel.classList.remove('cp-cli-blocked');
|
|
7339
|
-
for (const el of empties) {
|
|
7340
|
-
const banner = el.querySelector('.cp-cli-missing');
|
|
7341
|
-
if (banner) banner.remove();
|
|
7342
|
-
const hint = el.querySelector('.cp-empty-hint');
|
|
7343
|
-
if (hint) hint.style.display = '';
|
|
7344
|
-
}
|
|
7345
|
-
}
|
|
7346
|
-
// Re-sync send button state — installed transition should re-enable input drafts
|
|
7377
|
+
const missing = _cliInstalled === null || _cliInstallFailureForced;
|
|
7378
|
+
_panel.classList.toggle('cp-cli-blocked', missing);
|
|
7379
|
+
if (missing) _ensureCliBannerEl();
|
|
7380
|
+
else _removeCliBannerEl();
|
|
7347
7381
|
const $send = _panel.querySelector('#cp-send');
|
|
7348
|
-
if ($send &&
|
|
7382
|
+
if ($send && missing) $send.disabled = true;
|
|
7383
|
+
}
|
|
7384
|
+
|
|
7385
|
+
export function flagClaudeCliInstallFailure() {
|
|
7386
|
+
_cliInstallFailureForced = true;
|
|
7387
|
+
_refreshCliBanners();
|
|
7388
|
+
recheckCliStatus('claude-code').catch(() => {});
|
|
7349
7389
|
}
|
|
7350
7390
|
|
|
7351
7391
|
function _ensureCliSubscription() {
|
|
7352
7392
|
if (_cliUnsub) return;
|
|
7353
7393
|
_cliUnsub = subscribeCliStatus('claude-code', (info) => {
|
|
7354
7394
|
_cliInstalled = info?.installed || null;
|
|
7395
|
+
if (_cliInstalled) _cliInstallFailureForced = false;
|
|
7355
7396
|
_refreshCliBanners();
|
|
7356
7397
|
});
|
|
7357
7398
|
}
|