synabun 2026.5.2-beta → 2026.5.3
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
CHANGED
|
@@ -1,194 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
- **Navbar count badge for unread updates** — `neural-interface/public/shared/html-shell.js` adds a `<span class="count-badge" id="titlebar-notifications-count" hidden>` inside `#titlebar-sessions-btn`. `neural-interface/public/shared/ui-navbar.js` listens for the new `notifications:updated` event (dispatched by `ui-sessions.js`'s `emitNotificationsUpdate()`), writes the count, toggles `hidden`, and updates the button's tooltip to `Notifications · N updates`. Bootstraps via `fetchNotificationsForBadge()` on init plus a 5-minute polling fallback so the badge stays accurate without ever opening the drawer.
|
|
45
|
-
- **Read-only accessors on the existing update modules** — `getSynabunUpdateData()` exported from `ui-update.js`, `getToolUpdateData()` + `runToolUpdate(key)` exported from `ui-tool-updates.js`. The drawer reads cached state from those modules instead of duplicating the polling logic, so the existing top-right SynaBun update button and per-CLI-tool toolbar badges keep working unchanged and the drawer just mirrors them.
|
|
46
|
-
- **Updates-tab styling** — `neural-interface/public/shared/styles.css` adds `.sm-card-update`, `.sm-update-icon`, `.sm-update-title`, `.sm-update-version`, `.sm-update-meta`, `.sm-update-actions`, `.sm-btn-ghost`, `.sm-btn-primary`, `.sm-badge-warning`, plus an orange-pill `#titlebar-sessions-btn .count-badge` for the navbar.
|
|
47
|
-
- **Bell + per-card icons** — `ICON_BELL`, `ICON_RULESET`, `ICON_PACKAGE`, `ICON_UP_ARROW` SVG constants added to `ui-sessions.js`. Bell is the empty-state and tab icon; the rest are per-card kind icons.
|
|
48
|
-
|
|
49
|
-
### Changed — Session Monitor Renamed to Notifications Drawer
|
|
50
|
-
|
|
51
|
-
- **Window identity rewritten** — `neural-interface/public/shared/ui-sessions.js` `<h3>` reads `Notifications`, comment header rewritten to describe the window as the centralised notification surface, default `_activeTab` is now `'updates'` so the drawer opens to the most relevant tab. Tooltip on `#titlebar-sessions-btn` in `neural-interface/public/shared/html-shell.js` reads `Notifications`. Tutorial step 7 in `neural-interface/public/shared/ui-tutorial-steps.js` now labels the area `Notifications` with sublabel `Sessions, alerts, and ruleset / CLI updates`. CSS section comment in `neural-interface/public/shared/styles.css` renamed to `Notifications drawer`. Internal CSS classes (`.sm-*`, `.session-monitor-panel`), DOM IDs (`#titlebar-sessions-btn`, `#session-monitor-panel`), and event names (`session-monitor:closed`) kept as-is for backward compat with the rest of the app.
|
|
52
|
-
|
|
53
|
-
### Changed — Removed Dedicated Stale-Loops Alert Button (Titlebar)
|
|
54
|
-
|
|
55
|
-
- **Button + wiring deleted** — `#titlebar-loops-alert-btn` markup removed from `neural-interface/public/shared/html-shell.js`, the entire `if (loopsAlertBtn) { ... }` wiring block (~17 lines) removed from `neural-interface/public/shared/ui-navbar.js`, and the `.bar-loops-alert-btn` / `:hover` / `.count-badge` rules (~22 lines) removed from `neural-interface/public/shared/styles.css`. `openLoopsTab` dropped from the navbar import set; the export itself is kept in `ui-sessions.js` as a small public API surface.
|
|
56
|
-
- **Loops still surface via the drawer** — Stale loops continue to feed the `loops:updated` event and the existing `Loops` tab badge in the Notifications drawer. The 30s `fetchLoopsForBadge()` polling is preserved in `ui-navbar.js` so the Loops tab badge stays accurate without ever opening the drawer.
|
|
57
|
-
|
|
58
|
-
### Changed — Titlebar Icon Polish (Notifications, Image Gallery)
|
|
59
|
-
|
|
60
|
-
- **Notifications button now uses a bell glyph** — `#titlebar-sessions-btn` in `neural-interface/public/shared/html-shell.js` swapped from the legacy "tray with downward arrow" multi-path SVG to a clean two-path bell (24×24 viewBox, stroke-width 1.6, line-cap/join round). Count-badge `<span>` is unchanged so the orange `N` overlay still works.
|
|
61
|
-
- **Image Gallery button now uses the universal photo glyph** — `#titlebar-gallery-btn` swapped from a complex multi-path bag/basket-style icon (read as cluttered next to its cleaner siblings) to the universal photo icon: rounded rectangle frame, small circle (sun), diagonal mountain line. 24×24 viewBox, stroke-width 1.5, matches the rest of the navbar.
|
|
62
|
-
|
|
63
|
-
### Fixed — OpenCode Sidepanel Plan-Mode Post-Plan Card Silent Stall (Deepseek + non-Anthropic models)
|
|
64
|
-
|
|
65
|
-
- **Plan rendered but no `PLAN COMPLETE` card** — In OpenCode plan mode with non-Anthropic models (e.g. Deepseek v4 Pro), the plan would stream successfully but the post-plan action card (Edit / Continue / Compact) never appeared. The agent activity dock stayed "active" with no spinner, forcing the user to press Stop. Root cause: `showPostPlanUI(tab)` was only triggered from `tool.start` ExitPlanMode (Anthropic-only), `session.idle`, `session.status:idle`, and the WS-resolve fallback. When the planning subagent's `session.idle` fired but the parent never finalized (parent never received the task `tool.result`), none of the trigger sites fired. Single-file fix in `neural-interface/public/shared/ocp/ocp-tabs.js`:
|
|
66
|
-
- **`maybeShowPostPlanUI(tab, {source, forceRecapture})` helper** — Idempotent post-plan trigger gated on `mode === 'plan'`, `!showPostPlanActions`, and `planContent.length >= 40`. Centralizes the decision so non-`ExitPlanMode` terminal paths don't render an empty card.
|
|
67
|
-
- **8-second watchdog** — `schedulePlanFinalizationWatchdog(tab)` armed when `recordAgentChildEvent` records a subagent `session.idle`. If the parent's terminal signal doesn't fire by then, the watchdog force-finalizes (`tab.running = false`, `removeThinking`, `finalizeStreamingMessage`) and surfaces the post-plan card. `clearPlanFinalizationWatchdog(tab)` runs in every parent terminal path: WS-resolve fallback, `abortTab`, background-event idle, `message.completed`, `session.status:idle`, `session.idle`, `session.error`, `setTabMode` leaving plan.
|
|
68
|
-
- **ExitPlanMode mirror in `message.part.updated`** — Modern OpenCode SSE emits tools via `message.part.updated`, not legacy `tool.start`. Mirrored the ExitPlanMode/`plan_exit` detection (sets `_exitPlanDetected`, captures `toolInput.plan`, calls `showPostPlanUI`) so Anthropic models on modern OpenCode no longer rely solely on the `session.idle` fallback.
|
|
69
|
-
- **`message.completed` post-plan trigger** — Both rendered and fallback branches now call `maybeShowPostPlanUI` after clearing `tab.running`. Fixes the legacy-event silent stall.
|
|
70
|
-
- **`session.error` / `error` post-plan recovery** — When an error toast fires after plan content was already streamed, `maybeShowPostPlanUI` is called with `forceRecapture: true` so the user can still continue, edit, or retry without re-planning from scratch.
|
|
71
|
-
- **`sendMessage` catch-block recovery** — On non-abort, non-(timeout-with-recent-SSE) failures, `maybeShowPostPlanUI` runs with `forceRecapture: true` so a 4xx/5xx WS rejection after a successful plan stream doesn't strand the user.
|
|
72
|
-
- **Per-turn state reset** — `sendMessage` now resets `_exitPlanDetected` and `_pendingPostPlanCheck` (and clears the watchdog) at turn start. Previously these were only cleared on entering plan mode, breaking multi-turn plan sessions where the second turn's fallbacks would short-circuit on `!_exitPlanDetected`.
|
|
73
|
-
- **Watchdog ID not persisted** — Verified `_planFinalizationWatchdogId` is excluded from `saveTabs` serialization (only listed transient fields are persisted).
|
|
74
|
-
|
|
75
|
-
## 2026-04-30
|
|
76
|
-
|
|
77
|
-
### Fixed — Duplicate Hook Entries in `.claude/settings.json` (Claude Code Hook Installation)
|
|
78
|
-
- **Every Claude Code hook fired twice** — `addHookToSettings()` in `neural-interface/server.js` keyed dedup on exact command-string match, but `hookCommandString()` produces two valid forms for the same script depending on `targetProjectPath`: relative `node hooks/claude-code/<script>` for the SynaBun project itself, absolute `node "/abs/path/<script>"` otherwise. Different install flows (global cascade vs per-project) wrote different forms to the same `<project>/.claude/settings.json`, both passed the exact-string check, and accumulated. Symptom: every Stop-hook block reason was emitted twice (the user saw two near-identical "SynaBun Loop: Before this iteration ends..." Stop-hook errors per turn), and every recall-context block injected into `UserPromptSubmit` showed up twice. The two invocations also raced on the `pending-remember`/loop flag retry counters, so `MAX_RETRIES` could be hit in a single turn.
|
|
79
|
-
- **Variant-aware dedup with self-healing sweep** — New `hookCommandVariants(scriptName, targetProjectPath)` returns the set of every valid command-string form for a given script (relative + absolute when the target is `PACKAGE_ROOT`, absolute only otherwise). `addHookToSettings()` now walks `settings.hooks[def.event]` BEFORE pushing and removes any pre-existing entries whose command is in that set, keeping at most the first canonical match. Idempotent on re-install.
|
|
80
|
-
- **Startup self-heal across global + every registered project** — New `dedupeAllSettingsHooks()` runs in the existing startup block (`server.js`, just before the orphaned-loop cleanup). Walks `~/.claude/settings.json` and every `<projectPath>/.claude/settings.json` in the registered-projects list, applies the same variant-aware sweep across every event in `HOOK_SCRIPTS`, rewrites only when something changed. Logs `Hooks: deduped N stray entr[y/ies] across M settings file(s)` so the repair is visible. Cleans up existing damage from prior versions of the install path without user action.
|
|
81
|
-
|
|
82
|
-
### Added — Session Monitor Loops Tab + Title-Bar Stale-Loop Alert
|
|
83
|
-
- **New `Loops` tab in Session Monitor** — `neural-interface/public/shared/ui-sessions.js` adds a fourth tab between `Agents` and `Health` with `ICON_LOOP` glyph. Module-level `loops`/`staleLoopCount` state. Tab badge turns warning-orange when `staleCount > 0`. `_activeTab` enum extended with `'loops'`.
|
|
84
|
-
- **Per-loop card with state classification and live stalled timer** — `renderLoopCard()` shows a `Running` / `Finished` / `Orphaned` / `Stale` badge (driven by `loopStateClass()`/`loopStateLabel()`), a `${current}/${total}` iteration counter, a live `Stalled <Xm Ys>` timer (`now - lastIterationAt`, gray under 5min, red `.sm-loop-stalled-hot` at ≥5min), task preview, started-at relative time, terminal short-id with alive/dead pulse, and a per-loop `Stop` button that calls `POST /api/loop/stop` with that `terminalSessionId`. The stalled cell carries `data-stalled-ms`/`data-fetched-at` so the 1s `_stalledTickerTimer` can re-write `formatStalled(...)` without a server round-trip. Ticker only runs while `_activeTab === 'loops'` (`startStalledTicker()`/`stopStalledTicker()`).
|
|
85
|
-
- **Bulk action bar** — `Clear stale` (calls new `POST /api/loop/cleanup`, deletes only `active === false` files; disabled when zero stale) and `Clear all` (calls existing `POST /api/loop/stop` with empty body to kill PTY + close browser tab + delete file for every loop, with a `confirm()` prompt because it kills running work).
|
|
86
|
-
- **Title-bar alert button next to the cost value** — `neural-interface/public/shared/html-shell.js` adds `#titlebar-loops-alert-btn` immediately before `#titlebar-cost-btn` in the `bar-right` group, hidden by default. `neural-interface/public/shared/ui-navbar.js` imports the new `openLoopsTab` / `fetchLoopsForBadge` exports, wires click → `openLoopsTab()`, subscribes to the new `loops:updated` event (toggles `hidden` and writes `#titlebar-loops-count`), and bootstraps via `fetchLoopsForBadge()` plus a 30s polling fallback so the badge stays accurate even when the Session Monitor was never opened (the WS only connects when the panel opens).
|
|
87
|
-
- **Backend endpoints** — `GET /api/loop/list` returns the full inventory of `data/loop/*.json` (active + inactive) with classified `state`, `stalledMs`, `terminalAlive`, plus a `staleCount` field. `POST /api/loop/cleanup` deletes only `active === false` files (no PTY/browser side-effects since they're leftovers); corrupt JSON is treated as stale and removed too. Both endpoints filter out `pending-*.json` and `active-pointer.json` from the scan so the loop pointer file isn't classified as a loop.
|
|
88
|
-
- **`session:loops` WebSocket broadcast** — New `scanLoopInventory()` helper shared by the GET endpoint and a periodic polling loop that broadcasts on hash change (drops minute counters from the hash so it doesn't churn every tick). `handleSessionMonitorWebSocket()`'s `session:init` payload now carries `loops` + `staleCount` alongside `sessions`/`leaks`. `ui-sessions.js` `handleWsMessage()` updates state and re-renders on `session:loops`, plus emits `loops:updated` so the title-bar badge reflects WS pushes immediately when the panel is open.
|
|
89
|
-
- **CSS** — `neural-interface/public/shared/styles.css` adds `.sm-loop-running/-finished/-orphan/-stale` card borders, `.sm-loop-badge` color variants, `.sm-loop-iter` mono pill, `.sm-loop-stalled[-hot]` color states, `.sm-loops-actionbar` sticky-bottom action bar, and `.bar-loops-alert-btn` orange icon styling with a red `count-badge` overlay matching the existing `titlebar-trash-btn` pattern.
|
|
90
|
-
|
|
91
|
-
### Fixed — OpenCode Sidepanel Tables Not Rendering (Markdown Pipeline)
|
|
92
|
-
- **Root cause: missing CSS, not parser** — `marked@14` in `neural-interface/public/shared/ocp/ocp-render.js` correctly produced GFM `<table>/<thead>/<tbody>/<tr>/<th>/<td>` HTML (`gfm: true` set, `<table>` whitelisted in `renderAssistantMarkdown()`'s `hasContent` check), but `neural-interface/public/shared/ocp/ocp-styles.js` had ZERO rules targeting any table element. Default browser tables on the panel's translucent dark background produced no visible borders and dark-on-dark text — effectively invisible. The Claude sidepanel (`ui-claude-panel.js:677-687`) already had table styles, which is why tables rendered correctly there but not in OpenCode.
|
|
93
|
-
- **Markdown tables CSS block** — Added `.ocp-msg-assistant .ocp-table-wrapper` (`overflow-x: auto`, rounded border, dark bg), `table` (`border-collapse`, monospace 11px, `min-width: 100%`), `thead` background, `th` (bold, padded, bottom border, `white-space: nowrap`), `td` (padded, subtle row separators, `vertical-align: top`), `tbody tr:hover td` highlight, column dividers between adjacent cells, and `th[align]/td[align]` overrides honoring GFM column alignment.
|
|
94
|
-
- **`wrapTables()` post-processor** — New helper in `ocp-render.js` wraps every unwrapped `<table>` in `<div class="ocp-table-wrapper">` so wide tables get horizontal scroll without breaking `border-collapse` (you can't put `display: block; overflow-x: auto` directly on the table element). Idempotent via the `parent.classList.contains('ocp-table-wrapper')` guard. Called from `postProcessRenderedHtml()` so initial render, every streaming frame, finalize, and history-replay paths all wrap consistently.
|
|
95
|
-
|
|
96
|
-
### Changed — OpenCode Sidepanel Markdown Polish (Headings, HR, Task Lists)
|
|
97
|
-
- **Heading sizing fill-in** — Added explicit `h1` (15px), `h2` (13.5px), and `h4`/`h5`/`h6` (11.5px, semibold, brighter text) rules in `neural-interface/public/shared/ocp/ocp-styles.js`. Previously only `h3` had a size override; the others fell back to browser defaults that didn't match the panel scale.
|
|
98
|
-
- **HR gradient line** — `<hr>` now renders as a 1px linear-gradient line (transparent → 10%-white at 30-70% → transparent) so the rare horizontal-rule output looks intentional instead of a stark default browser bar. Note: `renderAssistantMarkdown` still strips `<hr>` to clean up CLI noise; the styling is in place if that filter is later removed.
|
|
99
|
-
- **GFM task list checkboxes** — Added `accent-color` and indent rules so `[ ]` / `[x]` checkboxes inherit the panel's off-white accent and render without the default disc bullet, using `:has(> li > input[type="checkbox"])` to scope the tweak to actual task lists only.
|
|
100
|
-
|
|
101
|
-
### Added — MCP Profile Change Sync for Codex/OpenCode Sidepanels
|
|
102
|
-
- **Live profile-change broadcast** — `neural-interface/server.js` now normalizes MCP profile names, broadcasts `mcp:profile-changed` over `/ws/sync`, and includes current `presets` plus `synced` results whenever `applyMcpProfile()` changes the active profile.
|
|
103
|
-
- **File-driven profile watcher** — New `startMcpProfileWatchers()` watches runtime `active-profile.json` locations, debounces writes from `mcp__SynaBun__profile(action=set)`, and routes external changes through `applyMcpProfile(..., 'file')` so all profile files and CLI env configs stay aligned.
|
|
104
|
-
- **Sidepanel profile listeners** — `neural-interface/public/shared/ui-sync.js` forwards `mcp:profile-changed` to the shared state bus, and `cdx-panel.js` / `ocp-panel.js` now use `applyMcpProfileState()` to rebuild dropdown items and selected labels immediately when the AI changes the profile.
|
|
105
|
-
|
|
106
|
-
### Changed — MCP Registry Profile Activation Consistency
|
|
107
|
-
- **Registry activation uses the shared profile path** — `/api/mcp/registry/activate` now calls `applyMcpProfile(profileValue, 'registry')` instead of writing only `DATA_HOME/data/active-profile.json`, so settings-driven activation updates every runtime profile file, Codex/OpenCode env sync, and the live sidepanel broadcast through the same code path.
|
|
108
|
-
|
|
109
|
-
## 2026-04-29
|
|
110
|
-
|
|
111
|
-
### Added — Google Search Console MCP Tool Group (30 tools)
|
|
112
|
-
- **Full GSC control via browser automation** — New `gsc` tool group registered in `mcp-server/src/tools/gsc.ts` (barrel) plus `mcp-server/src/tools/gsc/{helpers,navigate,inspect,performance,indexing,experience,settings}.ts` (~1300 LOC). 30 tools cover every section of Google Search Console without an API key, using the user's authenticated Google session in the SynaBun-managed Playwright browser.
|
|
113
|
-
- **Tools shipped** —
|
|
114
|
-
- **Navigation & property:** `gsc_navigate` (33 page enums incl. all 17 enhancement reports), `gsc_property` (list/select/current/add).
|
|
115
|
-
- **URL Inspection:** `gsc_inspect_url` (polls up to 30s, returns structured JSON of indexingState/canonicals/lastCrawl/sitemap/robots/enhancements), `gsc_inspect_test_live` (90s poll for live test), `gsc_inspect_request_indexing` (mutating), `gsc_inspect_view_crawled` (html/screenshot/http_response/more_info tabs).
|
|
116
|
-
- **Performance:** `gsc_performance_query` (searchType, dateRange preset/custom, dimension, filter chips, comparison, auto-scroll grid, returns totals + table JSON), `gsc_performance_export` (CSV/Excel/Sheets), `gsc_performance_chart_screenshot`.
|
|
117
|
-
- **Indexing:** `gsc_pages_report` (totals + reasons + drill-down), `gsc_pages_validate_fix` (mutating), `gsc_videos_report`, `gsc_sitemap` (list/submit/delete/view_errors), `gsc_removals` (list_temporary/list_outdated/list_safesearch/new), `gsc_removals_cancel`.
|
|
118
|
-
- **Experience & links:** `gsc_cwv_report`, `gsc_https_report`, `gsc_security_issues`, `gsc_manual_actions` (incl. reconsideration submit), `gsc_enhancements` (any of 17 structured-data reports), `gsc_links_report` (4 sections, auto-pagination), `gsc_links_export`.
|
|
119
|
-
- **Settings & meta:** `gsc_settings`, `gsc_crawl_stats`, `gsc_users` (list/add/remove/change_role), `gsc_associations` (Analytics/Merchant/Ads/Play/YouTube/Actions/Chrome Web Store), `gsc_disavow` (download/upload/delete, mutating), `gsc_shopping`, `gsc_extract_table` (generic grid → JSON), `gsc_screenshot`.
|
|
120
|
-
- **Locale tolerance** — All selectors prefer stable URL paths (`/inspect`, `/performance/search-analytics`, `/index/pages`, `/sitemaps`, `/removals`, `/links`, `/core-web-vitals/MOBILE`, etc.), material-icon ligature text (`home`, `trending_up`, `search`, `account_tree`), and aria roles. Translated UI strings are matched only as fallback regex with both English and Portuguese variants. Verified against the existing logged-in `sc-domain:synabun.ai` session running in Portuguese.
|
|
121
|
-
- **Polling helper** — `pollFor()` in `mcp-server/src/tools/gsc/helpers.ts` evaluates a JS predicate every 400ms (configurable) up to a timeout, returning the first truthy result. Used by every async-loading panel (URL Inspection result render, live-test completion, request-indexing modal, sitemap submission status, validate-fix confirmation, removal submission). Default cap 30s, bumped to 90s for live URL tests.
|
|
122
|
-
- **Generic table extractor** — `TABLE_EXTRACTOR_SCRIPT(scope)` walks `[role="grid"]` / `[role="table"]` / `<table>`, emits `{ headers, rows }` with header→cell mapping plus per-row `url` when an anchor is present. Backbone of every report tool.
|
|
123
|
-
- **Auth check** — `ensureAuth()` returns `'Not authenticated. Sign into the Google account...'` when the page redirects to `accounts.google.com`, instead of failing on missing selectors.
|
|
124
|
-
- **Profile presets** — Added `gsc` to `full` and `browser` presets and added a dedicated `gsc` preset (`git, image, browser, gsc`) in `mcp-server/src/services/profiles.ts`. `VALID_GROUPS` extended with `'gsc'`.
|
|
125
|
-
- **Index wiring** — `mcp-server/src/index.ts` imports `registerGscTools`, registers the group via `setToolGroup('gsc', ...)`, exposes the group line in `TOOL_GROUP_INSTRUCTIONS.gsc`, and tracks usage via the `gsc_` prefix in `groupToolPrefixes`.
|
|
126
|
-
- **Documentation** — Updated `README.md` (architecture box, MCP-tools heading and total `76 → 106`, added a full GSC section grouping all 30 tools by area, plus a tip about authentication and polling timeouts) and `CLAUDE.md` (architecture line bumped from `72 tools / 9 groups` to `106 / 12` with the locale-tolerance note).
|
|
127
|
-
|
|
128
|
-
## 2026-04-28
|
|
129
|
-
|
|
130
|
-
### Added — OpenCode Model Capability Explainers
|
|
131
|
-
- **Capability metadata and inline help** — Extended `CAP_BADGES` in `neural-interface/public/shared/ocp/ocp-tabs.js` with abbreviations and human-readable descriptions for `Reason`, `Tools`, `Vision`, `Audio`, `Video`, `PDF`, `ImgGen`, `TTS`, and `Files`. `populateModelDropdown()` now renders an inline `ocp-cap-help` strip that updates on capability chip hover/focus and active filter state, so users can understand each model capability without a floating tooltip covering the controls.
|
|
132
|
-
|
|
133
|
-
### Fixed — OpenCode Model Selector Filtering and Tooltip Overlap
|
|
134
|
-
- **Capability filters no longer stack additively** — Replaced the previous `activeCaps` `Set` / AND matching logic in `populateModelDropdown()` with a single `activeCapKey`, so selecting a capability replaces the previous filter and clicking the active capability clears it. Added an `All` chip and `ocp-dd-empty` clear action for no-result states.
|
|
135
|
-
- **Pill explanations no longer overlap the filter row** — Removed `data-tooltip` overlays from capability filter pills and moved their explanations into the dropdown header via `showCapHelp()`, `restoreCapHelp()`, and `bindCapHelp()`. Model-row capability badges still keep compact `data-tooltip` explanations, positioned above the badge instead of over the pill bar.
|
|
136
|
-
|
|
137
|
-
### Changed — OpenCode Model Selector Visual Refresh
|
|
138
|
-
- **Neutral OpenCode-style selector surface** — Restyled `#ocp-model-dd .ocp-dd-menu` in `neural-interface/public/shared/ocp/ocp-styles.js` with a darker glassy surface, restrained monochrome capability chips/badges, neutral favorite states, a wider 520px model menu, and grid-based model rows for model name, capabilities, and favorite star.
|
|
139
|
-
- **Subtle selector motion** — Added `ocp-model-menu-in` and `ocp-cap-help-in` animations, plus lightweight hover/active transitions for capability chips and model rows, making the selector feel fresher without the earlier rainbow-heavy visual treatment.
|
|
140
|
-
|
|
141
|
-
### Fixed — SynaBun MCP Auto-Heal After Claude Code Updates
|
|
142
|
-
- **MCP entry survives CC updates** — Claude Code updates (especially when the installer runs as root via `sudo npm i -g @anthropic-ai/claude-code`) could wipe `~/.claude.json` or strip `mcpServers.SynaBun`, leaving the side panel and CLI with SynaBun MCP disabled until the user manually re-registered it via the Neural Interface setup window. Added `healSynaBunMcp()` to `hooks/claude-code/session-start.mjs` that runs on every session start: reads `~/.claude.json`, ensures `mcpServers.SynaBun = { type: 'http', url: 'http://localhost:3344/mcp' }`, writes back only if missing or incorrect (idempotent on already-correct state). Heal events are logged to `data/compact-debug.log` with the `MCP-HEAL` prefix. Wrapped in `try/catch` — non-critical failures cannot block session start.
|
|
143
|
-
|
|
144
|
-
### Changed — OpenCode Capability Help Strip Polish
|
|
145
|
-
- **No layout jump on capability pill hover** — `.ocp-cap-help` strip in `neural-interface/public/shared/ocp/ocp-tabs.js` now stays mounted at all times, falling back to the `ALL_CAP_HELP` ("All — Showing every visible model.") badge when no pill is hovered and no filter is active. Replaced the prior `capHelp.hidden = true/false` toggling — which caused model rows below to shift on every mouseenter/leave — with a persistent strip and `applyCapHelp()` helper that tracks `lastHelpKey` to suppress redundant animation restarts.
|
|
146
|
-
- **Smooth filter-toggle collapse** — Filter-toggle button (`.ocp-cap-filter-toggle`) now toggles `.ocp-cap-help--collapsed` on the strip instead of hiding it abruptly. CSS in `neural-interface/public/shared/ocp/ocp-styles.js` animates `max-height`, `opacity`, `margin`, `padding`, and `border-width` over 220ms (cubic-bezier 0.2/0.8/0.2/1) for a smooth open/close, and `restoreCapHelp()` only fires on expand.
|
|
147
|
-
- **Crossfade-feel on badge text swap** — Added `@keyframes ocp-cap-help-swap` (opacity 0.45 → 1, 1px translateY, 0.4px blur clearing) and `.ocp-cap-help--swap` class. `applyCapHelp()` removes the class, forces reflow via `void capHelp.offsetWidth`, then re-adds it whenever `lastHelpKey` changes, producing a 180ms ease-out fade between badge descriptions instead of an abrupt text flip.
|
|
148
|
-
- **Description text wraps to two lines** — `.ocp-cap-help-text` switched from `white-space: nowrap` + ellipsis to `-webkit-line-clamp: 2` + `-webkit-box-orient: vertical` + `white-space: normal`. Long descriptions like "Stronger at multi-step reasoning, planning, and hard problem solving." now wrap fully within the strip's 32-80px height range instead of truncating with an ellipsis.
|
|
149
|
-
- **Active filter highlight** — Added `.ocp-cap-help--active` modifier that brightens the strip's border and gradient background when a real capability filter (not "All") is active, giving visual continuity between the pill and the explanation.
|
|
150
|
-
|
|
151
|
-
### Fixed — SynaBun Loop "No active loop" After /clear (Claude Code Hook-Driven Loop)
|
|
152
|
-
- **Stop hook stuck blocking with `call \`loop\` action \`update\``** — `mcp-server/src/tools/loop.ts` `resolveSessionId` returned `process.env.CLAUDE_SESSION_ID` without verifying a flag file existed. Under HTTP MCP transport (stateless, no per-call env) and after `/clear` (env stale), the lookup returned null → `loop update` reported `"No active loop"` → `hooks/claude-code/stop.mjs:489-503` re-blocked 3× per iteration with the journal-update nag, then advanced iterations with a stale journal.
|
|
153
|
-
- **`resolveSessionId` now verifies before trusting env** — added `isFreshActive()` (45-min staleness rule). New strategy chain: explicit param → verified `CLAUDE_SESSION_ID` (flag must exist + be active) → `SYNABUN_TERMINAL_SESSION` scan → `active-pointer.json` fallback → legacy unclaimed loops → exactly-one-fresh-active fallback.
|
|
154
|
-
- **Hook writes `data/loop/active-pointer.json` on every claim** — `writeActivePointer()` / `clearActivePointer()` in `hooks/claude-code/stop.mjs` persist `{sessionId, terminalSessionId, claudeSessionId, updatedAt}` on exact-match, Strategy A, and Strategy B claim paths. Cleared on iteration cap. Bridges env gap between hook (live Claude env) and HTTP MCP daemon (stale env).
|
|
155
|
-
- **`cleanupStaleLoops` aware of pointer file** — `hooks/claude-code/shared.mjs` skips `active-pointer.json` from the `.json` flag scan (different schema, would have been wrongly deleted as inactive) and drops the pointer when its target flag no longer exists.
|
|
156
|
-
- **MCP daemon rebuild** — recompiled `mcp-server/dist/tools/loop.js` via direct `node node_modules/typescript/bin/tsc` (the `node_modules/.bin/tsc` shim was broken).
|
|
157
|
-
|
|
158
|
-
### Fixed — OpenCode Sidepanel "Edit Plan" Empty Content
|
|
159
|
-
- **Robust click handler with multi-layer fallback chain** — Replaced silent-fail `setOnEditPlan` callback in `neural-interface/public/shared/ocp/ocp-panel.js` (previously bailed on `if (!tab.planContent) return`) with a handler mirroring `ui-claude-panel.js`. Chain: `tab.planFilePath` → `extractPlanTextLoose(tab)` → `requestAnimationFrame` retry → `fetchPlanFromMessagesAPI()` (WS `messages:list` proxy) → `/api/latest-plan` disk fallback. Added `setBusy()` opacity/pointer-events on the post-plan card while async work is in flight, `noFile()` rendering a visible `renderErrorMessage` instead of silently bailing, and `materialize()` posting to `/api/create-plan`.
|
|
160
|
-
- **Card element threaded through callback** — `renderPostPlanCard()` in `neural-interface/public/shared/ocp/ocp-render.js` now passes the card DOM node to `onEditPlan(card)`; `showPostPlanUI()` in `ocp-tabs.js` forwards as `_onEditPlan(tab, card)` so the click handler can disable/restore the card during async failures.
|
|
161
|
-
- **`extractPlanTextLoose(tab)` four-layer scan** — New exported helper in `ocp-tabs.js` ignores the `text.length > 80` and `tab.mode !== 'plan'` guards from the original `capturePlanContent`. Order: cached `tab.planContent` → ExitPlanMode tool card args (parses `.ocp-tool-args` JSON for `input.plan/.markdown/.content/.text`, regex-matched by `/exit\s*plan|plan[\s_-]?exit/i` on `.ocp-tool-name`) → last `.ocp-msg-assistant` text bubble → standalone `.ocp-think-block .ocp-think-content` (reasoning-only output) → Agent/task subagent `.ocp-tool-result`. Last-resort fallback to any short bubble.
|
|
162
|
-
- **ExitPlanMode tool input captured at `tool.start`** — `tool.start` handler in `ocp-tabs.js` reads `toolInput.plan || .markdown || .content || .text` and stores it on `tab.planContent` before `showPostPlanUI()`. Falls back to `capturePlanContent()` if input fields empty. The plan text lives in tool args, not the assistant message bubble.
|
|
163
|
-
- **Live capture during `message.part.updated`** — Both `text` and `reasoning|thinking|thought` part types now write to `tab.planContent` (length-monotonic) when `tab.mode === 'plan'`, so post-plan extraction has authoritative content even when the bubble rendered into a Thought block. Handles plan agents that emit all output as reasoning.
|
|
164
|
-
- **`fetchPlanFromMessagesAPI()` server-side fallback** — In `ocp-panel.js` `setOnEditPlan`, calls `requestWs('messages:list', { sessionId }, 15000)` (existing WS proxy at `server.js:5285` → `GET /session/{id}/message`). Walks the most recent assistant message, concatenates `type === 'text'` parts then `reason|think` parts (length > 40 threshold). Bypasses DOM scraping entirely.
|
|
165
|
-
- **`tab._planModeStartedAt` timestamp for disk fallback** — `setTabMode()` stamps `Date.now()` when entering plan mode. `diskFallback()` validates `/api/latest-plan` mtime against plan-mode start (skipped when unset to support pre-existing tabs).
|
|
166
|
-
|
|
167
|
-
### Fixed — OpenCode "Request message:send timed out" False Toast
|
|
168
|
-
- **WS request timeout bumped 6 min → 30 min** — `requestWs('message:send', ...)` in `neural-interface/public/shared/ocp/ocp-tabs.js` `sendMessage` was rejecting at 360000ms; Plan agent + subagent runs routinely exceed that. Bumped to 1800000ms.
|
|
169
|
-
- **SSE liveness suppresses red error** — `handleSSEEvent()` stamps `tab._lastEventAt = Date.now()` on every event. `sendMessage` catch block detects `/^Request\s+message:send\s+timed out$/i` and, when `Date.now() - tab._lastEventAt < 60000`, suppresses `renderErrorMessage` AND skips the `tab.running = false` / `endTurn` cleanup so SSE can drive completion to `session.idle` naturally.
|
|
170
|
-
|
|
171
|
-
### Changed — OpenCode Plan Mode SDK-Aligned Tool Restrictions
|
|
172
|
-
- **`body.mode` + per-message `body.tools` restrictions** — `sendMessage` in `neural-interface/public/shared/ocp/ocp-tabs.js` now sets `body.mode = tab.mode` (canonical SDK field per OpenCode `app.modes()`) and, when `tab.mode === 'plan'`, pins `body.tools = { write: false, edit: false, patch: false, bash: false }`. Belt-and-suspenders enforcement so the plan agent cannot even attempt mutating tools.
|
|
173
|
-
|
|
174
|
-
### Changed — OpenCode Plan-Mode Tool Denials Soft-Fail
|
|
175
|
-
- **Permission denials render as neutral "Skipped" tile** — `tool.result` handler in `neural-interface/public/shared/ocp/ocp-tabs.js` detects plan-mode permission errors via `/permission|not allowed|forbidden|read.?only|disabled|disallow|file_edit|edit not|write not|patch not|bash not/i`, renders the tool card with status `complete` and message "Skipped — plan mode is read-only" instead of the red error badge.
|
|
176
|
-
|
|
177
|
-
### Fixed — Restart Click Failed After Native-Module Crash During Shutdown
|
|
178
|
-
- **`Restart Server` from apps dropdown left no server running** — `/api/server/restart` in `neural-interface/server.js:1631` exited with sentinel code `75` so the `setup.js` supervisor would respawn. Native module teardown (sqlite / Playwright / node-pty) crashed mid-exit with `mutex lock failed: Invalid argument` (libc++abi → SIGABRT), clobbering the sentinel exit code. The supervisor's `child.on('exit')` in `setup.js:264` saw an abnormal non-75 code, hit the `fail()` + `process.exit(code)` branch, and died — no respawn, shell prompt returned.
|
|
179
|
-
- **Marker-file fallback makes restart resilient to crash codes** — `/api/server/restart` now writes `DATA_HOME/restart-requested` via `writeFileSync(RESTART_MARKER, ...)` before deferring shutdown. `setup.js` `child.on('exit')` treats marker presence the same as exit code `75` and respawns regardless of the actual code, then deletes the marker. Added `unlinkSync(RESTART_MARKER)` at the top of `startServer()` to clear stale markers on supervisor startup so a leftover from an unsupervised crash cannot trigger a spurious respawn. The crash-rate limiter (`5` restarts in `60s`) still applies so any crash loop stays bounded.
|
|
180
|
-
|
|
181
|
-
## 2026-04-27
|
|
182
|
-
|
|
183
|
-
### Fixed — OpenCode Sidepanel Plan Mode Subagent Stall
|
|
184
|
-
- **Plan mode appeared frozen during `task` subagent runs** — When the OpenCode plan agent dispatched a `task` subagent (e.g. `@explore`), the panel showed no progress for 60-150+ seconds while the subagent worked, leading users to abort. Root cause: `tab.toolActivityChildSessions` (parent task entry → child session id map) was only populated by `recordToolResult()` after the subagent completed, so all child SSE events (`tool.start`, `tool.result`, `message.part.updated`, `session.idle`) failed `findAgentEntryByChildSession()` lookup at `ocp-tabs.js:1570` and were dropped at line 1577. Fix in `neural-interface/public/shared/ocp/ocp-tabs.js`:
|
|
185
|
-
- **Top-level `session.created` interceptor** in `handleSSEEvent()` — when `child.parentID` matches a tab's session id, links the first running `task`/`agent` activity entry without `childSessionId` via `linkChildSession()`.
|
|
186
|
-
- **Fallback link in `case 'message.part.updated'`** task-tool branch — reads `part.state.metadata.sessionId` when `session.created` arrived late.
|
|
187
|
-
- **Fallback link in `case 'tool.start'`** agent-tool branch — reads `event.metadata.sessionId` / `event.state.metadata.sessionId`.
|
|
188
|
-
- Once linked, child events route via `findAgentEntryByChildSession` → `recordAgentChildEvent` populates `entry.steps` → `syncAgentChildIndicator` updates thinking indicator with live child tool names instead of stuck "Thinking…".
|
|
189
|
-
|
|
190
|
-
### Fixed — Sidepanel Plan Mode "No File Created" (Claude Code)
|
|
191
|
-
- **`ExitPlanMode.input.plan` ignored by post-plan hook** — `hooks/claude-code/post-plan.mjs` previously stored only existing plan files matched against stringified `tool_response`, so when the UI failed to capture, no file ever reached SQLite or disk. Hook now reads the authoritative markdown via `readPlanField(input.tool_input)` / `readPlanField(input.tool_response)`. When no match exists (or the matched file is stale vs `tool_input.plan`), the hook authors `data/plans/YYYY-MM-DD/<slug>.md` itself using `extractPlanTitle()` + `slugify()` + `uniqueSlugPath()` before embedding and storing. Debug log tags the new path as `method: 'authored-from-tool_input'`.
|
|
192
|
-
- **Inverted priority in `extractPlanTextFromMessage()`** — `neural-interface/public/shared/ui-claude-panel.js:5414` previously preferred any text block > 100 chars over `ExitPlanMode.input.plan`, so preamble narration was written to the plan file instead of the plan. Reordered to check `exitTool.input.plan` first, text fallback last.
|
|
193
|
-
- **Eager-capture chain captured DOM thinking text** — three `renderAssistant()` callsites at lines 4968, 5048, 5123 used `direct || extractPlanTextFromMessage(msg) || extractPlanText(tab)`, so when `ExitPlanMode.input.plan` was empty the chain fell through to DOM walks that grabbed `Thought›` thinking renders. Tightened to `const captured = direct;` — eager capture now only fires on authoritative plan input; the post-plan hook is the rescue when direct is empty.
|
|
194
|
-
- **`/api/create-plan` accepted thinking-prefix garbage** — `neural-interface/server.js:11960` slugified the first non-empty line into filenames like `thought-the-user-wants-me-to-search-....md`. Added 422 rejection when content has no H1 AND first line matches `/^(thought\b|›|thinking[:\s]|let me\b|i'?ll\b|i will\b|the user wants? me\b)/i`, preventing garbage plan files from accumulating.
|
|
1
|
+
# SynaBun Changelog
|
|
2
|
+
|
|
3
|
+
Raw internal changelogs are archived in [changelog/](changelog/).
|
|
4
|
+
|
|
5
|
+
## v.2026.5.3 — 2026-04-27 → 2026-05-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Google Search Console MCP tool group** — 30 new tools covering every GSC section (URL Inspection, Performance, Pages / Videos / Sitemaps, Removals, Core Web Vitals, HTTPS, Security Issues, Manual Actions, Enhancements, Links, Disavow, Crawl Stats, Users, Associations, Shopping, screenshots) via Playwright-driven browser automation. No API key required — uses your authenticated Google session. Locale-tolerant selectors (English + Portuguese verified). Added a dedicated `gsc` profile preset and bundled into `full` and `browser`.
|
|
10
|
+
- **Notifications drawer** — Session Monitor reworked into a unified Notifications drawer with a new Updates tab for ruleset, SynaBun, and CLI-tool updates. Bell badge in the titlebar shows unread count and stays accurate via 5-minute polling without ever opening the drawer. Per-version ruleset alerts driven by a manifest so whitespace edits don't cause notification noise.
|
|
11
|
+
- **Loops tab** — Per-loop card with state badges (Running / Finished / Orphaned / Stale), live stalled timer, terminal alive/dead pulse, per-loop Stop button, and bulk Clear-stale / Clear-all controls. New `/api/loop/list` and `/api/loop/cleanup` endpoints; live `session:loops` WebSocket broadcast.
|
|
12
|
+
- **Sidepanel kill-on-close** — Closing a tray pill now force-kills the underlying Claude / Codex / OpenCode session immediately rather than waiting for the orphan grace timer.
|
|
13
|
+
- **OpenCode model capability explainers** — Inline help strip describes each capability (Reason, Tools, Vision, Audio, Video, PDF, ImgGen, TTS, Files) on chip hover, focus, or active filter. Replaces overlapping floating tooltips.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **OpenCode markdown rendering** — Tables now render with proper styling (rounded border, horizontal scroll wrapper, header background, hover highlight, GFM column-alignment honored). H1 / H2 / H4–H6 sized to match panel scale, HR uses a subtle gradient line, GFM task-list checkboxes themed.
|
|
18
|
+
- **OpenCode model selector** — Capability filters now replace instead of stacking, with an All chip and clear-action for empty results. Neutral OpenCode-style dropdown surface with subtle hover/active motion.
|
|
19
|
+
- **OpenCode capability help strip** — No layout jump on capability hover; smooth filter-toggle collapse; crossfade text swap when switching between capabilities; descriptions wrap to two lines instead of truncating.
|
|
20
|
+
- **Plan-mode tool restrictions (OpenCode)** — Plan mode now uses canonical SDK `body.mode` plus per-message tool restrictions. Plan-mode permission denials render as neutral "Skipped" tiles instead of red errors.
|
|
21
|
+
- **Session monitor → Notifications drawer** — Renamed and re-themed. Drawer auto-defaults to the Updates tab when at least one unread item exists, otherwise falls back to Sessions. Internal class names and event names preserved for compatibility.
|
|
22
|
+
- **MCP profile sync** — Profile changes now broadcast over WebSocket and reflect immediately in Codex / OpenCode sidepanel dropdowns. File-driven changes (`mcp__SynaBun__profile(action=set)`) and registry activation both route through the same shared path so all profile files stay aligned.
|
|
23
|
+
- **Window-id storage GC** — Across Claude, Codex, and OpenCode panels, stale window-scoped tab keys are now garbage-collected on module load (>24h idle, plus an unregistered-key sweep that catches legacy entries from before the registry existed). Significantly reduces server-side state size.
|
|
24
|
+
- **Titlebar icon polish** — Notifications swapped to a clean bell glyph; Image Gallery swapped to the universal photo glyph. Removed the dedicated Stale-Loops alert button (loops still surface via the drawer's Loops-tab badge).
|
|
25
|
+
- **Backup coverage** — Already shipped earlier; mentioned here for context with the new restart marker work.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **OpenCode question-tool deadlock with Kimi / non-Anthropic models** — Answering an interactive question card no longer hangs indefinitely when the OpenCode session-processor doesn't resume the LLM stream after the answer is accepted. A 75-second watchdog auto-recovers by aborting the stuck turn and resending the answer as a regular message. Anthropic / Qwen flows clear the watchdog within ~1s as before.
|
|
30
|
+
- **OpenCode plan-mode silent stall with Deepseek / non-Anthropic models** — The post-plan action card (Edit / Continue / Compact) now reliably appears via an 8-second finalization watchdog plus multiple terminal-event triggers, even when the plan agent's subagent fires `session.idle` but the parent never finalizes. Recovers from `session.error` and post-stream WS rejections so a half-completed plan isn't stranded.
|
|
31
|
+
- **OpenCode plan → build / chat read-only tools with Qwen / non-Anthropic models** — Continuing into implementation after a plan no longer leaves the model with only read-only tools. Build and chat turns now explicitly re-enable write / edit / patch / bash to override OpenCode's session-level tool-restriction persistence.
|
|
32
|
+
- **OpenCode subagent progress** — Plan agent dispatching a `task` subagent now shows live child progress (with the subagent's current tool name) instead of stalling for 60–150 seconds with a generic "Thinking…" indicator.
|
|
33
|
+
- **OpenCode "Edit plan" empty content** — Edit-plan button now uses a 5-layer fallback chain (cached content → ExitPlanMode tool args → assistant text bubble → reasoning / Thought block → subagent result → server `messages:list` API → disk fallback) so the editor opens with real plan content even when the model surfaced the plan via reasoning or a subagent. Visible error message replaces the prior silent fail.
|
|
34
|
+
- **OpenCode "Request message:send timed out" false toast** — WebSocket request timeout bumped from 6 to 30 minutes (Plan agent + subagent runs routinely exceed the old cap). When a timeout does fire while SSE events are still streaming, the red error toast is suppressed and the session is allowed to drive itself to natural completion.
|
|
35
|
+
- **OpenCode tray pills hidden on fresh browser tabs** — Cross-window pill discovery now finds sessions saved under any prior window-id and migrates them into the current window on click. ~70 OpenCode / ~14 Codex sessions that previously required a toolbar click to surface now appear in the tray automatically.
|
|
36
|
+
- **OpenCode MCP profile dropdown label** — Dropdown label now updates immediately when the active profile changes (via MCP tool, sidepanel API, or file reload), instead of showing the previous profile until next page load.
|
|
37
|
+
- **OpenCode sidepanel tables not rendering** — Markdown tables (e.g. comparison tables in agent output) now render with visible borders, scroll wrapper, and proper text contrast. Was a missing-CSS issue, not a parser issue.
|
|
38
|
+
- **Sidepanel "no plan file created" with Claude Code** — Post-plan hook now authors the plan markdown file from `ExitPlanMode.input.plan` when the UI failed to capture, and rejects thinking-prefix garbage so plan files don't accumulate as `thought-the-user-wants-…md`. Eager DOM capture tightened so it no longer grabs preamble narration in place of the actual plan.
|
|
39
|
+
- **SynaBun loop "no active loop" after `/clear`** — Loop session resolution now verifies flag-file existence before trusting `CLAUDE_SESSION_ID`. New `active-pointer.json` fallback bridges the env gap between hook (live Claude env) and HTTP MCP daemon (stale env) under stateless transport. Stop-hook no longer re-blocks 3× per turn with the journal-update nag after a `/clear`.
|
|
40
|
+
- **SynaBun memory tools blocked by permission gate in CLI / PTY sessions** — Enabling SynaBun MCP now auto-injects allow rules for the 7 memory tools across Claude, Codex, Gemini, and OpenCode CLIs. Bare CLI sessions in PTY terminals no longer fall back to native memory or `CLAUDE.md` writes. Inject-only — never overwrites existing user permissions.
|
|
41
|
+
- **SynaBun MCP entry survives Claude Code updates** — Session-start hook now self-heals `~/.claude.json` so a `sudo npm i -g @anthropic-ai/claude-code` (which can wipe or strip `mcpServers.SynaBun`) doesn't leave SynaBun disabled until manual re-registration.
|
|
42
|
+
- **Duplicate Claude Code hook entries** — Hook installer now dedupes variant command-string forms (relative + absolute) and sweeps existing damage across global + per-project settings on startup. Stop-hook block reasons and recall-context blocks no longer print twice per turn.
|
|
43
|
+
- **Restart click after native-module crash** — `Restart Server` now writes a marker file before shutdown so the supervisor respawns even when sqlite / Playwright / node-pty teardown clobbers the sentinel exit code with `mutex lock failed`. Stale markers are cleared on startup so a supervised crash doesn't trigger a spurious respawn.
|
|
@@ -1405,6 +1405,10 @@ function populateProfileDropdown(dd) {
|
|
|
1405
1405
|
const lbl = dd.querySelector('.ocp-dd-label');
|
|
1406
1406
|
if (!menu || !lbl) return;
|
|
1407
1407
|
menu.innerHTML = '';
|
|
1408
|
+
const matched = _mcpProfiles.find(p => p.id === _currentProfile);
|
|
1409
|
+
lbl.textContent = matched ? matched.label : _currentProfile;
|
|
1410
|
+
dd.classList.add('has-value');
|
|
1411
|
+
dd._value = _currentProfile;
|
|
1408
1412
|
for (const p of _mcpProfiles) {
|
|
1409
1413
|
const opt = document.createElement('div');
|
|
1410
1414
|
opt.className = 'ocp-dd-option' + (p.id === _currentProfile ? ' selected' : '');
|
|
@@ -1524,6 +1524,10 @@ export function renderPostPlanCard(container, { headerText = 'PLAN COMPLETE', on
|
|
|
1524
1524
|
card.remove();
|
|
1525
1525
|
if (typeof onContinuePlanning === 'function') onContinuePlanning();
|
|
1526
1526
|
});
|
|
1527
|
+
card.querySelector('[data-action="continue-planning"]').addEventListener('click', () => {
|
|
1528
|
+
card.remove();
|
|
1529
|
+
if (typeof onContinuePlanning === 'function') onContinuePlanning();
|
|
1530
|
+
});
|
|
1527
1531
|
|
|
1528
1532
|
container.appendChild(card);
|
|
1529
1533
|
scrollToBottom(container);
|
|
@@ -179,6 +179,7 @@ let _onUpdate = null; // callback to refresh UI
|
|
|
179
179
|
let _panelVisible = false;
|
|
180
180
|
let _showPanel = null; // callback to show panel from tray pill click
|
|
181
181
|
let _hidePanel = null; // callback to close panel (last-tab-close)
|
|
182
|
+
let _activeChildSessionId = null; // sessionId of the currently active child agent's subprocess
|
|
182
183
|
|
|
183
184
|
export function getTabs() { return _tabs; }
|
|
184
185
|
export function getActiveTabIdx() { return _activeTabIdx; }
|
|
@@ -669,6 +670,8 @@ export function switchTab(idx) {
|
|
|
669
670
|
export function closeTab(idx) {
|
|
670
671
|
if (idx < 0 || idx >= _tabs.length) return;
|
|
671
672
|
const tab = _tabs[idx];
|
|
673
|
+
clearPlanFinalizationWatchdog(tab);
|
|
674
|
+
clearQuestionReplyWatchdog(tab);
|
|
672
675
|
flushSessionSnapshotSave(tab);
|
|
673
676
|
if (tab.pillEl) tab.pillEl.remove();
|
|
674
677
|
if (tab.trayPillEl) tab.trayPillEl.remove();
|
|
@@ -1092,6 +1095,11 @@ export function showPostPlanUI(tab, headerText = null) {
|
|
|
1092
1095
|
saveTabs();
|
|
1093
1096
|
update();
|
|
1094
1097
|
},
|
|
1098
|
+
onContinuePlanning: () => {
|
|
1099
|
+
tab.showPostPlanActions = false;
|
|
1100
|
+
saveTabs();
|
|
1101
|
+
update();
|
|
1102
|
+
},
|
|
1095
1103
|
});
|
|
1096
1104
|
}
|
|
1097
1105
|
|
|
@@ -1150,6 +1158,86 @@ function clearPlanFinalizationWatchdog(tab) {
|
|
|
1150
1158
|
}
|
|
1151
1159
|
}
|
|
1152
1160
|
|
|
1161
|
+
// Watchdog for the kimi-for-coding/k2p6 + OpenCode 1.14.30 deadlock: POST
|
|
1162
|
+
// /question/<id> returns 200 but session.processor never resumes — no second
|
|
1163
|
+
// LLM stream call, no message.part.delta, no session.idle. Without this, the
|
|
1164
|
+
// user sits in "Thinking…" until they manually Stop. After QUESTION_REPLY_WATCHDOG_MS
|
|
1165
|
+
// of total SSE silence post-reply, abort the stuck turn and resend the answer
|
|
1166
|
+
// as a regular `message:send` (which works fine — the bug is specific to the
|
|
1167
|
+
// question-tool resolve path, not normal message turns). Watchdog is cleared
|
|
1168
|
+
// in `handleSSEEvent` on any SSE event for this tab, in `abortTab`, and at the
|
|
1169
|
+
// start of `sendMessage`.
|
|
1170
|
+
const QUESTION_REPLY_WATCHDOG_MS = 75000;
|
|
1171
|
+
|
|
1172
|
+
function buildAnswerSyntheticMessage(questions, answers) {
|
|
1173
|
+
const fmt = (v) => Array.isArray(v) ? v.map(x => String(x ?? '').trim()).filter(Boolean).join(', ') : String(v ?? '').trim();
|
|
1174
|
+
if (typeof answers === 'string') return answers.trim();
|
|
1175
|
+
if (!answers || typeof answers !== 'object') return '';
|
|
1176
|
+
const entries = Object.entries(answers).filter(([, v]) => fmt(v));
|
|
1177
|
+
if (entries.length === 0) return '';
|
|
1178
|
+
if (entries.length === 1) return fmt(entries[0][1]);
|
|
1179
|
+
const lines = entries.map(([q, a]) => `- ${q}: ${fmt(a)}`);
|
|
1180
|
+
return `Here are my answers:\n${lines.join('\n')}`;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function scheduleQuestionReplyWatchdog(tab, syntheticMessage) {
|
|
1184
|
+
if (!tab || !syntheticMessage) return;
|
|
1185
|
+
if (tab._questionReplyWatchdogId) clearTimeout(tab._questionReplyWatchdogId);
|
|
1186
|
+
tab._questionReplyAnswerSnapshot = syntheticMessage;
|
|
1187
|
+
tab._questionReplyWatchdogId = setTimeout(() => {
|
|
1188
|
+
const snap = tab._questionReplyAnswerSnapshot;
|
|
1189
|
+
tab._questionReplyWatchdogId = null;
|
|
1190
|
+
tab._questionReplyAnswerSnapshot = null;
|
|
1191
|
+
if (!snap || !tab.running) return;
|
|
1192
|
+
executeQuestionReplyRecovery(tab, snap);
|
|
1193
|
+
}, QUESTION_REPLY_WATCHDOG_MS);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function clearQuestionReplyWatchdog(tab) {
|
|
1197
|
+
if (tab?._questionReplyWatchdogId) {
|
|
1198
|
+
clearTimeout(tab._questionReplyWatchdogId);
|
|
1199
|
+
tab._questionReplyWatchdogId = null;
|
|
1200
|
+
}
|
|
1201
|
+
if (tab) tab._questionReplyAnswerSnapshot = null;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
async function executeQuestionReplyRecovery(tab, syntheticMessage) {
|
|
1205
|
+
// sendMessage operates on activeTab(); if the user switched away while
|
|
1206
|
+
// waiting, switch back so the synthetic recovery lands in the right tab.
|
|
1207
|
+
if (tab !== activeTab()) {
|
|
1208
|
+
const idx = _tabs.indexOf(tab);
|
|
1209
|
+
if (idx >= 0) switchTab(idx);
|
|
1210
|
+
}
|
|
1211
|
+
const live = panelEl('#ocp-messages');
|
|
1212
|
+
if (live) {
|
|
1213
|
+
renderErrorMessage(live, "Model didn't resume after answering. Auto-recovering — resending your answer as a regular message…");
|
|
1214
|
+
removeThinking(live);
|
|
1215
|
+
finalizeStreamingMessage(live);
|
|
1216
|
+
}
|
|
1217
|
+
if (tab._activeSendRequestId) {
|
|
1218
|
+
try { rejectPending(tab._activeSendRequestId, 'Question stall recovery'); } catch {}
|
|
1219
|
+
if (_activeSendRequestId === tab._activeSendRequestId) _activeSendRequestId = null;
|
|
1220
|
+
tab._activeSendRequestId = null;
|
|
1221
|
+
}
|
|
1222
|
+
setActiveQuestionToolId(tab, null);
|
|
1223
|
+
tab.running = false;
|
|
1224
|
+
tab.turnStartedAt = 0;
|
|
1225
|
+
endTurn(tab);
|
|
1226
|
+
setTurnStatus(tab);
|
|
1227
|
+
update();
|
|
1228
|
+
// Await the abort so the stuck message is closed server-side before we
|
|
1229
|
+
// start a fresh turn — prevents a late `session.idle` from the abort
|
|
1230
|
+
// racing into the new turn and flipping `tab.running` back to false.
|
|
1231
|
+
if (tab.sessionId) {
|
|
1232
|
+
try { await requestWs('message:abort', { sessionId: tab.sessionId }, 5000); } catch {}
|
|
1233
|
+
}
|
|
1234
|
+
try {
|
|
1235
|
+
await sendMessage(syntheticMessage);
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
console.error('[ocp-tabs] question stall recovery sendMessage failed:', err);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1153
1241
|
export function applySavedPlan(tab, content, filePath = '') {
|
|
1154
1242
|
if (!tab || !content) return;
|
|
1155
1243
|
tab.planContent = content;
|
|
@@ -1195,6 +1283,7 @@ export async function sendMessage(content, options = {}) {
|
|
|
1195
1283
|
tab._exitPlanDetected = false;
|
|
1196
1284
|
tab._pendingPostPlanCheck = false;
|
|
1197
1285
|
clearPlanFinalizationWatchdog(tab);
|
|
1286
|
+
clearQuestionReplyWatchdog(tab);
|
|
1198
1287
|
|
|
1199
1288
|
const turnStartedAt = Date.now();
|
|
1200
1289
|
tab.running = true;
|
|
@@ -1362,6 +1451,7 @@ export async function sendMessage(content, options = {}) {
|
|
|
1362
1451
|
endTurn(tab);
|
|
1363
1452
|
setTurnStatus(tab);
|
|
1364
1453
|
notifyOpenCodeOutcome(NOTIF_TYPE.ERROR, tab, tab.sessionId || tab.id);
|
|
1454
|
+
_activeChildSessionId = null;
|
|
1365
1455
|
clearPlanFinalizationWatchdog(tab);
|
|
1366
1456
|
// Recover plan content if streaming completed before the WS rejection,
|
|
1367
1457
|
// so a 4xx/5xx after a successful plan stream doesn't strand the user.
|
|
@@ -1406,6 +1496,7 @@ function abortTab(tab, container = null) {
|
|
|
1406
1496
|
endTurn(tab);
|
|
1407
1497
|
setTurnStatus(tab);
|
|
1408
1498
|
clearPlanFinalizationWatchdog(tab);
|
|
1499
|
+
clearQuestionReplyWatchdog(tab);
|
|
1409
1500
|
return true;
|
|
1410
1501
|
}
|
|
1411
1502
|
|
|
@@ -1417,6 +1508,7 @@ export async function abortAllTabs() {
|
|
|
1417
1508
|
stoppedAny = abortTab(tab, tab === active ? activeContainer : null) || stoppedAny;
|
|
1418
1509
|
}
|
|
1419
1510
|
_activeSendRequestId = null;
|
|
1511
|
+
_activeChildSessionId = null;
|
|
1420
1512
|
if (stoppedAny) update();
|
|
1421
1513
|
return stoppedAny;
|
|
1422
1514
|
}
|
|
@@ -1572,6 +1664,7 @@ export function recordToolResult(tab, rawName, toolId, toolInput, result, isErro
|
|
|
1572
1664
|
step.updatedAt = now;
|
|
1573
1665
|
}
|
|
1574
1666
|
}
|
|
1667
|
+
_activeChildSessionId = null;
|
|
1575
1668
|
return;
|
|
1576
1669
|
}
|
|
1577
1670
|
const entry = {
|
|
@@ -1594,6 +1687,7 @@ export function recordToolResult(tab, rawName, toolId, toolInput, result, isErro
|
|
|
1594
1687
|
};
|
|
1595
1688
|
tab.toolActivity.push(entry);
|
|
1596
1689
|
if (childSid) linkChildSession(tab, entry, childSid);
|
|
1690
|
+
if (isError) _activeChildSessionId = null;
|
|
1597
1691
|
}
|
|
1598
1692
|
|
|
1599
1693
|
export function recordAgentChildEvent(tab, entry, eventType, event) {
|
|
@@ -1776,6 +1870,7 @@ export function markStuckActivityAsAborted(tab) {
|
|
|
1776
1870
|
}
|
|
1777
1871
|
}
|
|
1778
1872
|
}
|
|
1873
|
+
_activeChildSessionId = null;
|
|
1779
1874
|
}
|
|
1780
1875
|
|
|
1781
1876
|
export function setActivityVisible(tab, visible) {
|
|
@@ -1811,6 +1906,7 @@ export function abortAgent(tab, key) {
|
|
|
1811
1906
|
step.updatedAt = now;
|
|
1812
1907
|
}
|
|
1813
1908
|
}
|
|
1909
|
+
_activeChildSessionId = null;
|
|
1814
1910
|
update();
|
|
1815
1911
|
return true;
|
|
1816
1912
|
}
|
|
@@ -1896,6 +1992,9 @@ export function handleSSEEvent(eventType, event) {
|
|
|
1896
1992
|
// distinguished from a truly dead session (sendMessage uses this to suppress
|
|
1897
1993
|
// the red error toast when SSE is still streaming).
|
|
1898
1994
|
tab._lastEventAt = Date.now();
|
|
1995
|
+
// Any SSE event after a question reply means the LLM resumed — clear the
|
|
1996
|
+
// post-reply watchdog so it doesn't fire spurious recovery.
|
|
1997
|
+
clearQuestionReplyWatchdog(tab);
|
|
1899
1998
|
|
|
1900
1999
|
const isActiveTab = tab === activeTab();
|
|
1901
2000
|
const container = isActiveTab ? panelEl('#ocp-messages') : null;
|
|
@@ -2448,6 +2547,7 @@ export function handleSSEEvent(eventType, event) {
|
|
|
2448
2547
|
endTurn(tab);
|
|
2449
2548
|
setTurnStatus(tab, 'Error', msg);
|
|
2450
2549
|
notifyOpenCodeOutcome(NOTIF_TYPE.ERROR, tab, event.id || event.messageID || tab.sessionId || tab.id);
|
|
2550
|
+
_activeChildSessionId = null;
|
|
2451
2551
|
clearPlanFinalizationWatchdog(tab);
|
|
2452
2552
|
// Don't strand a half-completed plan: if plan content was streamed before
|
|
2453
2553
|
// the error, surface the post-plan card so the user can still continue,
|
|
@@ -2486,6 +2586,13 @@ function renderOpencodeQuestion(tab, container, event, requestID) {
|
|
|
2486
2586
|
startedAt: Date.now(),
|
|
2487
2587
|
waiting: true,
|
|
2488
2588
|
});
|
|
2589
|
+
// Arm the no-resume watchdog. Auto-recovers from the
|
|
2590
|
+
// kimi-for-coding/k2p6 + OpenCode 1.14.30 deadlock where the POST
|
|
2591
|
+
// is accepted but session.processor never resumes the LLM stream.
|
|
2592
|
+
if (!resp?.status || resp.status < 400) {
|
|
2593
|
+
const synthetic = buildAnswerSyntheticMessage(questions, answers);
|
|
2594
|
+
if (synthetic) scheduleQuestionReplyWatchdog(tab, synthetic);
|
|
2595
|
+
}
|
|
2489
2596
|
processQuestionQueue(tab, container);
|
|
2490
2597
|
update();
|
|
2491
2598
|
}
|