chore: upstream sync 2026-04-20 — 25 commits from lobehub/canary#1
Conversation
…ntries
Merge-conflict resolution collapsed pantheon-snapshots→topology and
pantheon-np→chart-review because the closing },{ between them was
dropped. Result: duplicate keys silently overrode the earlier entries,
so getRouteById(snapshots|np) returned undefined and useNavLayout
crashed with Cannot read properties of undefined (reading icon).
* 🐛 fix(cc-resume): guard resume against cwd mismatch (LOBE-7336) Claude Code CLI stores sessions per-cwd under `~/.claude/projects/<encoded-cwd>/`, so resuming a session from a different working directory fails with "No conversation found with session ID". Persist the cwd alongside the session id on each turn and skip `--resume` when the current cwd can't be verified against the stored one, falling back to a fresh session plus a toast explaining the reset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(cc-desktop): Claude Code desktop polish + completion notifications Bundles the follow-on UX improvements for Claude Code on desktop: - Completion notifications: CC / Codex / ACP runs now fire a desktop notification (when the window is hidden) plus dock badge when the turn finishes, matching the Gateway client-mode behavior. - Inspector + renders: add Skill and TodoWrite inspectors, wire them through Render/index + renders registry, expose shared displayControls. - Adapter: extend claude-code adapter with additional event coverage and regression tests. - Sidebar / home menu: clean up Topic list item and dropdown menu, rename "Claude Code Agent" entry point to "Add Claude Code" across EN/ZH. - Assorted: NotificationCtr, Browser, WorkflowCollapse, ServerMode upload, agent/tool selectors — small follow-ups surfaced while building the above. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✅ test(browser): mock electron.app for badge-clear on focus Browser.focus handler now calls app.setBadgeCount / app.dock.setBadge to clear the completion badge when the user returns. Tests imported the Browser module without exposing app on the electron mock, causing a module-load failure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(cc-topic): folder chip + unify cwd into workingDirectory (lobehub#13949) ✨ feat(cc-topic): show bound folder chip and unify cwd into workingDirectory Replace the separate `ccSessionCwd` metadata field with the existing `workingDirectory` so a CC topic's bound cwd has one source of truth: persisted on first CC execution, read back by resume validation, and surfaced in a clickable folder chip next to the topic title on desktop. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 13fe968)
* ✨ feat(cc-agent-profile): swap model/skills pickers for CC CLI status in CC mode When an agent runs under the Claude Code heterogeneous runtime, its model and tools are owned by the external CLI, so the profile page's model selector and integration-skills block are misleading. Replace them with a card that re-detects `claude --version` on mount and shows the resolved binary path — useful when CLAUDE_CODE_BIN or similar points at a non-default CLI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(cc-agent-profile): hide cron for CC agent and polish render previews - Hide cron sidebar entry when current agent is heterogeneous (CC) - Allow model avatar in agent header emoji picker - Add padding to Glob/Grep/Read/Write preview boxes for consistent spacing - Simplify NavPanelDraggable by removing slide animation layer Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(shared-tool-ui): extract ToolResultCard for Read/Write/Glob/Grep renders Hoist the shared card shell (icon + header + preview box) into @lobechat/shared-tool-ui/components so the four Claude Code Render files no longer duplicate container/header/previewBox styles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(agent-header): restyle title and expand actions menu Bold the topic title, render the working directory as plain text (no chip/icon), move the "..." menu to the left, and expand it with pin/rename/copy working directory/copy session ID/delete. Fall back to "New Topic" when no topic is active. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(topic-list): replace spinning loader with ring-and-arc loading icon Adds a reusable RingLoadingIcon (static track + rotating arc, mirroring the send-button style) and swaps the topic-item loader over to it so the loading state reads as a polished ring rather than a thin spinning dash. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(topic-list): switch unread indicator to a radar ping effect Replaces the glowing neon-dot pulse with a smaller 6px core dot plus a CSS-keyframe ripple ring that scales out and fades, giving the unread marker a subtler, more refined cadence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(cc-chat-input): drop file upload in CC mode, surface typo toggle Claude Code brings its own file handling and knowledge context, so the paperclip dropdown only showed "Upload Image" + a useless "View More" link — confusing and not clean. Replace fileUpload with typo in the heterogeneous chat input, and fold ServerMode back into a single Upload/index.tsx now that the ClientMode/ServerMode split is gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 5dc94cb)
…modal + inspectors (lobehub#13951) * ✨ feat(cc-desktop): git-aware runtime config + topic rename modal + inspectors Cluster of desktop UX improvements around the Claude Code integration: - CC chat input runtime bar: branch switcher, git status, and a richer working-directory bar powered by a new SystemCtr git API (branch list / current status) and `useGitInfo` hook. - Topic rename: switch to a dedicated RenameModal component; add an auto-rename action in the conversation header menu. - ToolSearch inspector for the CC tool client. - Shared DotsLoading indicator. - Operation slice tidy-ups for CC flows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(types): rename heterogeneous provider type `claudecode` → `claude-code` Align the type literal with the npm/CLI naming convention used elsewhere (@lobechat/builtin-tool-claude-code, claude-code provider id) so the union matches the rest of the codebase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(cc-desktop): polish TodoWrite labels, branch switcher refresh, and chat input affordances - TodoWrite render + inspector: i18n the header label (Todos / Current step / All tasks completed), surface the active step inline as highlighted text, and switch the in-progress accent from primary to info for better contrast. - BranchSwitcher: move the refresh button into the dropdown's section header, switch the search and create-branch inputs to the filled variant, and reuse DropdownMenuItem for the create-branch entry instead of a custom footer chip. - GitStatus: drop the inline refresh affordance (now lives in the switcher), collapse trigger styles, and split the PR badge with its own separator. - WorkingDirectory / WorkingDirectoryBar: tighten paddings and gaps so the runtime config row reads at a consistent height. - InputEditor: skip inline placeholder completion when the cursor is not at end of paragraph — inserting a placeholder mid-text triggered nested editor updates that froze the input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(cc-desktop): probe repoType for working dirs not cached in recents GitStatus was gated on the `repoType` stored in `recentDirs`, but legacy string entries and agent-config-driven paths that never went through the folder picker have no cached `repoType`. As a result, branch / PR status silently disappeared for valid git repos until users re-selected the folder. Promote `detectRepoType` to a public IPC method and add a `useRepoType` hook that uses the cached value as a fast path, otherwise probes the filesystem via SWR and backfills the recents entry so subsequent reads hit cache. Both runtime config bars (CC mode + heterogeneous chat input) now resolve `repoType` through the hook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(shared-tool-ui): rework Bash/Grep/Glob inspector rows - RunCommand: terminal-prompt icon + mono command text instead of underline highlight - Grep: split pattern by `|` into mono tag chips - Glob: single mono tag chip matching Grep - Switch rows to baseline alignment so the smaller mono text lines up with the label Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(DotsLoading): allow optional color in styles params The Required<StyleArgs> generic forced color to string, but it's only defaulted at the CSS level via fallback to token.colorTextSecondary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 7d5889a)
…behub#13955) * 💄 style(error): refine error page layout and stack panel Replace Collapse with Accordion for a clickable full-row header, move stack below action buttons as a secondary branch, and wrap in a Block that softens to filled when collapsed and outlined when expanded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(cc): boost topic loading ring contrast in light mode Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(error): reload page on retry instead of no-op navigate The retry button called navigate(resetPath) which often landed on the same path and re-triggered the same error, feeling broken. Switch to window.location.reload() so the error page actually recovers, and drop the now-unused resetPath prop across route configs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(cc-agent): send prompt via stdin stream-json to avoid CLI arg parsing Previously the Claude Code prompt was appended as a positional CLI arg, so any prompt starting with `-` / `--` (dashes, 破折号) got misinterpreted as a flag by the CC CLI's argparser. Switch the claude-code preset to `--input-format stream-json` and write the prompt as a newline-delimited JSON user message on stdin for all messages (not just image-attached ones). Unifies the image and text paths and paves the way for LOBE-7346 Phase 2 (persistent process + native queue/interrupt). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(cc): extract per-tool inspectors into Inspector/ folder Mirrors the Inspector/<Tool>/index.tsx convention used by builtin-tool-skills, builtin-tool-skill-store, and builtin-tool-activator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(cc): flatten Inspector/ to per-tool tsx files Drop the per-tool subfolder wrapper (Inspector/Edit/index.tsx → Inspector/Edit.tsx) since each tool is a single file — no co-located assets to justify the folder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(topic): add filter with By project grouping and sort-by option Split the legacy topicDisplayMode enum into independent topicGroupMode (byTime / byProject / flat) and topicSortBy (createdAt / updatedAt), and surface them from a new sidebar Filter dropdown. Adds groupTopicsByProject so topics can be grouped by their workingDirectory, with favorites pinned and the "no project" bucket placed last. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(cc): show Claude Code account and subscription on profile Add a getClaudeAuthStatus IPC that shells out to claude auth status --json, and render the returned email + subscription tag on the CC Status Card. The auth fetch runs independently of tool detection so a failure can't flip the CLI card to unavailable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(home): show running spinner badge on agent/inbox avatars Replace NavItem's generic loading state with a bottom-right spinner badge on the avatar, so a running agent stays clearly labelled without hiding the avatar. Inbox entries switch to per-agent isAgentRunning so only the actively running inbox shows the badge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(cc): default-expand Edit and Write tool renderers Add ClaudeCodeApiName.Edit and Write to ClaudeCodeRenderDisplayControls so their inspectors render expanded by default, matching TodoWrite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🔧 chore(cc): drop default system prompt when creating Claude Code agent Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Update avatar URL for Claude Code * ✅ test(workflow-collapse): stub ShikiLobeTheme on @lobehub/ui mock @lobehub/editor's init code reads ShikiLobeTheme from @lobehub/ui, which some transitive import pulls in during the test. Add the stub to match the pattern used in WorkingSidebar/index.test.tsx. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(cc): fall back to Desktop path instead of `/` when no cwd is set - Selector prefers desktopPath over homePath before it resolves nothing, so the renderer always forwards a sensible cwd. - Main-process spawn mirrors the same fallback with app.getPath('desktop'), covering cases where Electron is launched from Finder (parent cwd is `/`). Fixes LOBE-7354 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(topic): use remote app origin for topic copy link Desktop 下 window.location.origin 是 app://renderer,复制出来的链接无法分享。 改用 useAppOrigin(),与分享链接保持一致(web 用 window.location.origin, desktop 用 electron store 的 remoteServerUrl)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit d581937)
…ub#13959) 💄 style(shared-tool-ui): wrap RunCommand inspector in a rounded chip Put the terminal-prompt icon and the mono command text inside a single pill-shaped chip (colorFillTertiary background) so the command reads as one unit instead of two loose elements next to the "Bash:" label. Row goes back to center-aligned since the chip has its own vertical padding. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 9218fbf)
Previously `agentId` was only used to boost relevance in SearchRepo, so results from other agents still leaked into CMD+K when scoped to an agent. Strictly filter topics/messages by `agentId` when provided, and surface the active agent (avatar + title) as the scope chip so users can see what the search is limited to. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit bc9164a)
…ehub#13961) CC-specific naming leaked into a field/module that's meant to be shared across heterogeneous agent adapters. Rename to a provider-neutral id so new adapters can reuse the topic-level session binding without inheriting CC terminology. - ChatTopicMetadata.ccSessionId -> heteroSessionId - resolveCcResume / CcResumeDecision -> resolveHeteroResume / HeteroResumeDecision - ccResume.{ts,test.ts} -> heteroResume.{ts,test.ts} - updateTopicMetadata zod schema + executor + conversationLifecycle callsites Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 30e93ad)
…#13956) Hetero-agent topic creation went through `aiChat.sendMessageInServer`'s `newTopic` payload, which had no metadata field, so the topic row was inserted with `metadata.workingDirectory = NULL`. Today the only writer is the post-execution `updateTopicMetadata` in `heterogeneousAgentExecutor` — that never lands when CC is cancelled or errors before completion, and in the meantime the topic is missed by By-Project grouping and `--resume` cwd verification has nothing to compare against. Source the cwd at the start of the hetero branch and thread it through `newTopic.metadata`, so the binding is set at insert time. The post-exec update still runs to record `ccSessionId` (and is now a no-op for cwd). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit f38dcc4)
* ✨ feat(hetero-agent): add hetero-mode actions bar with copy/delete only Hide edit, regenerate, branching, translate, tts, share and delAndRegenerate for heterogeneous-agent sessions where these actions don't apply. Introduce `mode: 'hetero'` on MessageActionsConfig and dispatch to dedicated Hetero action bars for user, assistant, and assistant-group messages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(conversation): replace per-role action hooks with declarative action registry Replace the 4 duplicate per-role action hooks (useUserActions / useAssistantActions / useGroupActions / Task.useAssistantActions) and the 4 copies of stripHandleClick / buildActionsMap / dispatch logic with a single registry + universal MessageActionBar renderer. Each action (copy / del / edit / regenerate / delAndRegenerate / continueGeneration / translate / tts / share / collapse / branching) is now a standalone module under components/MessageActionBar/actions/. Config is declarative — string slot keys (e.g. ['copy', 'divider', 'del']) resolved against the registry at render time. Hetero-agent sessions drop the special mode flag; they just declare copy-only slot lists via config. Dev-mode branching becomes a registry key instead of a factory. Deletes ErrorActionsBar (handled in-place via slot lists), the dead Supervisor/Actions folder, and the HeteroActionsBar scaffold introduced in the previous commit. Net: -1900 lines, one place to add a new action. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit b909e4a)
…ync (lobehub#13957) * ✨ feat(desktop): add dedicated topic popup window with cross-window sync Introduce a standalone Vite entry for the desktop "open topic in new window" action. The popup is a lightweight SPA (no sidebar, no portal) hosting only the Conversation, and stays in sync with the main window through a BroadcastChannel bus. - Add popup.html + entry.popup.tsx + popupRouter.config.tsx - Add /popup/agent/:aid/:tid and /popup/group/:gid/:tid routes - Reuse main Conversation/ChatInput; wrap in MarketAuth + Hotkeys providers - Pin-on-top button in the popup titlebar (new windows IPC: set/isAlwaysOnTop) - Group topic "open in new window" now uses groupId (previously misused agentId) - Cross-window sync: refreshMessages/refreshTopic emit via BroadcastChannel; subscriber revalidates local SWR caches with echo-loop suppression - Hide WorkingPanel toggle inside /popup (no WorkingSidebar present) - RendererUrlManager dispatches /popup/* to popup.html in prod; dev middleware rewrites SPA deep links while skipping asset/module requests * 💄 style(desktop): restore loading splash in popup window * ♻️ refactor(desktop): replace cross-window sync with popup-ownership guard The BroadcastChannel-based bidirectional sync between the main SPA and the topic popup window had edge cases during streaming. Drop it in favour of a simpler ownership model: when a topic is already open in a popup, the main window shows a "focus popup" redirect instead of rendering a second conversation. - Remove src/libs/crossWindowBus.ts and src/features/CrossWindowSync - Remove postMessagesMutation/postTopicsMutation calls from refresh actions - Add windows.listTopicPopups + windows.focusTopicPopup IPC - Main process broadcasts topicPopupsChanged on popup open/close; parses (scope, id, topicId) from the popup window's /popup/... path - Renderer useTopicPopupsRegistry subscribes to broadcasts and fetches the initial snapshot; useTopicInPopup selects by scope - New TopicInPopupGuard component with "Focus popup window" button - Desktop-only index.desktop.tsx variants for (main)/agent and (main)/group render the guard when the current topic is owned by a popup - i18n: topic.inPopup.title / description / focus in default + en/zh * 🐛 fix(desktop): re-evaluate popup guard when topic changes Subscribe to the popups array and derive findPopup via useMemo so scope changes (e.g. switching topic in the sidebar while a popup is open) correctly re-compute the guard and let the main window render the newly active topic. * 🐛 fix(desktop): focus detached topic popup from main window * ✨ feat(desktop): add open in popup window action to menu for active topic Signed-off-by: Innei <tukon479@gmail.com> * 🎨 style: sort imports to satisfy simple-import-sort rule * ✨ feat(error): add resetPath prop to ErrorCapture and ErrorBoundary for customizable navigation Signed-off-by: Innei <tukon479@gmail.com> * ♻️ refactor: restore ChatHydration in ConversationArea for web/mobile routes Reintroduce ChatHydration component to agent and group ConversationArea so that URL query sync (topic/thread) works on web and mobile routes, not only on desktop entry files. * ✨ feat(electron): enforce absolute base URL in renderer config to fix asset resolution in popup windows Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com> (cherry picked from commit 2711aa9)
…ant message (lobehub#13964) * ♻️ refactor(hetero-agent): persist per-step usage to each step assistant message Previously, usage tokens from a multi-step Claude Code run were accumulated across all turns and written only to the final assistant message, leaving intermediate step messages with no usage metadata. Each Claude Code `turn_metadata` event carries per-turn token usage (deduped by adapter per message.id), so write it straight through to the current step's assistant message via persistQueue (runs after any in-flight stream_start(newStep) that swaps currentAssistantMessageId). The `result_usage` grand-total event is intentionally dropped — applying it would overwrite the last step with the sum of all prior steps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(hetero-agent): normalize usage inside CC adapter (UsageData) Follows the same principle as LOBE-7363: provider-native shape knowledge stays in the adapter, executor only sees normalized events. The previous commit left Anthropic-shape fields (input_tokens, cache_creation_input_tokens, cache_read_input_tokens) leaking into the executor via `buildUsageMetadata`. Introduce `UsageData` in `@lobechat/heterogeneous-agents` types with LobeHub's MessageMetadata.usage field names. The Claude Code adapter now normalizes Anthropic usage into `UsageData` before emitting step_complete, for both turn_metadata (per-turn) and result_usage (grand total). Executor drops `buildUsageMetadata` and writes `{ metadata: { usage: event.data.usage } }` directly. Future adapters (Codex, Kimi-CLI) normalize their native usage into the same shape; executor stays provider-agnostic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(hetero-agent): persist per-step provider alongside model CC / hetero-agent assistant messages were writing `model` per step but leaving `message.provider` NULL, so pricing/usage lookups could not key on the adapter (e.g. `claude-code`, billed via CLI subscription rather than raw Anthropic API rates). CC adapter now emits `provider: 'claude-code'` on every turn_metadata event (same collection point as model + normalized usage). Executor tracks `lastProvider` alongside `lastModel` and writes it into: - the step-boundary update for the previous step - `createMessage` for each new step's assistant - the onComplete write for the final step Provider choice is the CLI flavor (what the adapter knows), not the wrapped model's native vendor — CC runs under its own subscription billing, so downstream pricing must treat `claude-code` as its own provider rather than conflating with `anthropic`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): read authoritative usage from message_delta, not assistant Under `--include-partial-messages` (enabled by the CC adapter preset), Claude Code echoes a STALE usage snapshot from `message_start` on every content-block `assistant` event — e.g. `output_tokens: 8` or `1` — and never updates that snapshot as more output tokens are generated. The authoritative per-turn total arrives on a separate `stream_event: message_delta` with the final `input_tokens` + cache counts + cumulative `output_tokens` (e.g. 265). The adapter previously grabbed usage from the first `assistant` event per message.id and deduped, so DB rows ended up with `totalOutputTokens: 1` on every CC turn. Move turn_metadata emission from `handleAssistant` to a new `message_delta` case in `handleStreamEvent`. `handleAssistant` still tracks the latest model so turn_metadata (emitted later on message_delta) carries the correct model even if `message_start` doesn't. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(extras-usage): fall back to metadata.usage when top-level is absent The assistant Extras bar passes `message.usage` to the Usage component, which conditionally renders a token-count badge on `!!usage.totalTokens`. Nothing in the read path aggregates `message.metadata.usage` up to `message.usage`, so the top-level field is always undefined for DB-read messages — the badge never shows for CC/hetero turns (and in practice also skips the gateway path where usage only lands in `metadata.usage`). Prefer `usage` when the top-level field is populated, fall back to `metadata.usage` otherwise. Both fields are the same `ModelUsage` shape, so the Usage/TokenDetail components don't need any other change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(extras-usage): promote metadata.usage inside conversation-flow parse The previous fix spread a `usage ?? metadata?.usage` fallback across each renderer site that passed usage to the Extras bar. Consolidate: `parse` (src/store → packages/conversation-flow) is the single renderer-side transform every consumer flows through, so promote `metadata.usage` onto the top-level `usage` field there and revert the per-site fallbacks. UIChatMessage exposes a canonical `usage` field, but no server-side or client-side transform populated it — executors write to `metadata.usage` (canonical storage, JSONB-friendly). Doing the promotion in parse keeps the rule in one place, close to where display shapes are built, and covers both desktop (local PGlite) and web (remote Postgres) without a backend deploy. Top-level `usage` is preserved when already present (e.g. group-level aggregates) — `metadata.usage` is strictly a fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit ccbb75d)
…lapse workflow summary (lobehub#13968) * 🐛 fix(hetero-agent): persist accumulated text alongside tools[] writes Carry the latest streamed content/reasoning into the same UPDATE that writes tools[], so the DB row stays in sync with the in-memory stream. Without this, gateway `tool_end → fetchAndReplaceMessages` reads a tools-only row and clobbers the UI's streamed text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(workflow-summary): collapse summary when many tool kinds When a turn calls >4 distinct tool kinds, list only the top 3 by count and append "+N more · X calls total[ · Y failed]". Keeps the inline summary scannable on long tool-heavy turns instead of running off the line. Short turns keep the existing full list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(claude-code): use chip style for Skill inspector name Replace the colon+highlight text with a pill-shaped chip containing the SkillsIcon and skill name. Gives the Skill activation readout visual parity with other tool chips and prevents long skill names from overflowing the inspector line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✅ test(agent-documents): assert on rendered title, not filename lobehub#13940 changed DocumentItem to prefer document.title over filename, but the sidebar test still expected 'brief.md' / 'example.com'. Align the assertions with the current behavior so the suite is green on canary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(tab-bar): show agent avatar on agent/topic tabs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 77fd0f1)
…obehub#13970) * ✨ feat(hetero-agent): synthesize pluginState.todos from CC TodoWrite Adapter now translates Claude Code's declarative TodoWrite tool_use input into the shared StepContextTodos shape and attaches it to tool_result. Selector drops the GTD identifier filter so any producer honoring pluginState.todos lights up the TodoProgress card. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): skip TodoWrite pluginState synthesis on error results A failed TodoWrite (is_error=true) means the snapshot was never applied on CC's side. Since selectTodosFromMessages now picks the latest pluginState.todos from any producer, leaking a failed-write snapshot could overwrite the live todo UI with changes that never actually happened. Drain the cache either way so a retry with a fresh tool_use id doesn't inherit stale args. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): prefer topic-level cwd on send; route UI changes to active topic Topic-level workingDirectory now takes priority over agent-level on the send path, matching what the topic is actually pinned to. The UI picker writes to the active topic's metadata (not the agent default), and warns before switching when doing so would invalidate an existing CC session. * ✨ feat(tab): reset tab cache when page type changes to stop stale metadata bleed Switching a tab from one page type to another (e.g. agent → home) kept the previous page's cached title/avatar, so the new page rendered with the wrong header. Reset the cache on type change; preserve the merge only when the type stays the same. * 🐛 fix(hetero-agent): kill CC process tree on cancel so tool children exit SIGINT to just the claude binary was leaving bash/grep/etc. tool subprocesses running, which kept the CLI hung waiting on them. Spawn the child detached (Unix) so we can signal the whole group via process.kill(-pid, sig); use taskkill /T /F on Windows. Escalate SIGINT → SIGKILL after 2s for tool calls that swallow SIGINT, and do the same tree kill on disposeSession's SIGTERM path. * ✨ feat(hetero-agent): show "Full access" badge in CC working-directory bar Claude Code runs locally with full read/write on the working directory and permission mode switching isn't wired up yet — the badge sets that expectation up-front instead of leaving users guessing. Tooltip spells out the constraint for anyone who wants detail. * ♻️ refactor(agent-list): show runtime name (Claude Code/Codex) instead of generic "External" tag The "External" tag on heterogeneous agents didn't tell users which runtime backs the agent — multiple CLI runtimes (Claude Code, Codex, …) looked identical in the sidebar. Map the heterogeneous type to its display name so the tag identifies the actual runtime, with the raw type as a fallback for any future provider we haven't mapped yet. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 6ca5fc4)
…by default (lobehub#13973) * 💄 style(tab-bar): blend inactive tabs with titlebar, show close icon by default Inactive tabs now use a transparent background and gain a subtle hover fill, matching Chrome's tab chrome so the titlebar feels visually unified. The close icon is always visible instead of fading in on hover, so users don't have to hunt for it on narrow tabs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(desktop): CMD+N now actually clears active topic on agent page Previously the File → 新建话题 (CMD+N) handler only `navigate()`d to the agent base path. When the user was on `/agent/:aid?topic=xxx`, this stripped the URL param but `ChatHydration`'s URL→store updater skips `undefined` values, so `activeTopicId` in the chat store was never cleared and the subscriber would push the stale topic right back into the URL. Call `switchTopic(null)` on the store directly when an agent is active so the change propagates store→URL via the existing subscriber. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(hetero-agent): don't surface self-cancelled exits as runtime errors User-initiated cancel/stop and Electron before-quit kill the agent process with SIGINT/SIGTERM, producing non-zero exit codes (130/143/137). Mark these via session.cancelledByUs so the exit handler routes them through the complete broadcast — otherwise a user cancel or app shutdown would look like an agent failure (e.g. "Agent exited with code 143" leaking into other live CC sessions' topics). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(tab-bar): show running indicator dot on tab when agent is generating Adds a useTabRunning hook that reads agent runtime state from the chat store for agent / agent-topic tabs, and renders a small gold dot over the tab avatar/icon while the conversation is generating. Other tab types stay unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(claude-code): render ToolSearch select: queries as inline tags Parses select:A,B,C into individual tag chips (monospace, subtle pill background) instead of a comma-joined string, so the names of tools being loaded read more clearly. Keyword queries keep the existing single-highlight rendering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(git-status): show +N ±M -K diff badge next to branch name Surface uncommitted-file count directly in the runtime-config status bar so the dirty state is visible at a glance without opening the branch dropdown. Each segment is color-coded (added / modified / deleted) and hidden when zero; a tooltip shows the verbose breakdown. Implementation: - Backend buckets `git status --porcelain` lines into added / modified / deleted / total via X+Y status pair - New always-on useWorkingTreeStatus SWR hook (focus revalidation, 5s throttle) shared by GitStatus and BranchSwitcher — single fetch path - BranchSwitcher's "uncommitted changes: N files" now reads `total` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(assistant-group): show only delete button while tool call is in progress When the last child of an assistantGroup is a running tool call, `contentId` is undefined and the action bar fell through to a branch that dropped the `menu` and `ReactionPicker`, leaving a single copy icon with no overflow. Replace the legacy `continueGeneration / delAndRegenerate / del` bar with a del-only bar in this state — delete is the only action that makes sense before any text block is finalized. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(conversation-flow): aggregate per-step nested metadata.usage in assistantGroup After hetero-agent moved to per-step usage writes (`metadata: { usage: {...} }`), the assistantGroup virtual message stopped showing the cumulative token total across steps and instead surfaced only the last step's numbers. Root cause: splitMetadata only recognised the legacy flat shape (`metadata.totalTokens`, etc.) and didn't read the new nested shape, so each child block went into aggregateMetadata with `usage: undefined`. The sum was empty, and the final group inherited a single child's metadata.usage purely because Object.assign collapsed groupMetadata down to the last child. - splitMetadata now reads both nested (`metadata.usage` / `metadata.performance`) and flat (legacy) shapes; nested takes priority - Add `'usage'` / `'performance'` to the usage/performance field sets in parse and FlatListBuilder so the nested objects don't leak into "other metadata" - Regression test: multi-step assistantGroup chain sums child usages Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(hetero-agent): tone down full-access badge to match left bar items The badge was shouting in colorWarning + 500 weight; reduce to colorTextSecondary at normal weight so it sits at the same visual rank as the working-dir / git buttons on the left. The CircleAlert icon still carries the warning semantics. Also force cursor:default so the non-interactive label doesn't pick up an I-beam over its text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 46df77a)
…idebar polish (lobehub#13978) * 🐛 fix(desktop): detect repo type for submodule and worktree directories Route detectRepoType through resolveGitDir so directories where `.git` is a pointer file (submodules, worktrees) are correctly identified as git/github repos instead of falling back to the plain folder icon. Fixes LOBE-7373 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(desktop): reprobe repo type for stale recent-dir entries The recents picker rendered `entry.repoType` directly from localStorage, so any submodule/worktree entry cached while `detectRepoType` still returned `undefined` stayed stuck on the folder icon even after the main-process fix. Wrap each row icon in a component that calls `useRepoType`, which re-probes missing entries and backfills the cache. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(chat-input): clear autocomplete hint on IME start to prevent freeze Dispatch KEY_ESCAPE_COMMAND on compositionstart so the autocomplete plugin removes PlaceholderInline/PlaceholderBlock nodes before the IME begins composing. Composing next to those placeholder nodes caused the editor to freeze during pinyin input with a visible hint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(topic-sidebar): split project grouping into ByProjectMode Extracts project-specific group rendering from ByTimeMode into its own ByProjectMode folder, with a shared GroupedAccordion container. Project groups get a folder-icon column aligned with the topic item layout and a "new topic in {directory}" action. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(desktop): read config via commondir for linked worktrees `resolveGitDir` returns `.git/worktrees/<name>/` for linked worktrees — that dir has its own `HEAD` but no `config`, so `detectRepoType` still returned `undefined` and worktrees missed the repo icon. Resolve the `commondir` pointer first so `config` is read from the shared gitdir. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 8240e86)
* fix: local webhook typing * feat: add dormant status * feat: add bot status tag * feat: add bot connection status and refresh status * feat: support bot status list refresh * fix: bot status * chore: add test timeout (cherry picked from commit 0213656)
…ext (lobehub#13972) * ✨ feat(electron): add + button to TabBar to open new topic in active context Introduce a pluggable `createNewTabAction` extension on RecentlyViewed plugins so each page type can decide whether (and how) to spawn a new tab from the active tab. Implemented for agent / agent-topic / group / group-topic — clicking `+` creates a fresh topic under the current agent/group and opens it as a new tab; other page types hide the button by default. * ✨ feat(electron): support new tab from page context Page plugin now implements `createNewTabAction`, creating a fresh untitled document via `usePageStore().createPage` and opening it as a new `page` tab. * 🐛 fix(electron): refresh page list after creating a new page via TabBar + `createPage` only hits the service; without refreshing the documents list, the sidebar / PageExplorer wouldn't show the freshly-created page until the next full reload. * 🐛 fix(electron): highlight new page in sidebar when opened via TabBar + Switch to `createNewPage`, which runs the full optimistic flow — dispatches the new document into the sidebar list and sets `selectedPageId` — so the nav item active state stays in sync with the freshly-opened page tab. * 🐛 fix(electron): dispatch real page doc into sidebar list for TabBar + The earlier `createNewPage` approach relied on an optimistic temp document that SWR revalidation can clobber before the real doc replaces it, leaving the new page absent from the sidebar. Create the page via `createPage` first, then synthesize a `LobeDocument` from the server response and dispatch it into the list alongside setting `selectedPageId` — the nav item now appears and highlights in sync with the new tab. (cherry picked from commit 730169e)
…tial load (lobehub#13981) 🐛 fix(electron): align TabBar left padding with NavPanel width on initial load Defer DraggablePanel mount in NavPanelDraggable until `isStatusInit` flips true so defaultSize captures the hydrated `leftPanelWidth` instead of the pre-hydration default. Before hydration, render a placeholder div matching the store's current width so NavigationBar's live-read width stays aligned with the DOM. Also adds a small paddingRight to NavigationBar for visual balance. Without this, the TabBar's left edge drifted away from the NavPanel's right edge whenever the user's persisted panel width differed from the 320px default. (cherry picked from commit 3bd7f1f)
…hub#13980) * 💄 style(todo-progress): replace green bar with inline progress ring Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(chat-input): split branch and diff blocks, add changed-files popover Branch now has its own hover tooltip for the full name; the diff stat is a sibling block that opens a lazy-loaded popover listing changed files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(chat-input): show ahead/behind commit count vs upstream Adds a badge next to the branch chip showing commits pending push (↑, blue) and pull (↓, red) against the branch's upstream tracking ref. Hidden when no upstream is configured or both counts are zero. Refreshed on focus, after checkout, and on manual refresh from the branch switcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ♻️ refactor(desktop): extract git IPC methods into dedicated GitController Moves detectRepoType, getGitBranch, getLinkedPullRequest, listGitBranches, getGitWorkingTree{Status,Files}, getGitAheadBehind, and checkoutGitBranch out of SystemCtr into a new GitCtr (groupName = 'git'). Shared helpers (resolveGitDir / resolveCommonGitDir / detectRepoType) become pure functions under utils/git.ts so SystemCtr's selectFolder can still probe the picked folder without crossing controller boundaries. Renderer side: new electronGitService wraps ipc.git.*, and all six chat-input hooks plus BranchSwitcher are switched over. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(chat-input): inline ahead/behind arrows into branch chip Moves the ↑/↓ counts out of a separate status block and inside the branch trigger next to the label, so they sit with the branch they describe instead of after the file-change badge. Tooltip folds into the branch tooltip (full name · N to push · M to pull) so a single hover covers both pieces of info. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(desktop): parse git status with -z to avoid filename misparse The previous getGitWorkingTreeFiles split every line on ' -> ' to detect renames, but only R/C status codes emit that delimiter. Legitimate filenames containing ' -> ' (or spaces, or embedded newlines) were misparsed — the popover would report a truncated path or lose the entry entirely. Switch both getGitWorkingTreeStatus and getGitWorkingTreeFiles to `git status --porcelain -z`: NUL-terminated records, no C-style quoting, no \n splitting hazards. Rename/copy entries emit two NUL-separated tokens (DEST\0SRC) which we consume as a pair so counts and paths stay correct. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(todo-progress): hide stale todos when a new user turn starts Add `selectCurrentTurnTodosFromMessages` that scopes the todos lookup to messages after the last user message. The inline TodoProgress component now uses it, so a completed 8/8 progress bar from a previous operation no longer lingers across the next user turn. The original `selectTodosFromMessages` is unchanged because the agent runtime step context still needs cross-turn visibility of the plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🔒 fix(desktop): tighten GitHub remote detection to host position Replace substring check `config.includes('github.com')` with a regex anchored to URL host position so look-alikes like `evilgithub.com` and `github.com.attacker.com` no longer classify as GitHub. Closes CodeQL "Incomplete URL substring sanitization" on PR lobehub#13980. Not a real security issue (the config file is local and the classification only drives a UI icon), but the tightened check is strictly more correct and silences the scanner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit a0471d5)
…ehub#13896) (cherry picked from commit fb47112)
…er (lobehub#13982) Reject avatar values that aren't a base64 data URL, an absolute http(s) URL, or an internal /webapi/user/avatar/<userId>/ path for the caller. Also require the old avatar URL to live under the caller's own prefix (and contain no '..') before removing it from S3. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit e7236c0)
…olish (lobehub#13983) * 💄 style(topic): darken project group folder label in sidebar Previous `type='secondary'` on the group title was too faint against the sidebar background; promote the text to default color for better legibility and keep the folder icon at tertiary so it stays subtle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(topic): use colorTextSecondary for project group title Text's `type='secondary'` resolves to a lighter token than `colorTextSecondary`; apply `colorTextSecondary` directly so the title lands at the intended shade (darker than before, lighter than default). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(electron): show blue unread dot on tab when agent has unread badge Mirror the sidebar agent unread badge on the corresponding browser-like tab as a subtle blue dot, so unread completions are visible even when the sidebar is out of view. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(electron): forward proxy env vars to spawned agent CLI The main-process undici dispatcher set by ProxyDispatcherManager only covers in-process requests — child processes like claude-code CLI never saw the user's proxy config. Extract a shared `buildProxyEnv` so any CLI spawn can merge HTTP(S)_PROXY / ALL_PROXY / NO_PROXY into its env. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(electron): close active tab on Cmd+W when multiple tabs are open Cmd/Ctrl+W now closes the focused tab first and only closes the window when a single tab (or none) remains. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(electron): add Cmd+T shortcut to open a new tab Reuses the active tab's plugin context to create a same-type tab, mirroring the TabBar + button behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 💄 style(electron): use container color for active tab background Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✅ test(electron): update Close menu item expectations for smart Cmd+W Tests now assert the CmdOrCtrl+W accelerator and click handler instead of the legacy role: 'close'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(electron): drop const/store import from HeterogeneousAgentCtr The controller previously pulled defaultProxySettings from @/const/store, which chain-loads @/modules/updater/configs and electron-is — that breaks any unit test that mocks `electron` without a full app shim. Make buildProxyEnv accept undefined and read the store value directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit ed64e2b)
The 200-char truncation is no longer needed as the caller already handles length limits. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> (cherry picked from commit 18042b7)
…obehub#13992) (cherry picked from commit eb99190)
(cherry picked from commit bacf422)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a951105cb3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (activeAgentId) { | ||
| this.#get().clearUnreadCompletedTopic(activeAgentId, id ?? null); | ||
| } |
There was a problem hiding this comment.
Clear unread topic badges across all agent buckets
switchTopic now clears unread state only for activeAgentId, but topic unread rendering is keyed by topic across all agent buckets (isTopicUnreadCompleted scans every set). If a topic ID is marked unread under a different agent bucket, opening that topic will not clear the badge and the unread indicator can remain stuck. This path should clear by topic globally (or use purgeUnreadTopics([id])) rather than only clearing the current agent bucket.
Useful? React with 👍 / 👎.
Summary
Routine weekly upstream sync from lobehub/lobehub@canary. All 25 cherry-picks applied without conflict. Buckets: bug fixes (9), desktop polish (5), chat-input (2), model-runtime (1), CC/hetero-agent (8).
Verification
Watch list for review
Test plan