This release includes breaking changes for platform teams planning a safe upgrade.
Published 11d
Developer Productivity
✓ No known CVEs patched
✓ No known CVEs patched in this version
Topics
agents
claude
claude-code
web
developer-tools
headless
+4 more
kanban
llm-tools
local-first
python
Summary
AI summaryBroad release touches sid, full-screen, purple, and orange.
Full changelog
Added
- Annotation screenshots forwarded through
/api/annotations/ux-fixes-queuenow render inline in the conv view instead of appearing as a bare absolute path. Two changes: (1)/api/pasted-imagesandbox extended to also allow files under~/.claude/command-center/annotation-screenshots/, and (2)linkifyPastedImagesgained anANNOTATION_IMG_REregex that rewrites any matching path into an<img class="msg-image">pointing back at the same endpoint. Keeps the lazy-load and cache headers the existing pasted-image path already had. - "Add to UX fixes queue" in the annotation editor now fires an immediate "Sending annotation to UX fixes queue…" toast at click time, so you don't have to wait for the network round-trip to see that something happened. The existing success/error toast still follows once the request lands.
- Implement Escape/interrupt capability for Codex sessions. Clicking the Esc button in the composer bar or pressing the physical Escape key on the keyboard sends
SIGINT(Ctrl+C) to terminate the running Codex process (which does not natively handle Escape keystrokes), supporting both headless spawns and terminal window runs. - Fixed a liveness detection bug where spawned Codex and Gemini sessions were not correctly identified as live by the backend, which hid the "ESC" button in the dashboard's input composer.
- Added a Codex steer action in the composer. Normal Send queues behind a running Codex turn; the steer button immediately pushes input into that active turn through Codex app-server
turn/steerwhen supported. - CCC now snapshots a session's JSONL to
~/.claude/command-center/compact-backups/<sid>-<timestamp>.jsonlwhenever a/compactcommand is detected in the inject path — before Claude Code rewrites the on-disk transcript. Claude Code's/compactdeletes the original message history (replaces with the compacted summary), and users only discover this after the fact when scrolling back finds the pre-compact messages gone. Keeps the 10 most-recent backups per session; older ones rotate out. The in-progress banner now mentions the backup location so users know recovery is available. - /compact now has clear in-progress and result UI. (1) When the user sends
/compact, a sticky banner appears in the conversation view — "Compacting conversation context… Claude is summarizing the prior turns. This usually takes 1-3 minutes." with a spinner, so the user knows the request is still running during the 1-3 minute wait. (2) When the compact-resume block lands (the long "This session is being continued from a previous conversation…" user_text), it's now wrapped in a collapsible card with a click-to-expand toggle. Intro line is always visible; full body (numbered sections) is hidden by default and scrollable when expanded, so the active conversation isn't drowned by it. - Conversation pane header now shows the JSONL transcript size next to the title (e.g.
5.2 MB). Helps correlate slow first-paint with sheer conversation size — large sessions take noticeably longer to parse and render, and the badge makes that visible. Pulled directly from the row'ssizefield; cleared when the row has no known size (issue/PR rows, new-session mode). The conv list rows already show size in their inline meta strip; this surfaces the same data in the place the user is staring at while the conversation loads. - Conversation view now follows new content automatically when you were already at the bottom — any path that adds content (SSE events, streaming bubble, re-render, etc.) re-scrolls to the new bottom. If you scroll up to read earlier text, the auto-scroll stops; scrolling back to the bottom re-enables it. The clickable "End" button still works as the manual jump. Implemented via a per-view MutationObserver tied to a
_pinnedToBottomflag that scroll events keep in sync. - Added Cursor IDE agent view visibility integration. Spawned and resumed Cursor CLI sessions now automatically appear in the Cursor IDE agent view by writing workspace metadata databases directly under
~/.cursor/chats/<project_md5>/<session_id>/store.db, with a background backfill scan at server startup to ensure existing recent sessions are also registered. - Added Cursor as a dashboard engine alongside Claude, Codex, and Antigravity. CCC can discover Cursor agent transcripts, spawn headless
cursor-agentruns, resume existing Cursor chats, show Cursor rows/logs in the UI, and manage Cursor spawn defaults. - Added
scripts/cut-release.sh— a single command that cuts a full release (changelog rollup, version bump, tag, GitHub release, notarized DMG + Sparkle appcast, and Homebrew formula bump with auto-computed sha256), with--dry-run,--skip-dmg, and--notes-fileoptions. - Flow board now remembers whether you had it expanded (full-screen) and restores that state on the next page load. Persisted via
localStorage['ccc-flow-expanded']. The toggle (chevron in the flow toolbar) writes the new state on every click; reload picks it up at init time and applies it as soon as the flow view renders. - New "Organize" button in the flow toolbar. Keeps every parent (repo + object) exactly where it is, then lays out each parent's session children in a tidy grid right under it — most recent session leftmost, descending recency rightward and downward. Useful after a drag-and-drop session that left children scattered.
- Flow board gains a per-session pin that's independent of the conv-list pin. Hover any session card in flow view and a 📌 button appears next to the archive icon — click to pin that session to the flow board. Pinned sessions stay visible in flow even when archived and "Include archived" is off. Stored client-side at
localStorage['ccc-flow-pinned-sessions']so the pin survives reloads but doesn't leak into the regular conversation list. - Flow session cards now carry a compact chip strip mirroring the regular conv list: a single lifecycle chip (uncommitted | PR # / state | pushed | committed | no edits — priority chain identical to the list), a live activity chip when the agent is mid-tool (yellow pulsing "WIP" or the tool name) or paused waiting on input (cyan "WAITING"), and a numeric "N commits" chip when the server exposes a commit count. Also bumps inter-session spacing inside a cluster (CHILD_GAP_X 14→18, CHILD_GAP_Y 10→16, SESSION_GAP_BELOW_PARENT 14→18) so chips have room to breathe.
- Flow toolbar now has its own "Annotate" button. The global topbar (where Annotate normally lives) is hidden in expanded flow view, so previously you had to collapse the flow board just to grab a note. The new button delegates to the existing
#annotationStartBtnclick handler — same behavior, just reachable from the flow toolbar. - Group chat input now autocompletes
@mentions against the chat's participants. Type@and a floating menu shows matching participant names (filter by display-name or 8-char short-id, max 8 results). Arrow keys to navigate, Enter / Tab to commit, click to pick, Esc to dismiss. Inserts@<full-name>so the nudge mention-scan picks it up cleanly. Participant list comes from the reader's livename_mapso newly-joined sessions appear without a reload. - Group-chat messages now align chat-style: each participant stays on its own side of the available width so the conversation reads like a thread, not a flat log. Human always lands on the right (iMessage convention for "me", green accent). Agents cycle through three stable slots — left (accent), center (purple), right-agent (orange) — based on first-seen order so each agent keeps its own column and color throughout the chat. System messages stretch full-width so the lifecycle log isn't squeezed into one side. Per-side header tinting makes each participant's column distinguishable at a glance.
- Sessions spawned by agents via
/api/sessions/spawn*(Codex, Gemini, Cursor, Antigravity, etc.) now appear in the conv list within seconds — no waiting for a manual refresh./api/session-statusreturnsspawn_registry_count(running count of CCC-spawned sessions); the client's 5srefreshLiveStatuspoll compares it against the last seen value, and any growth triggers an immediaterefreshConversationListplus tighter follow-up polls at 1.2s / 3s to catch sessions whose JSONL materializes a moment later. - Added
/lean-commitslash command,scripts/lean-commit.sh, andCLAUDE.md/AGENTS.mdguidance for Tier-A path-only commits on sharedmain(parallel sessions). - Orchestrator panel now surfaces the last-nudge timestamp inline on the "Auto-nudge: checks every Xs, nudges ≤ every Ys" row (e.g. "· last: 2m ago") plus the existing dedicated "Last nudge:" row gets an absolute-time tooltip on hover. Server: bumped
last_nudge_atresolution to takemax(in_memory last_nudge, persisted last_reminder_at)so the timestamp stays accurate across server restarts AND when the targeted per-participant Nudge button fires (which only writes to the sidecar, not to the in-memory watcher entry). The per-participant Nudge now also bumps both stores so it shows up in the panel immediately. - Per-participant Nudge button in the orchestrator panel. Each participant card under "Participants" now has a small Nudge action that re-injects the /group-chat prompt into just that one agent's session — wakes a specific participant up without nudging the whole group. Useful when one agent has gone quiet (e.g. "spoken 3h ago, last mentioned 2m ago"). Powered by extending
_group_chat_nudge(path, chat_uuid, target_sid="")with atarget_sidparameter that bypasses the auto-select-last-writer logic and pings exactly that sid; the/api/group-chat/nudgeroute now acceptstarget_sidin the payload. - Pasting an image into any composer now shows an instant thumbnail preview above the input — using a browser-side blob URL so the preview appears the moment you paste, before the upload finishes. Once the server returns the canonical path, the pending thumbnail swaps to a final version with a hover "×" remove button that strips the path token from the input value (so the send doesn't carry an orphan reference). Wired across the standard conv input, the split-pane input, the new-session modal, and the group-chat reader input. Thumbnails clear automatically when the input goes empty post-send.
- Added real-time subscription plan limits and usage popover (5-hour limit, weekly models limit, sonnet only limit, and extra usage USD credits) retrieved dynamically from Anthropic, displayed when clicking the context usage pill at the bottom of the composer.
- Answer AskUserQuestion from the dashboard. Headless sessions used to auto-decline questions instantly; now a blocking modal surfaces the question (and its options) in the UI and feeds your pick back to the agent so it continues with your answer.
- Conversation list: each repo folder header now shows the repo's git status and a Push all control that orchestrates a repo-wide ship for the main checkout, with a live terminal-style log feed in the "Needs your attention" panel. The flow: auto-restores dirt whose content is already preserved in git (hand-copied PR work / behind-origin files — provably non-destructive), flags junk, nudges the sessions that own remaining changes and reads their replies back (two-way), then safely integrates by fetch + fast-forward only — refusing to rebase a shared clone in place when the branch has diverged. It never auto-commits unattributed work (there's a push-to-prod behind it); instead it surfaces the remainder split into "safe to bulk-commit (docs/infra)" vs "review (app/deploy code)" vs "junk", each offered as an Approve / Reject action (approve commits the infra group or deletes the junk; review stays manual). The log feed persists across page refresh and restart (click the status chip to reopen it), sessions are shown by name, and repeated runs skip sessions that already replied. For Vercel repos it then polls the production deploy to READY.
- CCC now renders right-to-left scripts (Hebrew, Arabic, etc.) correctly throughout the conversation view. Each message block detects its own direction from its first strong directional character via
unicode-bidi: plaintext+text-align: start, so mixed-language conversations don't need any per-message tagging — Hebrew quotes flip RTL while interleaved English text stays LTR. Code blocks and inline<code>are pinned back to LTR so code stays readable. The composer textarea also gotdir="auto"so typed Hebrew/Arabic flows the right way. - Subagent (Task tool) work now lives in its own tab inside the conversation pane instead of mixing into the master agent's stream. New
.conv-tab-stripappears above the conversation view as soon as a subagent starts streaming; "Master" tab is the parent agent's flow, one tab perparent_tool_use_idfor each Task delegate (label taken from the Task description). Tabs auto-close 30s after their subagent completes IF the user is on another tab when the result lands — finished work clears the strip without piling up, but stays visible if you're currently reading it. Manually closable via the × on each task tab. Switching conversations resets all tabs. - What's New carousel now highlights the four headline items shipped today: Subagent Tabs (visualization), Headless Question Relay (orchestration), Antigravity Orphan Resume (engine), and Ship Auto-Reconcile (workflow). Each entry has a description and a styled inline mockup so users opening the carousel on next launch see what's new without leaving the dashboard.
Changed
- "Active Group chat" pill now reflects REAL recency, not just "in the watcher backlog." Previously it showed for the full 45-minute server-side death timeout even when no trigger or file activity had landed in tens of minutes. New behavior: pill hides if no trigger /
last_nudge_at/last_activity/last_mtimehas happened within the last 10 minutes (separateGC_ACTIVE_PILL_FRESHNESS_MSconstant, decoupled from the server's nudge-keep-alive window). Label and tooltip also annotate the recency ("Active Group chat · 3m ago" plus an absolute-time tooltip) so the pill itself answers "still active?" at a glance. - Active group-chat pill now shows what actually triggered the activity, not just "N Active Group chats". Format:
<topic> · <reason> · <age>(e.g.marketing · nudged Alice, Bob · just nowor2 chats — marketing · auto-pinged Alice +3 · 2m ago). Picks the freshest oforchestrator_last_nudge_at/orchestrator_last_trigger_at/last_activityand labels with the matching reason and target names. Targets truncated at 3 to keep the pill readable. Annotation: "i doubt there is really something triggering right now — if so i want to know what it was — put it on the UI even if the pill becomes long." - Clearer error when in-page tab capture isn't available (CCC native app shell). The previous message said only "use Screen Recording for the CCC server instead" — which left users staring at the System Settings without knowing what to click in CCC. New message points directly at the topbar "Screen" button as the working alternative and explains it uses the macOS area screenshot picker (gated by Screen Recording permission for Claude Command Center).
- Throttle all-repos archive refreshes and coalesce session scans so large local histories do not starve conversation loading.
- Active conversation breadcrumb (
claude · claude-command-center · UX fixes queue · 15.0 MB) now lives in the global toolbar at the top row, freeing the slot below the toolbar for the sticky "original ask" panel (the "Fix the following UX issue based on this annotation: …" header). Single-pane mode: the in-pane header is CSS-hidden so the sticky panel rises into its space; the topbar mirrors the active row's category + title + size badge. Split mode: each pane still shows its own in-pane header (so you can tell panes apart) AND the topbar reflects whichever pane is active. - Improved conversation composer typing responsiveness in the Mac app: draft saves to localStorage are debounced instead of running on every keystroke, slash-command handling no longer touches split-pane localStorage unless a
/command is active, draft storage keys are cached per conversation, and live transcript/usage updates are deferred while the composer is focused (flushed on blur). - In Progress list reshuffles repo groups much less aggressively. Bumped
_FOLDER_ORDER_HYSTERESIS_Sfrom 5 min to 60 min: when two repo groups' max-modified timestamps are within an hour of each other, the previous render order is kept regardless of which one most recently received activity. Repos dormant for more than an hour can still be promoted by fresh activity (the user's "OK to bring up from below the fold" exception). Reduces the jitter the user reported when actively cross-working on two repos. - Cmd+F find modal is now purple — temporary visual marker so we can tell the new build is loaded while iterating on the focus-loss fix. Will revert once focus is confirmed solved.
- Dragging the empty flow-board background now pans the canvas (matches Figma/Miro). Hold Alt while dragging the background to fall back to the previous behavior — rectangular area-select. Node drag is unchanged; only the background gesture is rerouted.
- Dragging a repo or object node in the flow board now moves all of its descendants (sessions, drafts, nested objects) along with it by default — the cluster travels as one. Hold Alt while dragging to break that link and move only the parent node, leaving children in place (useful when you want to reparent or reorganize manually).
- Clicking a session card in the flow board now jumps straight to the conversation view: collapses the flow board out of full-screen and closes the conversation sidebar so the chat takes the full viewport. One click does the navigation the user previously had to do in three steps (click session → exit flow → close sidebar). Restoring the sidebar afterwards uses the existing chevron / Cmd+B.
- Flow toolbar is now grouped by topic with vertical dividers between groups (Add | Filter | Layout | Zoom | View) and the Annotate button moved to the rightmost position behind a flex spacer. Adds a new "Include archived" filter toggle that pulls completed/archived sessions back into the flow with a clear marker — translucent + green check badge in the top-right of each archived session card — so finished work is visible at a glance but doesn't compete with active sessions. Toggle state persists across reloads via
localStorage['ccc-flow-include-archived']. - Two group-chat fixes. (1) Cards now flush to the transcript edges —
margin-left: 0for left-side speakers,margin-right: 0for right-side speakers (Human + agents on right slot), no more 6% inset. Max-width down to 72% so adjacent columns separate clearly. Agent assignment simplified to alternate left/right by first-seen order; per-agent distinct color (accent / purple / orange / cyan / red / yellow) lives on a separatedata-speaker-colorattribute so multiple agents on the same side stay distinguishable. (2) Hard-refreshing while reading a group chat now reopens the same chat —openGroupChatReaderstamps a unifiedccc-last-view = {type: 'gc', path, id, topic, mode}marker, and on boot if the last view was a group chat we callopenGroupChatReaderinstead ofrestoreLastConversation. Conv selection writes the same marker withtype: 'conv', so whichever was most recent wins. - Group chat @mentions truncate participant names over 20 chars (e.g.
@Movie i wanted for vid…) instead of dumping the full label inline. The bare prefix in the system note still spells out the full name once, so the @mention pill no longer adds a redundant copy of the same long string. Full name + session id stay in the title tooltip. - Group-chat messages now render as clearly-bounded cards: each
.gc-messagehas a 1px border + 8px radius, the.gc-message-metais a full-width tinted header bar with the speaker name + timestamp, and the body sits below with comfortable padding. Makes the separator between consecutive messages obvious at a glance. System messages get a quieter grey-tinted header so the lifecycle log doesn't compete visually with actual participant posts. - Clicking "+ New Group chat" now prompts for the chat name up front instead of silently creating an "empty chat" you had to click ✏️ to rename afterwards. Cancel aborts (no chat created); pressing Enter with nothing typed falls back to "empty chat" so users who don't want to name it can still ship.
- Group-chat nudge mention-scan is now engine-agnostic: agent posts can also address specific participants ("@Maya let's verify that") and the nudge will wake only those addressees, not everyone. Previously the @mention scan ran only when the Human was the last author; agent messages fell through to the legacy "ping everyone except the writer" behavior even when they explicitly named someone. Now the scan runs on whichever author wrote last (Human or agent), and the writer themselves is always excluded from the nudge set (no self-mentions).
- Two group-chat nudge improvements. (1) When the Human's last message addresses multiple participants — by
@<name>mention or@<8hex>short-id — ALL of them get nudged, not just the most recent prior writer. Previously a "Maya and Jordan, can you both…" message only woke whichever agent wrote immediately before the Human; now it wakes everyone the Human named. Falls back to the prior-writer single target only when no mentions are detected. (2) The nudge injection no longer inlines the last message body (~2KB) — agents re-read the chat file anyway, so that was pure token waste at every nudge. The injection now sends just the heading line ("a new post just landed —## <ts> — <author>") as a pointer; agents read the chat file for content. - install.sh now defaults to yes on the "install as background service?" prompt (was no). Most users hit enter on prompts, and the previous default left them with a foreground server tied to the install Terminal — closing that window killed CCC and produced a frequent "where did CCC go" confusion for DMG users. Users who explicitly want the foreground path can still type
n. Non-interactive runs (no TTY) stay in foreground, unchanged. - DEBUG: globally pauses every
setIntervalcallback while a textarea or text input is focused, so we can confirm whether timer-driven work is the source of remaining typing hitches. SSE,requestAnimationFrame, and one-shotsetTimeouts are not affected. - Organize now treats each parent + its descendants as a single cluster zone — no other parent or session may land inside that rectangle. Replaces per-node overlap avoidance with per-cluster exclusion zones (parent's bounding box expanded with its placed children + a 12px pad). When the next parent would land inside a prior cluster's rectangle, the whole parent is pushed down until it clears the zone; same for any session that would otherwise fall inside someone else's cluster.
- Organize now condenses: any repo/object node sitting outside the currently visible flow viewport gets brought back inside it, while parents already in-frame stay exactly where they are. Out-of-frame parents land in the first empty slot scanned row-by-row inside the viewport (with a session-row reservation below each so out-of-frame parents don't drop on top of an in-frame parent's session grid). After the parent pass the existing session sort-and-grid step runs, so everything ends up tidy under each parent regardless of how scattered the board was.
- Organize now guarantees zero overlaps. Replaces the old fixed-grid pass with a collision-aware placement: parents are processed top-to-bottom, pushed strictly downward until they clear every previously placed rect, then each parent's sessions are placed in a 4-column grid below, with grid cells that would overlap an occupied rect (a sibling parent, its sessions) skipped — sessions wrap to a 2nd, 3rd, Nth row as needed. Falls back to a vertical stack at the bottom of all occupied rects when the grid truly can't fit. If a parent has to move to keep things clean, the whole object moves.
- Organize gains four more rules (R5-R8) and now keeps the full rule set documented in the source above
organizeFlowSessions: (R5) sessions placed below their parent are indented 10px to the right; (R6) connector lines always render BEHIND nodes — archived sessions switched fromopacity: 0.62to muted background/text colors so lines no longer bleed through; (R7) nested objects (an object whose parent is another object/repo) are pinned to the right of their parent (parent.right + 24, parent.top) — never above or to the left, enforced via topological sort; (R8) within a cluster, non-archived sessions come before archived ones; within each group, reverse-chronological order. - Organize rewritten as a deterministic bin-pack: clusters (parent + sessions) are precomputed with an area-minimizing column count (chosen so each cluster's width ≈ its session-area height), then packed top-left to bottom-right in rows with a 20px margin between clusters. Each row wraps to the next when the next cluster would exceed the row budget. Results: (1) idempotent — running Organize twice produces the exact same layout; (2) minimum cluster area for the given session count; (3) consistent 20px margins around every cluster; (4) tight pack with no isolated rectangles or empty space between neighbors.
- Push all now auto-reconciles a diverged branch in an isolated throwaway worktree (cherry-pick local commits onto origin, push, fast-forward the shared clone) and only hands off to a manual reconcile when there's a real conflict; loose root-level scratch files (e.g. snapshot.png) are no longer misclassified as app/deploy review.
- Push all now attributes uncommitted files to their authoring session via the local conversation index (claude-index), showing the owner+age on each handoff action and reducing over-nudging; git remains the safety authority.
- Subagent (Task tool) streaming bubbles now render visually distinct from the parent's bubbles — purple "subagent" label, left-indent + colored left border — so a busy Task-tool run no longer reads as one frantic stream-of-consciousness coming from the parent. Server:
_normalize_spawn_eventpassesparent_tool_use_idthrough to the assistant_block payload (Claude Code stream-json sets it on subagent messages). Client:ensureStreamingBubbleacceptsparentToolUseId, tags the bubble withstream-bubble-subagent+ adata-parent-tool-use-id, swaps the header label from "streaming" to "subagent". CSS variant indents 16px and switches the border/dot tint to purple. - Telemetry opt-in banner: punchier copy that names the 5 fields up front ("version, OS, engines you have") and the explicit-not list ("Zero paths, prompts, or content"). Primary button is now "Send daily ping" instead of "Enable" so the action is obvious. Aims to lift the dismal opt-in conversion (1-of-7 DMG downloads since 2026-05-22).
- TTS button (read-message-aloud) simplified to fixed 1.25x rate with play/pause toggle. Clicks cycle play → pause → play; the rate-cycling (1.0x → 1.5x → stop) is gone. The button auto-resets when a new assistant turn lands, so the next click reads the freshly-arrived message instead of resuming the prior one mid-sentence. Paused state shows a calmer tinted button (so it reads as "click to resume" instead of "still speaking").
Fixed
- Conversation rows now show "now" (or the freshest sidecar timestamp) when the agent is actively running — was showing "11h" for sessions actively mid-tool because the row time was anchored to
last_interacted(the user's last UI action) and not the agent's autonomous activity. The new clock honorssidecar_tswhen_isAgentRunningis true, falling back to literal "now" if no sidecar stamp is present. - Annotate screenshots: captures the annotated DOM region in-browser (no tab prompt required), falls back to tab capture or macOS window capture, and always attempts server capture when pixels are still missing. Saves without a screenshot show a clear warning instead of failing silently.
- When an annotation creates a NEW session (UX-fixes-queue first-spawn path), the conv sidebar now refreshes immediately so the new row appears without waiting for the next poll cycle.
annOpenUxFixesQueuepreviously refreshed only the archive list on success; if the server reportedaction: 'spawned', the just-created session was invisible in the In Progress list until the next refreshConversationList tick (manual click or some other code path). Now it callsrefreshConversationList()immediately + schedules tighter polls at 600/1500/3000ms to catch sessions that materialize just after the first refresh window. - CCC now recognizes
.dbas a valid AGY CLI conversation state file (was checking only.pb). AGY writes a.dbto~/.gemini/antigravity-cli/conversations/<sid>.dbwhen it rebuilds an orphan conversation from the brain transcript on the first--conversation <sid>resume. Without.dbrecognition, sessions that had been resumed once stayed labeled "orphan" in the UI placeholder/tooltip even though their state was healthy on disk. The actual send path was unaffected (both routes through the sameagy --conversation -pcommand), so this is a cosmetic/routing-preference fix — the placeholder now correctly says "Resume Antigravity headlessly and send..." once.dbexists, instead of stuck on the orphan-rehydrate copy forever. - Antigravity spawns no longer create a phantom second session row. The server was pre-writing a placeholder
transcript.jsonlunder~/.gemini/antigravity-cli/brain/<sid>/with our generated session_id before launching the CLI, but Antigravity CLI ignores the--conversation <sid>flag in this path and writes its own transcript with its own UUID — so both files showed up as separate conversations (the placeholder always with just the prompt and no replies). Drop the pre-write; let AGY CLI be the single source of truth for the transcript. - Antigravity sessions now accept typed follow-up questions even when Antigravity isn't currently running. Previously the input bar was set to
readOnlyand the send button disabled whenever neithercan_headless_resumenorcan_app_resumewas true — leaving the user with a dead input bar and no way to ask a follow-up without first opening Antigravity manually. The server'sresume_session_antigravity()already falls back to opening the Antigravity app with the prompt when CLI-headless and app-resume aren't available, so the client restriction was over-cautious. Send button now reads "Send — opens Antigravity with this prompt" in that state; placeholder reads "Type a follow-up — Antigravity will open with this prompt…". - Orphaned Antigravity sessions (transcript on disk, no CLI
.pb, no app conversation file) can now be continued.resume_session_antigravitypreviously short-circuited to the broken app-resume path whenever the CLI.pbwas missing, which then errored withantigravity_app_conversation_missing— the session was effectively read-only. The CLI print-mode code path (agy --conversation <sid> -p <text>) already existed for live CLI sessions; it now runs for orphans too, letting AGY rehydrate from the brain transcript and append the new turn headless (same mental model as Claude resume). App-resume is still preferred when the app conversation file exists. Input bar copy updated to "Antigravity will resume headless" / "Send — runs AGY headless on this session" (was "Antigravity will open with this prompt"). - Antigravity orphan sessions (no CLI .pb, no app conversation file) now show feedback when you hit send. Two send handlers (the main conv input bar and the split-pane composer) were early-returning with
$input.blur()wheneverantigravityCanSend(session)was false — which for orphans meant pressing Enter did literally nothing (no optimistic echo, no toast, no input clear). Drop the gates now that the server has a graceful headless-fallback path. The flag still drives placeholder/tooltip copy ("Resume Antigravity headlessly (orphan rehydrate)..."), it just no longer blocks the send pipeline. - Antigravity sessions spawned by agents via
/api/sessions/spawn-antigravitynow appear in the conv list within seconds, matching the Codex/Gemini behavior. Previously the spawn-registry-count ping triggered a conv-list refresh, but the refresh found nothing because AGY's brain dir +transcript.jsonltake several seconds to materialize on disk.find_antigravity_conversationsnow synthesizes a stub row (display_name from spawn prompt, markedpending_spawn) for any live AGY spawn whose JSONL hasn't landed yet — the real row replaces it on the next scan once the transcript exists. Works for both client-initiated spawns and sibling agent spawns. - CRITICAL: AskUserQuestion no longer auto-continues silently. The resume-queue watcher and the direct inject path now BOTH check
_pending_ask_user_question_for_session(sid)before flushing any queued text into a live session — if a question is in flight, the input waits until the user actually answers in the UI. Previously the watcher only checked_session_status_is_busy()(status=busy/running), which returned False even whilesidecar_tool == "AskUserQuestion"was pending; injecting queued text in that window made Claude Code synthesize a tool_result for the open question (treating the queued text as the user's "answer") and continue the conversation silently past the prompt. - Fixed Cmd+F find bar losing the typing caret in the Mac app. In-conversation search no longer uses
window.find()(which moves focus into the transcript in WebKit); matches are highlighted with the CSS Highlight API and scrolled into view while the caret stays in the find field. - Fixed live indicator missing on codex/gemini/cursor session rows in the list. The bulk session list only checked Claude Code sidecar markers for liveness, so non-Claude engine sessions never showed the live glow even while actively working (the right-hand pane lit up but the row stayed dark). The list now also recognizes a session as live when its engine CLI is running it, via a single cached process scan shared across all rows.
- Flag stale Codex tool calls as stuck and add a one-click wake action.
- Fixed Codex steer/send replies disappearing from the conversation pane when the composer stayed focused. Stream batches were deferred during typing but the JSONL cursor still advanced, so steered answers never rendered.
- The
/api/sessions/live-activitypoll no longer pins a CPU core re-parsing whole transcripts._extract_codex_tail_metais mtime-cached, but a live session appends to its rollout constantly, so the cache always missed and the entire (often multi-MB) JSONL was re-read andjson.loads-ed on every poll — for every live session, on every concurrent poll. A real 13 MB live rollout was being fully parsed several times a second. The parser now resumes from a saved byte offset (rollout JSONL is append-only) and reads only the lines appended since the last poll, folding them into the carried-forward parse state; truncation/rotation falls back to a full reparse, and a partially-written trailing line is left for the next poll. On a 12 MB rollout this drops a poll from ~131 ms to ~0.03 ms (~4000×). Resume state is in-memory only, so a restart costs one full parse per file and then rebuilds. (Gemini/Cursor/Antigravity tail extractors share the same pattern and remain a follow-up.) - Context-pct badge no longer shows the pre-
/compactpercentage after a compact runs. Root cause:_extract_tail_metacapturedlive_context_*from/contextslash command outputs and held them across the rest of the JSONL — even when a subsequentcompact_boundaryevent rewrote the JSONL much smaller. The client preferredlive_context_percentover the freshly-computedlatest_input_tokensratio, so the badge stayed pinned at the pre-compact value (e.g. 91%) when actual usage had dropped (e.g. 42%). Fix: zero outlive_context_*oncompact_boundary. Next/contextinvocation repopulates them with real post-compact numbers; until then, the badge falls back to the calc-from-tokens path which already reflects the compact. Bumped_CONV_META_SCHEMA_VERSIONto 13 so persisted entries with stale live_context get re-extracted on next boot. - The context-pct badge in the sidebar (e.g. "91%") no longer stays stale after a fresh assistant turn lands in the JSONL.
_extract_tail_metawas keyed onpath.stat().st_mtime— second resolution. Two writes inside the same wall second (common: a tool result plus the next assistant turn within ~100ms) collapsed to the same mtime and the cache kept returning the prior snapshot, solatest_input_tokens/live_context_tokens/live_context_percentstayed pinned on the old value until the next-second write evicted the entry. Cache key is now(st_mtime_ns, st_size)(same shape_conv_parse_jsonl_mtimealready uses for the right-pane parser). Bumped_CONV_META_SCHEMA_VERSIONto 12 so old persisted entries (mtime-only keyed) get re-extracted on first hit instead of perpetually missing the new check. - Fixed missing WIP and in-progress tool chips (e.g. Bash command) on conversation list rows while a session is active. Live sidecar data was dropped when the sidebar re-rendered from cached archive data, and HTTP 304 archive refreshes never re-applied live fields — the list now polls
/api/sessions/live-activityevery 5s and merges that overlay into each row render. - Conversation row time chip now reflects ALL activity on a session: user UI actions (
last_interacted), CCC injects (touched optimistically viamarkSessionSending), AND agent JSONL writes (modified= mtime). Previously the chip readlast_interacted || modified— a short-circuit OR that returned the user-action time even when the agent had been actively writing events seconds ago. Now we takemax(last_interacted, modified)so the freshest signal wins. The "now" override when_isAgentRunningstill applies on top. - Sidebar conversation search (
#convSearch) is responsive again. The RTL belt-and-suspenders MutationObserver was attached todocument.bodywithsubtree: true, so every sidebar re-render on each keystroke fired thousands of mutation records that the observer walked synchronously — starving the input thread and reading as "search stopped working". Re-scoped the observer to the conversation view containers (#conversationsView,#p1ConvView,#p2ConvView,#p3ConvView) where message HTML actually lives; sidebar mutations no longer trigger any RTL work. - Sidebar conversation search no longer "stops working" until you click a conv-item. Root cause: the
_sidebarDragInProgressboolean got stucktruewhenever a dragstart fired but dragend/drop never did (mid-drag Escape, browser-cancelled drag, removed source element). With the flag stuck, every renderSidebar call was deferred — typed characters showed in the input but the list never filtered. Clicking an item happened to call a code path that cleared the flag, making search "work again" by coincidence.isSidebarDragInProgressnow self-heals: if the boolean says we're dragging but no DOM element actually carries.dragging, the flag is cleared and renders resume. - Conversations view no longer shows a spurious horizontal scrollbar at the bottom of the pane. Added
overflow-x: hidden+min-width: 0to.conversations-viewso long URLs / unbreakable tokens in a message wrap instead of pushing the pane wider than its container. Code blocks have their own inner horizontal scroll, so the clip doesn't lose any content. - Cursor / Codex / Gemini / Antigravity rows no longer falsely light up with a Claude WIP "Shell" badge after a Claude Code hook pollutes their sidecar slot. Pattern observed: a Claude session ran
git commit --trailer "Co-authored-by: Cursor <[email protected]>"and the hook wrote~/.claude/command-center/live-state/<cursor_sid>.jsonwithtool:"Shell", status:"active"— keyed under the Cursor session id, not the Claude one. From then on_archive_session_is_live(cursor_sid)returned True (sidecar exists), the cursor row gotis_live: true, and the WIP path lit up a stale "Shell" badge that survived forever (sidecar files persist until manually deleted). Defense:_archive_session_is_livenow detects the engine first and skips the Claude-sidecar check for non-Claude sessions — their liveness comes only from the live-process scan. Root cause (which Claude hook wrote the cross-engine sidecar) is a separate hunt; this is the defensive layer. - Fixed Cursor follow-ups that immediately fail (for example usage-limit errors) so they surface as send failures instead of leaving the conversation pane stuck on an optimistic "sending" echo. Non-live Cursor rows also stop carrying stale pending-tool state from the last transcript event.
- Live-activity polls and group-chat opens no longer trigger a full scan of the Gemini chat store per session.
_detect_session_enginefalls through to_is_gemini_session, which JSON-parses every Gemini chat file on disk to look for a matchingsessionId— so for a Claude session (no match) it parses the whole store. With ~14 live sessions that was ~2.3s per/api/sessions/live-activitypoll, and it also ran per-participant on every group-chat open (the "opening a group chat is slow" symptom). A session's engine is immutable, so the result is now memoised per session: a non-"claude" engine is definitive and cached forever; "claude" is cached with a 30s TTL so a just-spawned non-Claude session whose store appears a beat later is still re-checked. Engine detection drops from ~153ms (14 sessions) to ~0ms once warm; the full live-activity build drops ~206ms → ~62ms per poll. - Cmd+F find modal really keeps focus on the input now — the prior synchronous
.focus()restore beat WebKit's async focus move to the matched element. Defer the restore via microtask + requestAnimationFrame so we land AFTER WebKit's reflow-time focus shift instead of before it. - Cmd+F find input now genuinely keeps focus while typing. The triple deferred restore (microtask + rAF + 60ms) still lost to WebKit's deferred focus shift in some cases. Added a 300ms
focusinguard window after everydoFind: any focus move off the input while the find modal is open snaps right back. Belt-and-suspenders with the existing restore chain. - Add a
setTimeout(60)fallback to the Cmd+F find input's focus-restore chain — microtask + requestAnimationFrame from8a85660weren't late enough in WebKit; focus shifted to the matched element on a later commit-phase tick. Triple-restore (microtask, rAF, 60ms) catches whichever frame the focus move lands on. Each restore no-ops if focus is already on the input. - Clicking a session from the flow board now collapses the RIGHT status rail (the panel with the original ask, session activity, and files container) — not the left conv-list sidebar (which the prior two attempts incorrectly targeted via
setConvPanelOpenand thensidebar-tucked). Reverts the sidebar-tucked plumbing (floating button + body class + index.html button), addsbody.status-rail-collapsed+localStorage['ccc-status-rail-collapsed']writes to the single-click handler. The status rail's existing chevron restores it. - Clicking a session from the flow board now actually hides the LEFT sidebar (the conv list / flow board) instead of the chat panel — the prior fix targeted
$convPanelwhich is the kanban-split chat. Added a body classsidebar-tuckedthat hides.sidebar+#sidebarResizer, paired with a floating "☰ Sidebar" restore button in the top-left and Esc-to-restore. State persists across reloads vialocalStorage['ccc-sidebar-tucked']. - Group-chat files now self-normalize trailing blank lines. Agents writing to the chat via the Edit tool routinely leave dozens of trailing blank lines at the end of a post (worst observed: 230+ blank lines under a 9-line body). The reader UI hid them but every other agent re-reading the file paid tokens for each blank — wasted context. Added
_group_chat_normalize_whitespace(real_path)that walks the file, strips trailing blanks per post, and guarantees exactly one blank line between consecutive##headers. Called from_group_chat_post(after each Human entry) and_group_chat_read(on every reader poll) so the file stays lean across multiple agent posts in a row. Idempotent — no-op when the file is already clean. - Group chat pill no longer claims "new message · just now" minutes after the actual last message. The pill's freshness calc was using
Math.max(chat.last_activity, chat.last_mtime), butlast_mtimeis the chat file's stat mtime — the server bumps it on every metadata write (name_map updates, sidecar refreshes, etc.) without any real message arriving. So the file's mtime stayed pinned to "just now" while the actual last conversation event was minutes/hours old. Fix: droplast_mtimefrom the reason/age calc; onlylast_activitycounts as a real message signal.gcShouldShowActivePillstill consultslast_mtimefor the "should the pill exist at all" check (so the pill stays visible during background writes) — only the displayed reason and age tighten up. - Conversation history search no longer crashes under concurrent requests. The server caches one read-only sqlite3 connection for the whole process, but runs behind
ThreadingHTTPServer(a thread per request). A single sqlite3 connection can't be used from multiple threads at once —check_same_thread=Falseonly silences Python's guard, it doesn't serialise access — so overlapping searches raisedsqlite3.InterfaceError: bad parameter or other API misuse(SQLITE_MISUSE), surfacing as 500s and error log spam. Every use of the shared connection (search_conversation_historyBM25 + semantic paths,get_history_message) is now serialised through a new_history_query_lock, and the index-reset path takes that lock before closing the handle so it can't be yanked out from under an in-flight search. Added a concurrency regression test that reproduced the exact error before the fix. - install.sh no longer hard-exits when the
claudeCLI isn't on PATH; it now warns and continues. CCC also drives Codex / Gemini / Antigravity sessions and the dashboard is useful without any engine installed, so the old gate silently dropped curious DMG downloaders the moment they double-clicked the .app — install.sh would error in a Terminal they may have already closed, and the .app's only signal was a "didn't start in 60s" fatal alert. Required prereqs are still git and python3. - Fixed the message echo sometimes lagging after pressing Enter. The optimistic "sending" echo now paints immediately and synchronously, before any async work (stopping in-progress text-to-speech, the inject-input request) — previously a TTS teardown was awaited first, so sends during playback felt delayed.
- When the conv input bar's send fails because the session's launch directory no longer exists on disk, the error toast now shouts the missing path ("⚠ Session cwd is gone: /path — every send will fail …") and stays up for 12 seconds instead of vanishing in 5. Previously the generic
invalid_cwdserver error message flashed by quickly and users assumed their text was being injected when it wasn't. Same longer-stay treatment applied to macOS automation-permission failures. - Fixes intermittent "last message in a conversation disappears" bug. Both conversation caches (
_CONV_PARSE_CACHEparsed events +_CONV_BYTES_CACHEserialized response bytes) usedst_mtime(float, 1-second resolution on macOS/Linux) as their freshness key. When a final assistant event landed in the same wall-clock second as a previous cache write, a poll/re-fetch would hit the stale cache and re-render the conversation without the trailing message. Switched_conv_parse_jsonl_mtimeto return(st_mtime_ns, st_size)— nanosecond precision + file size — and updated all four callers to use the tuple. Cache invalidates correctly even on sub-second appends. /api/sessions/live-activityis now coalesced, so concurrent dashboard polls no longer pile up and pin a CPU core. Every dashboard client (browser tab + desktop app) polls this endpoint, and each poll recomputed the whole live snapshot from scratch with no sharing. When polls got slow, clients fired new ones before old ones finished — a feedback loop that left many identical builds running at once, contending on the GIL. The snapshot is now cached behind a single-flight lock with a 1.5s TTL: at most one build runs at a time, and concurrent or rapid polls share its result (20 concurrent polls → 1 build; 50 rapid polls → 0). The data is inherently approximate (sidebar WIP chips), so ≤1.5s staleness is imperceptible. Combined with the codex incremental-tail and engine-detection memoisation fixes, this collapses the steady-state CPU of the poll path.- Live tool indicator (the green "▶ Bash command /bin/zsh -c …" pill) no longer leaks into a group-chat reader from the session you came from.
openGroupChatReaderstopped the conv/spawn SSE streams and rewrote#conversationsView, but leftcurrentSession.idpointing at the previous Claude session. The 1sliveStatuspoller kept querying that session andupdateLiveToolStripkept appending a.conv-live-tool-inlinenode into the now-group-chat-rendered view. Fix: callsetCurrentSession(null, …)on group-chat open so the poller bails (refreshLiveStatusshort-circuits on missing id) and sweep any in-DOM live-tool nodes immediately so there's no flash. - Dragging a conversation outside the Command Center macOS app window now opens another in-app window instead of launching the system browser. The dashboard also routes pop-out fallbacks through the native
.appwhen installed. - Model picker popup now flips ABOVE the pill when there isn't room below the viewport (the pill lives in the input strip near the bottom of the screen, so the default below-anchor used to clip the popup mostly off-screen). Picks below when it fits; otherwise picks whichever side has more room and clamps to keep the popup fully visible.
- Two more "no edits" false positives fixed. (1) Server: the Claude Bash parser was discarding the
signals["edit"]flag from_shell_command_signals, so sessions that edited viased -i,tee,apply_patch,cat > file,printf > file,perl -pi, etc. stayed marked as has_edit=false. Every other engine's parser already consumed this signal; the Claude branch was the lone holdout. (2) Client:hasNoEdits(c)now returns false for any live or just-spawned session so an in-flight session that hasn't recorded an edit yet doesn't get prematurely labeled "no edits". WIP/sending chips already take precedence at higher priority, so this just prevents the misleading verdict during the gap before they appear. - Sessions that delegate work to subagents no longer show a misleading "no edits" lifecycle chip.
c.has_editonly tracks Edit/Write/NotebookEdit tool_uses on the PARENT session's JSONL; when the parent spawns subagents (Task tool), the actual edits live in the subagent JSONLs and the parent'shas_editstays false. BothhasNoEdits(c)(list view) and the flow chip path now suppress "no edits" whenc.subagent_count > 0. - Organize no longer forgets manual parent connections (sessions dragged onto objects, nested objects). New rule R9 documented in the source. Pre-pass: every visible node's
data-flow-parentattribute is re-synced from the persistedflowNodeParentsmap so stale render-time attributes can't shadow a fresh drop. Session bucketing reads the map FIRST (manual link) and the DOM attribute SECOND (default parent). Post-pass: a full sidebar re-render rebuilds the DOM from source-of-truth maps (flowNodePositions+flowNodeParentsviaflowParentMapFor) so connector lines and parent links survive the Organize pass — no more reliance on a future poll-driven render to fix drift. - Pasted-image thumbnails now actually clear after sending. The post-send clear in every send path uses
el.value = ''via direct assignment, which does NOT fire an input event — so the prior "input goes empty → clear thumbs" listener never triggered post-send and the thumbnails lingered. Hook the value setter on every composer the paste handler binds to; any assignment (user input, programmatic clear, autoresize callsite) that results in an empty value now triggers thumbnail cleanup. - Fixed a layout reflow issue where the plan usage popover was cut off by using bottom-anchored positioning and restricting its max-height.
- After /compact lands, the view now scrolls to the resume-card boundary instead of the very bottom. Users reported the log "went back a day" after compact — actually the prior auto-scroll-to-end landed them at the post-compact END (still empty since no replies yet), so the visible area showed the LAST pre-compact message above the boundary. New behavior: detect the just-arrived compact-resume event and scrollIntoView({block: 'start'}) on its DOM node so the "Resumed from /compact summary" header sits at the top of the gaze area.
- Fixed queued outbound messages disappearing from the conversation pane after switching away and back in the conversation list.
- Ready to merge no longer shows the same PR twice. When two conversations both reference the same
tail_pr_number(e.g. a coding session + a GitHub-issue mirror), only the most recent one is kept in the section. The convs list is already sorted by recency upstream, so first-seen wins. - Ready-to-merge rows now prefer the real session row over the synthesized github_pr row when both reference the same PR — clicking the row jumps into the session that worked on it instead of opening the PR in a new tab. Previously, whichever row landed first in the dedup pass won; if the github_pr-source row got there first, the click was routed to
window.open(tail_pr_url)(the source='github_pr' branch in the row click handler) and the session was unreachable from this section. Dedup now swaps in-place when a real session row shows up after a github_pr one, keeping position stable. - Renamed sessions no longer revert to the AI-generated title after the next sidebar refresh.
_hydrate_conversation_rowswas unconditionally settingdisplay_name = Nonefor interactive sessions without a side-car name override — butrename_session()for dormant sessions writes the new name to the JSONL as a custom-title event, NOT the side-car. The clear was wiping the parser-derived rename on every fetch and the client fell back toai_title. Now we keep whatever display_name the parser derived from the JSONL. - RTL: added a MutationObserver that walks every
.assistant-text/.user-msgand tags its block descendants withdir="auto"post-insertion as a backup for the renderMarkdown regex. Catches DOM inserted by alternate paths (group-chat doc, issue render, etc.) or rendered by markdown that doesn't go through the centralrenderMarkdown. Idempotent — only sets the attribute when missing. - RTL fix v2: Hebrew/Arabic paragraphs now actually right-align (not just flip character order). The prior
unicode-bidi: plaintext+text-align: startCSS-only approach had a known gotcha —text-align: startresolves based on the element'sdirectionproperty, whichunicode-bidi: plaintextdoesn't change. Addeddir="auto"directly: (a) appended to every block-level tag (p,li,blockquote,h1-h6,td,th,dt,dd) emitted byrenderMarkdown, and (b) on every.assistant-text/.user-msgwrapper at creation time. The browser now detects direction per element from first strong directional character and aligns accordingly. - Prevent parallel session scans from piling up and slowing the whole app.
- Sidebar search input focus-loss fix now covers the poller-driven re-renders too — the earlier
5b8c4a8change only covered the keystroke-debounced render path, but the sidebar also re-renders every 5-15 seconds from independent pollers (liveStatus, sessions, group-chat-active). Those ticks landed mid-keystroke and blew away focus. Restore at the lowest level (right after$convList.innerHTML = …inrenderConversationList) so all render paths benefit. - Sidebar search input no longer loses focus after every keystroke in the native (WKWebView) app — capture focus + caret position before each debounced sidebar re-render and restore them right after if the search box was the active element. Only restores when the search box owned focus going in, so clicking away to a conversation between keystrokes doesn't yank focus back.
- Slash command suggestions are now ranked so name matches beat description matches. Typing
/contextno longer highlights/compact(whose description includes the word "context"). Scoring: exact name (after/) > name prefix > name substring > description substring; ties broken alphabetically. - Slash-commands typed without arguments (
/compact,/context,/clear, etc.) now render as visible "User typed /compact" events instead of being stripped to empty. The cleanIssuePrompt regex required all three of<command-name>,<command-message>,<command-args>to match; bare commands omit the args tag, so the match failed and the catch-all stripped everything — leaving the user_text blank. Users perceived this as "lots of text lost" between their last message and the SYSTEM compact marker. Now<command-args>is optional in the collapse pattern, and the/compactline surfaces between the prior conversation and the compact boundary. Also strips<local-command-caveat>/<local-command-stdout>wrappers so their verbose plumbing doesn't leak through. - Conversation row times in the sidebar no longer get stuck at "1d" / older when the underlying JSONL keeps getting written. Root:
archiveData(the source for the In Progress / archive lists) was only refreshed on app boot and folder-filter change.refreshLiveSessionsActivitypatched live sidecar fields onto rows but never the time fields (last_interacted/modified/mtime). So a Codex session whose CLI ran intermittently — or any session whose work continued across the day — kept showing whatever row time the original archive scan captured, even hours/days later. Added a 90sarchiveTimespoller that callsrefreshArchiveData({staleOk: true})and re-renders. 90s sits under the server's 5min_ARCHIVE_RESPONSE_CACHE_FRESH_TTLso most hits are cheap cache reads; the cadence guarantees row times advance within ~90s of real activity. Paused while the tab is hidden. - Streaming bubble content is no longer wiped when a sub-agent (Task tool) starts emitting blocks concurrently with the parent. Previously
ensureStreamingBubblekept a singleton_streamingBubblekeyed on_streamingMsgIdand CLEARED it whenever a new message_id arrived — so the parent's "let me find the existing X tests…" bubble vanished the moment the subagent's firstassistant_blocklanded with a different msg_id. Refactored to one bubble per msg_id (lookup-by-data-msg-id in the DOM); existing bubbles survive new sibling msg_ids landing.clearStreamingBubblenow sweeps every.stream-bubblenode so end-of-turn cleanup still works with multiple bubbles in flight. JSONL handoff path (renderConversationEventsline 19764) already removed bubbles by msg_id, so no change needed there. - Fixed the visual jump and flicker when assistant text is streamed. Streaming text blocks are now rendered incrementally as Markdown matching the final layout, horizontally aligned with the settled conversation messages, and no longer prepended with a layout-shifting timestamp prefix.
test_conv_bytes_cache_misses_when_pending_input_queuedno longer leaks a fakecache-pending-test-sessionJSONL into the user's real~/.claude/projects/-cache-pending/. The test now mocksPROJECTS_ROOTto a tmp dir for its duration and cleans up in a finally block. Previously the ghost row showed up in the live CCC UI after every test run and the archive endpoint errored on it because the session had no real metadata.- Injected sessions (UX-fixes-queue, etc.) now show the yellow WIP lifecycle chip while the agent is processing. Two changes: (1)
sessionIsOptimisticallySending(sid)is now part of the_isAgentRunningcalculation, so the chip appears as soon as the inject lands instead of only when the sidecar emits its first event. (2) The optimistic-sending window bumped from 60s to 5min so it bridges most agent turns. The chip clears either when real sidecar data lands or when the 5-minute window expires. - "Shell" / tool-name WIP chip no longer sticks on idle sessions. Added a final sanity gate in the row render: if the agent's last event was a
result(turn-finished marker) AND the row hasn't seen any new activity in the last 60s AND there's no fresh optimistic-send tag AND no pending spawn,_isAgentRunningis forced false — regardless of whatever stalepending_tool/sidecar_toolfields the server forgot to clear. The result-event marker is the authoritative "turn done" signal; trusting it over stale tool-name fields prevents the row from showing "Shell" indefinitely after the shell command finished.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About CCC
All releases →Related context
Related tools
Earlier breaking changes
- v5.0.1 Removes horizontal-drag gesture that collapsed conversation pane.
Beta — feedback welcome: [email protected]