The session indexer scans ~/.claude/projects recursively via
findFilesRecursivelyCreatedAfter, which descends into per-session
subagents/ directories. Claude writes subagent transcripts at:
~/.claude/projects/<encoded-cwd>/<session-id>/subagents/agent-<id>.jsonl
These files repeat the parent session's sessionId. When indexed as
standalone sessions they upsert over the parent row and overwrite its
jsonl_path with the subagent path, corrupting the main session record
(the sidebar then points at, and renders, the subagent transcript).
Add a single isSubagentTranscript() guard (path segment named
"subagents") and apply it in both the recursive scan and the
single-file watcher path.
Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
- Rename Browser Use surfaces to Browser
- Register Browser MCP under the new server name
- Mark CloudCLI-managed MCP servers read-only
- Adjust MCP stdio framing and sidebar footer sizing
The sidebar had to understand cursorSessions, codexSessions,
and other provider buckets because /api/projects exposed
provider-shaped arrays.
That leaked backend adapter storage into project state and made
frontend behavior drift each time a provider needed another bucket
or exception.
Return one sessions list with provider metadata instead. Project
state, search, and running-session filtering now share one contract,
while provider-specific storage remains behind the backend boundary.
The sidebar could keep a provider-native id after backend remapping.
That left a duplicate non-working session visible until refresh.
Fresh sessions could also appear hours old.
SQLite CURRENT_TIMESTAMP is UTC without a timezone suffix.
Browser parsing then treated those values like local time.
Broadcast a canonical session_upserted event when the provider id is mapped.
Collapse provider-id aliases onto the stable app session id in the client.
Normalize session-row timestamps to ISO UTC when reading from the repository.
Add a running-session view to the sidebar, including header controls, running counts, empty states, and row-level processing indicators so active provider work is visible outside the current chat.
Hydrate running state after refresh through a status-only /api/providers/sessions/running endpoint backed by chatRunRegistry.listRunningRuns, then sync and poll the frontend processingSessions map from AppContent without attaching to chat streams or replaying messages.
Preserve fresh local processing entries during sync so newly sent messages are not cleared before the backend registry catches up, and clear completed sessions once the status endpoint no longer reports them.
Thread active session state through sidebar project/session components, show rotating loaders for processing sessions, and keep the running search mode expanded and filterable.
Fix optimistic local user-message dedupe so repeated prompts are only collapsed when a matching server echo appears from the same send window, preventing sent messages from disappearing until assistant completion.
Add registry test coverage for listing currently running app sessions.
Tests: npx eslint on changed files; npx tsc --noEmit -p tsconfig.json; npx tsc --noEmit -p server/tsconfig.json; npx tsx --tsconfig server/tsconfig.json --test server/modules/websocket/tests/chat-run-registry.test.ts.
The frontend previously juggled placeholder IDs, provider-native IDs, and session_created handoffs, which caused race conditions and provider-specific branching. This introduces app-allocated session IDs, a chat run registry with event replay, delta sidebar updates, and one kind-based websocket contract so the UI can treat every provider the same while JSONL remains the source of truth.
Replace the chat processing banner with a minimal activity indicator and
rebuild the state model underneath it. The old banner was driven by five
overlapping pieces of state (isLoading, canAbortSession, claudeStatus in the
chat, plus two app-level Sets updated in lockstep through four callbacks)
that had to be kept in sync imperatively. Because completion and status
events mutated the *viewed* session's flags regardless of which session they
belonged to, a background session finishing could hide the indicator for a
still-running session, returning to a finished session could briefly show a
stale banner, and a late status reply could override a newer request.
The fix is structural rather than patch-by-patch: a single
Map<sessionId, {statusText, canInterrupt, startedAt}> in useSessionProtection
is now the only source of truth for "this session is working". The indicator,
stop button, composer streaming state, and session protection are all derived
from the viewed session's entry on render, so there is no stale local copy to
restore or reset when switching sessions. A PENDING_SESSION_ID sentinel
covers the window before a new conversation receives its real session id.
Terminal events delete the entry atomically, which is why the indicator
disappears the instant the final chunk arrives. Stale check-session-status
replies are discarded via an ifStartedBefore guard (an idle reply older than
the entry's startedAt describes a previous request, not the current one).
The second half unifies the provider lifecycle contract, because the frontend
could not be made race-free while each provider terminated differently:
- cursor emitted complete twice per run (result line + process close), which
double-played the completion sound and let a late close-complete clear a
newer request's indicator
- aborts produced two completes (the abort-session reply plus the provider's
own non-aborted one), so cancelling a run played the celebration sound
- codex omitted exitCode; others attached ad-hoc fields (resultText, isError,
isNewSession) the client had to know about
- claude/codex failures ended with only an error event while gemini/cursor
also emit kind:'error' for mid-run stderr noise, so 'error' was ambiguous
between "the run died" and "a process wrote to stderr"
Every run now ends with exactly one complete built by createCompleteMessage()
({sessionId, actualSessionId, exitCode, success, aborted}); abort-session
sends it on behalf of cancelled runs and providers detect the abort and skip
their own. error is demoted to an informational row, so stderr noise no
longer kills the indicator mid-run, and the client celebrates only
success: true completes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Users can miss chat completions while the app is in the background.
They can also miss completions when their attention is elsewhere.
Add opt-out sound notifications and a temporary title marker.
This makes completion noticeable without external audio assets or persistent browser notifications.
Claude's model catalog changes quickly enough that a shared three-day cache can
leave users selecting stale defaults or missing newly available model aliases.
Route Claude model lookups through the provider every time so the UI and slash
commands reflect the current provider result instead of an old disk snapshot.
Keep the static fallback catalog aligned with the latest Claude defaults so the
provider still has a sensible response when live discovery is unavailable.
The WebSocket gateway never sent ping frames, so any reverse proxy with
an idle timeout (Cloudflare Tunnel ~100s, AWS ALB 60s, nginx 60s, etc.)
would silently tear down /shell, /ws and /plugin-ws/* connections after
the idle window. The UI reconnects automatically but users see a
"Connecting to shell" toast every 1–3 minutes during normal use and any
in-flight PTY/chat traffic can race the reconnect.
Schedule a 30s ws.ping() per connection at the gateway level, cleared on
close/error. ping/pong counts as protocol activity for all proxies that
implement WebSocket correctly, so this single change covers every
deployment topology without per-proxy tuning.
Fixes#769
Co-authored-by: Haile <118998054+blackmammoth@users.noreply.github.com>
* fix: preserve WebSocket frame type in plugin proxy
The plugin WebSocket proxy relays all messages as binary frames
regardless of the original frame type. This causes text-based ready
messages to be forwarded as binary, so the browser never processes
them and plugin UIs (like web-terminal) show a spinner indefinitely.
Pass the isBinary flag through in both relay directions so the
original frame type is preserved.
FixesCoderLuii/HolyClaude#11
* fix(plugins): preserve websocket frame type in proxy
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix: refresh Claude auth status after login flow
* fix: rely on refreshed auth status after login
---------
Co-authored-by: HolyCode User <noreply@holycode.local>
* fix: remove the hide cursor on windows logic
* feat(cursor): update fallback models
* fix(claude): force fallback models and disable supportedModels lookup