Commit graph

10031 commits

Author SHA1 Message Date
Innei
c154dc5692
Merge remote-tracking branch 'origin/canary' into innei/dev/agent-workspace
Some checks are pending
E2E CI / Check Duplicate Run (push) Waiting to run
E2E CI / Test Web App (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
# Conflicts:
#	src/routes/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx
#	src/routes/(main)/agent/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts
#	src/routes/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx
#	src/store/global/actions/general.ts
2026-04-22 01:24:03 +08:00
Innei
8a1a0dafdf
feat: enhance agent document management with LiteXML operations
- Updated API names for clarity, changing 'patchDocument' to 'modifyNodes'.
- Introduced LiteXML operation schema for document modifications.
- Implemented new mutation for modifying document nodes via LiteXML.
- Enhanced document retrieval methods to support format options (XML, Markdown, Both).
- Added support for editor data snapshots and normalization of diff nodes.
- Improved document history management to handle editor data with diff nodes.
- Created tests for new features and ensured existing functionality remains intact.

Signed-off-by: Innei <tukon479@gmail.com>
2026-04-22 01:16:36 +08:00
Innei
1334ffafaa
Implement Active Topic Document and Disabled Tool Call Filtering
- Introduced ActiveTopicDocumentContextInjector to inject context for active topic documents into user messages.
- Added DisabledToolCallFilter to remove historical tool calls for disabled tools in the current runtime scope.
- Updated MessagesEngine to utilize the new context injectors and filters.
- Enhanced tests to verify the correct injection of active topic document context and filtering of disabled tool calls.

This update improves the handling of document editing contexts and tool management in the conversation flow.

Signed-off-by: Innei <tukon479@gmail.com>
2026-04-21 23:12:04 +08:00
Arvin Xu
f3fca500e4
🐛 fix(heterogeneous-agents): stream subagent Thread + fix parallel-tool orphan (#14024)
Some checks are pending
E2E CI / Check Duplicate Run (push) Waiting to run
E2E CI / Test Web App (push) Blocked by required conditions
Release Desktop Canary / Calculate Canary Version (push) Waiting to run
Release Desktop Canary / Code quality check (push) Blocked by required conditions
Release Desktop Canary / Build Desktop App (push) Blocked by required conditions
Release Desktop Canary / Merge macOS Release Files (push) Blocked by required conditions
Release Desktop Canary / Publish Canary Release (push) Blocked by required conditions
Release Desktop Canary / Publish to S3 (push) Blocked by required conditions
Release Desktop Canary / Cleanup Old Canary Releases (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
*  feat(heterogeneous-agents): stream subagent Thread + fix parallel-tool orphan

When a main-agent step emits a parallel tool_use (e.g. `[Grep, Agent]`),
the gateway handler's stream_chunk branch was forwarding the subagent's
inner `tools_calling` chunks onto `currentAssistantMessageId` (main),
overwriting main.tools[] with subagent tools — main's own Task/Agent
tool_use then had no matching entry and every tool message under it
rendered with the "orphan tool call" banner.

Two coordinated changes:

1. Main-bucket isolation: the executor now drops subagent-tagged
   `stream_chunk` events before forwarding to the gateway handler. DB
   persistence continues via `persistSubagent*Chunk` so the subagent
   content is never lost; only the main-handler in-memory dispatch is
   suppressed for subagent chunks.

2. Thread-bucket streaming: `internal_dispatchMessage` now accepts a
   `threadId` override that snaps scope to `thread`, routing
   create/update payloads to the thread's `messagesMap` bucket. Each
   `SubagentRunState` carries a thread-scoped dispatcher; ensureSubagentRun
   seeds user + assistant on lazy Thread creation and at turn boundaries,
   persistToolBatch gets an `onToolCreated` hook that the subagent path
   uses to seed role:'tool' rows, persistSubagent*Chunk dispatches
   tools[] / content / reasoning updates on every chunk, and the
   tool_result branch mirrors subagent tool_result content (+ pluginState)
   into the thread bucket. Thread view now streams token-by-token with
   the same cadence as the main bubble.

Tests:
- `does NOT forward subagent-tagged stream_chunks to the gateway handler`
  — asserts main bucket isolation under parallel main+subagent tool use.
- `streams subagent create/update dispatches into the thread messagesMap
  bucket` — asserts user/assistant/tool createMessage dispatches land in
  the thread scope, plus streaming updateMessage for tools[], content,
  and tool_result, with no bleed into the main bucket.

Local repro verified end-to-end: main assistant.tools=[Grep, Agent]
stays intact across two parallel runs, thread bucket populates 14 rows
(user + 2 subagent assistants with Bash/Glob then Read×8 + 10 tool
results) during the run, `mainOrphans`/`threadOrphans`/
`threadIntoMainBleed` all empty, orphan warning DOM count = 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ♻️ refactor(heterogeneous-agents): route subagent stream through a per-spawn sub-operation

Replace the threadId-override on `internal_dispatchMessage` with a
proper per-spawn child operation, eliminating the second context
expression at the dispatch boundary.

The previous design accepted `{ operationId, threadId? }` and snapped
scope to `'thread'` when the override was present. That was a leaky
parallel path to the operation registry — the same "which messagesMap
bucket should this dispatch hit?" question got answered two different
ways. `startOperation` already supports `parentOperationId` + context
inheritance + recursive cancel cascade, so the right move is to model
the subagent run as a first-class child op and let
`internal_getConversationContext` do its normal job.

Changes:
- Add `'subagentThread'` to `OperationType` (NOT in
  `AI_RUNTIME_OPERATION_TYPES` — it's a context container, not an
  independent loading state, so it shouldn't double-count for spinners).
- `executeHeterogeneousAgent` opens the sub-op in `beginSubagentRun`
  via `startOperation({ type: 'subagentThread', parentOperationId,
  context: { ...context, threadId, scope: 'thread' } })` and binds a
  thread-scoped dispatcher to that sub-op's id.
- `SubagentRunState.subOperationId` carries the id so `finalizeSubagentRun`
  can mark it completed when the spawn's tool_result arrives (or on the
  `onComplete` fallback for crash/abort paths). Cancel cascade + cleanup
  flow through the existing parent/child op linkage.
- Revert the `threadId` override in `internal_dispatchMessage` — the
  store boundary is back to a single context expression
  (`{ operationId? }`).

Test:
- Add `startOperation` mock to `createMockStore` (returns monotonic
  `sub-op-N` ids).
- Update the streaming regression to identify the sub-op via the
  `startOperation` call with `type: 'subagentThread'`, assert the
  sub-op's parent + context shape, filter Thread bucket dispatches by
  `ctx.operationId === subOperationId`, and verify
  `completeOperation(subOperationId)` fires when the run finalizes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(heterogeneous-agents): drain subagent buffers only after DB flush confirms

`finalizeSubagentRun`'s buffer reset used to run unconditionally after
the flush try/catch, so a transient `messageService.updateMessage`
failure silently wiped the accumulated streamed text/reasoning — the
later `onComplete` fallback then had nothing left to retry, leaving the
subagent's streamed content absent from persisted thread history.

Move the clear into the success branch. A second concern surfaces once
the clear moves: after the flush block, the `resultContent` branch
advances `currentAssistantMsgId` to the newly created terminal
assistant, so a naive retry that reads `currentAssistantMsgId` would
overwrite the authoritative terminal content with the leftover streamed
buffer — corrupting the subagent summary with stale partial text.

Pin the flush target via a new `SubagentRunState.pendingFlushTarget`:
captured before the DB attempt, carried on the run when the flush
fails, cleared alongside the buffers on success. The retry uses the
pinned target instead of the live `currentAssistantMsgId`, so leftover
streamed buffers always land on the streaming turn's assistant — never
on the terminal row.

Test: `retains subagent buffers + pinned target when the finalize flush
fails` stubs `updateMessage` to throw once for the subagent streaming
write, runs streamed text → spawn `tool_result` → `onComplete`, and
asserts (1) the leftover content eventually reaches DB across ≥2
write attempts and (2) every attempt targets the streaming turn's
assistant — not the terminal row created by `resultContent`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:18:30 +08:00
Innei
a992ad0460
Support current-topic agent documents 2026-04-21 22:00:35 +08:00
AmAzing-
6ddef95249
chore: fix follow-up chat input state during message queueing (#14020)
* 💄 style(chat-input): improve agent assignment placeholder

*  improve follow-up queue input ux

* 💄 sync runtime placeholder locale keys

* Update SKILL.md

* 💄 style(chat-input): hide send menu while generating

Co-Authored-By: Oz <oz-agent@warp.dev>

* fix: ensure sendMenu is correctly cleared in store when prop becomes undefined and add test coverage

---------

Co-authored-by: Oz <oz-agent@warp.dev>
2026-04-21 18:56:52 +08:00
Innei
88cdcc4f67
Add page-agent init gating and runtime diagnostics 2026-04-21 17:48:59 +08:00
Arvin Xu
b02b727261
feat(heterogeneous-agent): support CC subagent rendering (#14001)
*  feat(heterogeneous-agents): preserve CC subagent lineage in adapter

Restores the CC subagent-lineage adapter work that was held back from
#LOBE-7392 until the thread-router backend changes ship. This PR targets
the LOBE-7392 branch so the adapter diff stays isolated from the
thread/UI foundation — GitHub will auto-retarget to canary once
LOBE-7392 merges.

Original scope (unchanged from the held-back commits):
- ToolCallPayload.parentToolCallId carries parent tool_use id downstream
  so consumers can group subagent inner tools under their spawning
  parent.
- claudeCode.ts routes raw.parent_tool_use_id events through
  handleSubagentAssistant so the main-agent step tracker is not advanced
  on subagent message.id changes, usage is not double-counted, and
  subagent text / reasoning are dropped (their final answer flows back
  via the outer tool_result).
- emitToolChunk helper shared by main-agent and subagent paths so new
  suppress-rules live in one place.
- 6 subagent-lineage tests: lineage propagation, no newStep on
  subagent message.id change, no turn_metadata emission, text/reasoning
  drop, main-agent step boundary resumes after subagent, subagent
  tool_result passthrough.

Refs LOBE-7319, LOBE-7260

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 style(workflow-collapse): move expand toggle to action slot

Pass the fullscreen toggle as AccordionItem action so the built-in
chevron indicator (same as TopicList) sits inline with the title on
the left, with Maximize2/Minimize2 on the right.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(heterogeneous-agents): route CC Task tool_use to subagent Thread

When a main-agent tool_use spawns a subagent, the executor now sync-
allocates a threadId and creates a Thread, routing subsequent subagent
inner tool_uses (tagged with `parentToolCallId` by the adapter) into
that thread instead of the main assistant's tools[].

The "this tool_use spawns a subagent" decision lives entirely in the
adapter layer via a new `ToolCallPayload.subagentSpawn` descriptor
(`description`, `subagentType`). The CC adapter populates it on every
`Task` tool_use; when Codex (or any other CLI) grows a subtask concept,
its adapter populates the same field and the executor needs zero
changes. The executor never checks `identifier === 'claude-code'` or
`apiName === 'Task'` — it just reacts to the presence of
`subagentSpawn`.

- `ToolCallPayload.subagentSpawn?: { description?, subagentType? }`
  in `packages/heterogeneous-agents/src/types.ts` — adapter-agnostic
  spawn signal, paired with the existing `parentToolCallId` (which
  marks tool_uses BELONGING to a subagent). Together they cover both
  directions of the lineage.
- `claudeCode.ts` stamps `subagentSpawn` on main-agent `Task` tool_uses
  using the already-parsed `block.input` — no redundant JSON.parse.
- `ThreadService.createThread` helper wraps the sync-id TRPC mutation
  shipped in #14000. `generateThreadId()` mirrors the server's
  `idGenerator('threads', 16)` shape (`thd_<16 chars>`) so caller-
  provided ids match the schema pattern.
- `persistNewToolCalls` splits fresh tools into main/subagent groups:
  Phase 1 (pre-register assistant.tools[]) and Phase 3 (backfill
  result_msg_id) run for main tools only. A new Phase 1b creates the
  Thread per `subagentSpawn` — guarded on `context.topicId` (required
  for Thread creation; missing falls back to normal tool rendering).
  Phase 2 writes tool messages for both groups, attaching `threadId`
  to subagent writes. Orphaned subagent events (parent spawn never
  registered) warn + drop instead of leaking into the main timeline.
- `taskThreadMap` lives at executor scope (not on ToolPersistenceState
  which resets per step) so pathological orderings that straddle the
  main-agent step boundary can't lose the parent→thread mapping.

7 new tests: 2 adapter-level (subagentSpawn stamped on Task,
NOT stamped on Read) + 5 executor-level (Thread creation, threadId
propagation onto subagent tool messages, main assistant.tools[]
isolation, orphan drop + warn, topicId-missing fallback).

Refs LOBE-7319, LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(types): persist subagent lineage fields on ChatToolPayload schema

Add `parentToolCallId` and `subagentSpawn` as first-class optional
fields on `ChatToolPayload` + `ChatToolPayloadSchema`, so the adapter-
emitted lineage metadata survives the TRPC `update-message` gate
instead of being silently stripped by zod's default strip behavior.

Reviewer-flagged bug: `UpdateMessageParamsSchema.tools` runs each
payload through `ChatToolPayloadSchema`, which previously only
whitelisted `apiName / arguments / id / identifier / intervention /
result_msg_id / thoughtSignature / type`. Any adapter-level
extension (subagent spawn marker, parent-child pointer) was dropped
before it ever reached the `messages.tools` JSONB column, so lineage
only lived in transient stream events and vanished on the first
`tool_end → fetchAndReplaceMessages`. Downstream consumers that
wanted to key off `tool.subagentSpawn` to render a TaskBlock, or
follow `tool.parentToolCallId` to reconstruct the spawning parent,
had nothing to work with.

- `SubagentSpawnInfo` + `SubagentSpawnInfoSchema` defined in
  `packages/types/src/message/common/tools.ts` as the canonical
  shape. Structurally identical to the same-named type in
  `@lobechat/heterogeneous-agents` (which stays self-contained by
  design) — TypeScript structural typing handles the bridge.
- Both new fields are optional on the interface and the zod schema,
  so existing callers continue to parse unchanged.
- Jsonb column accepts any shape, so no DB migration — the only
  missing piece was the schema gate.

3 new regression tests next to the executor's subagent-thread-routing
suite, asserting `ChatToolPayloadSchema.parse()` preserves both
fields and the same fields survive through `UpdateMessageParamsSchema`
(the actual TRPC gate that was stripping them before).

Refs LOBE-7319

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Revert " feat(types): persist subagent lineage fields on ChatToolPayload schema"

This reverts commit 042e48c733.

* ♻️ refactor(heterogeneous-agents): lift subagent context to event-peer fields

`ToolCallPayload` is "one tool call" — it shouldn't carry stream-level
lineage (parent spawn id, subagent turn id). That info describes the
containing event/chunk and should live as a peer field on the event
`data`, not nested inside each payload.

Event model changes:
- New `SubagentEventContext` + `SubagentSpawnMetadata` types. Events
  originating from a subagent stream (CC Task, future Codex subtask,
  etc.) carry `data.subagent` as a peer field next to `toolsCalling`
  / `toolCallId`. Covers `stream_chunk` (tools_calling), `tool_start`,
  `tool_end`, and `tool_result`.
- `SubagentEventContext.spawnMetadata` appears ONLY on the first event
  for each new parent — lets the executor lazy-create the subagent
  Thread on first sight without needing to know CC-specific argument
  shapes or to re-parse `tool_use.input`. Subsequent events for the
  same parent carry just the lineage ids.
- `ToolCallPayload` is back to its minimal form (`apiName / arguments
  / id / identifier / type`). No `parentToolCallId`, no `subagentSpawn`
  — those were the wrong abstraction level; removing them also sidesteps
  the `ChatToolPayloadSchema` strip-on-persist issue (the fields never
  need to survive DB roundtrip because Thread container persistence
  expresses the lineage).

CC adapter (`claudeCode.ts`):
- `handleSubagentAssistant` emits tools through a shared `emitToolChunk`
  that stamps the `subagent` peer field on the chunk + each tool_start.
  The FIRST subagent chunk for a new parent gets `spawnMetadata` pulled
  from a new adapter-internal `taskArgsById` cache — description /
  prompt / subagentType — announced exactly once via `announcedSpawns`.
- `handleUser` stamps `subagent.parentToolCallId` on `tool_result` +
  `tool_end` when the user event carries `parent_tool_use_id`
  (CC's shape for subagent inner tool_results).
- Main-agent tool_use handling no longer stamps lineage on payloads.

Adapter tests updated — 4 rewrites in the subagent suite:
- assert chunk-level peer fields (not payload-nested lineage)
- assert `spawnMetadata` on first subagent event, absent on subsequent
- assert main-agent tool_uses don't get `subagent` context
- assert subagent `tool_result` + `tool_end` carry the peer

59 adapter tests pass (52 existing + 7 covering the new peer contract).

Refs LOBE-7319, LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(heterogeneous-agents): persist subagent runs as Thread containers

Subagents now materialize as a nested conversation inside a Thread,
shaped identically to the main topic:

    Thread
    ├─ user          (content = Task prompt, threadId=thread.id)
    ├─ assistant#1   (tools[] = subagent turn 1 tool_uses, threadId)
    ├─ tool          (parentId=assistant#1, threadId)
    ├─ assistant#2   (tools[] = subagent turn 2 tool_uses, threadId)
    └─ tool          (parentId=assistant#2, threadId)

Same schema as a main topic, just rooted at a Thread instead of a
Topic. No new persistence shape, no new renderer — the existing
`query({ threadId })` read path reconstructs the subagent's full
conversation when the UI expands the TaskBlock.

Executor changes:
- `ToolPersistenceState` shrinks to `{ payloads, persistedIds }` — the
  `tool_use.id → tool message DB id` map moves to executor scope as
  one global `toolMsgIdByCallId` shared across main + every subagent
  run. `tool_result` lookups don't care which scope created the row.
- `persistNewToolCalls` → renamed `persistToolBatch` and made scope-
  agnostic (takes an optional `threadId` + the global id map). Runs
  the same 3-phase flow (pre-register → create → backfill) whether
  target is main assistant or in-thread subagent assistant.
- New `persistSubagentToolChunk` handles the subagent path: reads the
  adapter's `SubagentEventContext` peer field off the chunk, lazy-
  creates the Thread + user message on the FIRST chunk for each
  parent (using `spawnMetadata`), opens a new in-thread assistant on
  `subagentMessageId` change (same shape as main-agent step
  boundary), then delegates to `persistToolBatch`.
- `SubagentRunState` tracks per-parent Thread id, current in-thread
  assistant, `currentSubagentMessageId`, chain parent, and its own
  `ToolPersistenceState`. Lives at executor scope so subagent events
  straddling a main-agent step boundary keep their mapping.
- Step-boundary parent lookup reads from `toolState.payloads` (not
  the global id map) so main-agent chain doesn't accidentally pick
  up a subagent tool's msg id as the step parent.
- Executor has NO CC-specific knowledge — it never checks
  `identifier`, `apiName`, or parses `tool_use.arguments`. All CC
  quirks live in the adapter; new CLIs (Codex subtask, ...) plug in
  by emitting the same `SubagentEventContext` peer.

Test rewrite — 6 tests under "CC subagent thread-container":
- Task tool_use alone does NOT create a Thread (lazy)
- First subagent event creates Thread + `role:'user'` seeded with
  the Task prompt + first in-thread `role:'assistant'`
- Subagent inner tools persist as `role:'tool'` messages with
  threadId set and parentId chained to the in-thread assistant
- `subagentMessageId` change opens a new in-thread assistant
- Main `assistant.tools[]` carries Task only; subagent inner tools
  appear on the in-thread assistant's `tools[]`
- Missing topicId gracefully skips Thread creation

25 executor tests pass (19 existing + 6 rewritten for new shape).

Refs LOBE-7319, LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(heterogeneous-agents): subagent prompt + closing summary in Thread view

Electron E2E surfaced two gaps in the Thread-container model shipped in
the previous commit:

1. **Subagent user-message content empty.** Real CC emits `Agent` as
   the spawn-tool name for general-purpose subagents (not only `Task`
   as the spec documents). My earlier `taskArgsById` cache keyed off
   `ClaudeCodeApiName.Task` only, so `spawnMetadata.prompt` was
   undefined when the user watched the actual app — the Thread's
   `role:'user'` message landed with empty content and the thread
   view looked like a tool call floating alone.

2. **No closing summary in the Thread.** The adapter dropped subagent
   text/reasoning per an earlier comment claiming the subagent's
   final answer arrives via the outer tool_result. That's true for
   the MAIN timeline (the outer spawn tool's result content = the
   subagent's summary), but the THREAD view is a standalone
   conversation — dropping the subagent's final text left it ending
   on a bare tool call with no assistant conclusion.

Adapter changes (`claudeCode.ts`):
- Rename `taskArgsById` → `mainToolInputsById` and cache EVERY
  main-agent tool_use input (not just `Task`). `emitToolChunk` looks
  up the parent's input by `parent_tool_use_id` on the first subagent
  event and extracts `description` / `prompt` / `subagent_type`
  defensively — any CC spawn-tool variant that shares this input
  shape (`Task`, `Agent`, future ones) gets spawn metadata for free.
- `handleSubagentAssistant` stops filtering `tool_use` only. Text
  and `thinking` blocks now emit as `stream_chunk` events with the
  `subagent` peer field attached — routed to the in-thread assistant,
  NOT the main assistant's accumulators.

Executor changes (`heterogeneousAgentExecutor.ts`):
- `SubagentRunState` gains `accumulatedContent` + `accumulatedReasoning`,
  mirroring main-agent content tracking.
- Extract `ensureSubagentRun` helper so text chunks and tool chunks
  share the Thread / user / assistant lifecycle logic. On turn
  boundary (`subagentMessageId` change), flush the prior turn's
  accumulated content before creating the next in-thread assistant —
  covers text-only turns that never hit `persistToolBatch`.
- New `persistSubagentTextChunk` accumulates text/reasoning onto the
  run; `persistToolBatch` writes content alongside tools[] so DB
  sees both in one update (same pattern as main agent).
- New `finalizeSubagentRun` flushes pending content when the main-
  agent receives the spawn tool's `tool_result` — ensures the
  closing summary lands before `fetchAndReplaceMessages` refreshes
  from stale DB state.
- `onComplete` iterates `subagentRuns.keys()` and flushes any
  un-finalized runs, covering the CLI-crashed-mid-subagent edge case.

Tests:
- Adapter: replaced the "drops subagent text" test with two tests
  asserting text/reasoning ARE emitted with correct `subagent` peer
  context. New test covers the `Agent` spawn-tool variant.
- Executor: 4 new tests cover the Thread user message content
  population, subagent text accumulation into the in-thread assistant,
  non-leakage into main assistant content, and tool_result-triggered
  finalization. Total 29 executor tests pass.

E2E verified via Electron + CDP: fresh CC session → `Agent`-based
subagent → Thread created with `title="Run pwd command"`,
`metadata.subagentType="general-purpose"`, `role:'user'` seeded with
the Task prompt, Bash tool_use + result inside the thread.

Refs LOBE-7319, LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(heterogeneous-agents): refresh thread list when subagent Thread is lazy-created

Earlier Electron E2E repro: a subagent Thread born mid-stream landed
in DB correctly, but the topic sidebar only picked it up after the
user manually navigated topics / called `refreshThreads()` — the
SWR cache for the thread list (`SWR_USE_FETCH_THREADS`) wasn't
invalidated, so the new Thread stayed invisible until the next
cold fetch.

- `ensureSubagentRun` now accepts an optional `onThreadCreated`
  callback fired once per lazy Thread create. Kept as a callback
  (not a direct `store.refreshThreads` call) so the executor
  persistence logic stays decoupled from the Zustand store shape.
- `persistSubagentToolChunk` + `persistSubagentTextChunk` thread
  the callback through to `ensureSubagentRun`.
- Executor defines `onSubagentThreadCreated` once at run scope and
  passes it into all three subagent persist call sites. Calls
  `get().refreshThreads()` fire-and-forget — it's a no-op when the
  user has navigated away from the topic, so no need to block
  persist on cache refresh.

Two regression tests:
- Subagent-spawning run → `refreshThreads` called exactly once
- Non-subagent run (plain tool only) → `refreshThreads` NOT called

Refs LOBE-7319, LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(builtin-tool-claude-code): specialize Agent subagent Inspector + Render

CC's subagent-spawn tool arrives as `tool_use.name: 'Agent'`, not `Task` —
rename the apiName so the Inspector/Render registry actually matches the
stream. Inspector switches icon/label by `subagent_type` (Explore / Plan /
general-purpose / statusline-setup), with `description` surfaced in a chip;
new Render shows `prompt` and tool_result as labelled Markdown blocks that
can't fit in the folded header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 style(workflow-collapse): unify expand toggle with ActionIcon

Replace the hand-rolled motion span + role="button" / keyboard-handler
expand toggle with a single @lobehub/ui ActionIcon — fewer a11y edge
cases to maintain and the icon/title/blockSize layout matches other
toolbar buttons in the group.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 style(builtin-tool-claude-code): inline-pad Edit diff container

Give the Edit render a small inline padding so the CodeDiff lines up
with the rest of the tool renders; zero-width flush-left was awkward
against the surrounding labelled blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(heterogeneous-agents): interpolate agent name in running indicator

ContentLoading now renders "{name} is running" / "{name} 运行中" for
heterogeneous agent execution — previously it collapsed to the generic
"External agent running" so a user watching a long CC run couldn't tell
which external CLI was working (mattered once Codex landed as a sibling
adapter).

- Share `HETEROGENEOUS_TYPE_LABELS` (claude-code / codex) out of the
  heterogeneous-agents package so all consumers read one map; home
  Sidebar AgentItem switches to it and drops its inline copy.
- `conversationLifecycle.startOperation` passes
  `metadata.heterogeneousType` on the heterogeneous-exec operation so
  ContentLoading can resolve the label from the running op without
  re-deriving the adapter type from session state.
- New `operation.heterogeneousAgentFallback` key covers the (rare) case
  where the metadata is absent — keeps the dot loader labelled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(claude-code): CC subagent Thread rendering pipeline

Closes the viewing loop for CC subagent runs: the main-topic Agent tool
row now links into the spawned Thread, the Thread's Portal view renders
with provenance + read-only affordances, and the sidebar surfaces which
entries are subagent-produced.

UX:
- Agent render gains a trailing "View / Collapse full subagent
  conversation" toggle. It looks up the Thread by
  `metadata.sourceToolCallId === toolCallId` and calls
  openThreadInPortal / closeThreadPortal — hidden until the executor
  lazy-creates the Thread on the first subagent event, so it never
  renders as a no-op.
- Portal Thread Header shows a `[icon] subagentType` Tag next to the
  title ("Explore" / "General purpose" / ...). Inspector's folded row
  already exposes the same detail, so the icon + label stays
  consistent across the two surfaces.
- Portal Thread Chat flips into read-only mode when
  `metadata.sourceToolCallId` is set: ChatInput is hidden (the
  external CLI owns the session — new turns have nowhere to go),
  `disableEditing` propagates to every message (no double-click to
  edit, no user action bar), and `useThreadActionsBarConfig` wipes
  `bar` + `menu` across assistant / assistantGroup / user roles.
- Sidebar ThreadItem on both /agent and /group routes renders a plain
  "Subagent" badge next to the title when
  `metadata.subagentType` is present. The type detail deliberately
  lives on the Thread Header, not here — sidebar space is tight.

Shared resolver:
- `CC_SUBAGENT_TYPES` + `resolveCCSubagentType` move out of the
  Inspector into `packages/builtin-tool-claude-code/src/client/
  subagentTypes.ts` and re-export from the `/client` entry. Inspector
  + Portal Thread Header both consume it, so the icon/label stay in
  sync. Kept UI-level (LucideIcon | FC) rather than pushed into
  heterogeneous-agents, which is a pure-data package.
- Root package.json adds a direct dep on
  `@lobechat/builtin-tool-claude-code` so Portal Thread Header can
  import from `/client` (previously only transitive via builtin-tools).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  test(workflow-collapse): mock @lobehub/ui ActionIcon + AccordionItem action slot

After the expand-toggle refactor to ActionIcon + the `action` prop on
AccordionItem, the test's module mocks were missing both: ActionIcon
wasn't exported from the @lobehub/ui mock, and AccordionItem dropped
`action` on the floor so the toggle never made it into the rendered
DOM. Restore both — ActionIcon renders as a real \`button\` with
aria-label so \`getByRole('button', { name })\` can still target it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:48:16 +08:00
Arvin Xu
c0db58e622
feat(topic): add completed status with dropdown action and filter (#14005)
Some checks are pending
E2E CI / Check Duplicate Run (push) Waiting to run
E2E CI / Test Web App (push) Blocked by required conditions
Release Desktop Canary / Calculate Canary Version (push) Waiting to run
Release Desktop Canary / Code quality check (push) Blocked by required conditions
Release Desktop Canary / Build Desktop App (push) Blocked by required conditions
Release Desktop Canary / Merge macOS Release Files (push) Blocked by required conditions
Release Desktop Canary / Publish Canary Release (push) Blocked by required conditions
Release Desktop Canary / Publish to S3 (push) Blocked by required conditions
Release Desktop Canary / Cleanup Old Canary Releases (push) Blocked by required conditions
Release ModelBank / Build ModelBank (push) Waiting to run
Release ModelBank / Publish ModelBank (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
*  feat(topic): add completed status with dropdown action and filter

- Surface ChatTopicStatus (active/completed/archived) on topic list items and pass to dropdown menu
- Add markTopicCompleted / unmarkTopicCompleted store actions wired into the topic item dropdown
- Show CheckCircle2 icon on completed topics in the sidebar list
- Add topicIncludeCompleted user preference (default false) and an "Include Completed" toggle in the topic filter menu (agent + group routes)
- Wire excludeStatuses and triggers filters through TopicModel, TRPC router, service, and store SWR keys so completed topics are excluded by default

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🌐 i18n(topic): add zh-CN/en-US for completed status keys

Translate actions.markCompleted / actions.unmarkCompleted and filter.filter / filter.showCompleted for dev preview. CI's pnpm i18n will fill in remaining locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ♻️ refactor(topic): scope completed exclusion to routes with the toggle

Move the topicIncludeCompleted preference read out of the chat-store useFetchTopics action and into the (main) agent/group sidebars where the "Include Completed" filter actually lives. Popup and mobile topic views call useFetchTopics without excludeStatuses, so completed topics remain reachable on surfaces that don't expose the toggle (e.g. the popup window for a deep-linked completed topic, the mobile TopicModal).

Also switch ChatTopicStatus imports in the topic item / dropdown files to @lobechat/types to match the rest of the topic-feature imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  test(topic-model): cover excludeStatuses + triggers filters

Add cases to the TopicModel.query suite for the new params introduced alongside the topic.status column:
- triggers (positive trigger filter) on the container branch
- excludeStatuses on the container, agent, and groupId branches (verifies null status rows are still returned)
- status / completedAt are populated on returned items

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 style(topic): move "Mark Completed" to top of agent topic dropdown

Promote the completed-status toggle to the first menu item, with a divider before favorite, so the most-used status action sits at the top of the dropdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:37:09 +08:00
YuTengjing
61224fe76c
🐛 fix(auth): return 401 for expired OIDC JWT instead of 500 (#14014) 2026-04-21 16:43:57 +08:00
Innei
62dc91e444
Use topic titles for auto-created page documents
Some checks are pending
E2E CI / Check Duplicate Run (push) Waiting to run
E2E CI / Test Web App (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
2026-04-21 16:30:44 +08:00
Innei
8119789849
🐛 fix(model-bank): add repository metadata for provenance (#14018) 2026-04-21 15:59:55 +08:00
Innei
1ffd01a9eb
🐛 fix(model-bank): publish initial npm package publicly (#14017) 2026-04-21 15:50:28 +08:00
Innei
9d3696ceef
👷 build(model-bank): automate npm release (#14015) 2026-04-21 15:38:04 +08:00
LiJian
595193ce62
🐛 fix: clarify lobe-gtd and lobe-cron tool descriptions to prevent routing confusion (#14013)
When users say "daily task" or "routine", the model confused lobe-gtd (one-time todos) with lobe-cron (recurring automation), often falling back to user-memory or GTD instead of cron.

Fixes LOBE-7486

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 15:30:45 +08:00
LiJian
665b482390
🐛 fix: inject timezone and cron jobs list into cron tool system prompt (#14012)
* 🐛 fix: inject timezone and cron jobs list into cron tool system prompt

Add {{timezone}} to cron systemRole session_context so the model knows
the user's local timezone when creating scheduled tasks. Wire up the
{{CRON_JOBS_LIST}} placeholder that was already referenced in the
systemRole but never populated — now fetches the agent's existing cron
jobs via tRPC and injects them, following the same pattern as CREDS_LIST.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 🐛 fix: limit cron jobs context to 4 items to save context window

Only inject a preview of up to 4 cron jobs into the system prompt.
When there are more, append a hint directing the model to call
listCronJobs API for the full list. This avoids bloating the context
window for agents with many scheduled tasks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 15:25:55 +08:00
LiJian
ca47d972a4
🐛 fix: fallback to skill activation when activateTools cannot find identifier (#14010)
* 🐛 fix: fallback to skill activation when activateTools cannot find identifier

When an LLM calls activateTools with a skill identifier (e.g. "lobehub"),
the tool lookup fails with "Not found" because skills and tools are separate
registries. Now activateTools falls back to activateSkill for identifiers
not found as tools, so skills can be activated regardless of which API the
LLM chooses to call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* 🐛 fix: fallback to skill activation when activateTools cannot find identifier

When an LLM calls activateTools with a skill identifier (e.g. "lobehub"),
the tool lookup fails because skills and tools are separate registries.

Two changes:
1. ActivatorExecutionRuntime.activateTools() now falls back to activateSkill
   for identifiers not found as tools
2. selectActivatedSkillsFromMessages() now also extracts skills from
   activateTools messages (pluginState.activatedSkills[]), so downstream
   stepContext and execScript zip resolution work correctly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 14:04:58 +08:00
YuTengjing
c5db823a69
💄 style: add Kimi K2.6 to LobeHub-hosted card (#14006) 2026-04-21 11:40:15 +08:00
Arvin Xu
518358b95e
💄 style(todo-progress): vertically center collapsed header row (#13996)
Clear residual list-container margin/border when collapsed and slightly
increase bottom padding so the header sits on the bar's visual center.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 10:02:37 +08:00
sxjeru
a15d962ae8
💄 style: add new Kimi K2.6 model (#14004)
*  feat(models): update AI models with new capabilities and pricing adjustments

*  feat(aiModels): add new AI models Kimi K2.6 and GLM-5.1 to ollamaCloud; enhance siliconCloud with Qwen3.6 35B A3B and update pricing and settings
2026-04-21 10:02:26 +08:00
Arvin Xu
569dcc8765
💄 style(thread): sync id allocation + ChatMiniMap polish (#14000)
Some checks are pending
E2E CI / Test Web App (push) Blocked by required conditions
Release Desktop Canary / Calculate Canary Version (push) Waiting to run
Release Desktop Canary / Code quality check (push) Blocked by required conditions
Release Desktop Canary / Build Desktop App (push) Blocked by required conditions
Release Desktop Canary / Merge macOS Release Files (push) Blocked by required conditions
Release Desktop Canary / Publish Canary Release (push) Blocked by required conditions
E2E CI / Check Duplicate Run (push) Waiting to run
Release Desktop Canary / Publish to S3 (push) Blocked by required conditions
Release Desktop Canary / Cleanup Old Canary Releases (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
*  feat(heterogeneous-agents): preserve CC subagent lineage in adapter

Claude Code tags subagent events (Agent / Task tool spawns) with
parent_tool_use_id pointing back at the outer tool_use. The adapter
used to flatten these, breaking the main-agent step tracker — each
subagent turn introduces a NEW message.id, which the adapter read as
"new main-agent step" and forced stream_end + stream_start(newStep),
producing orphan assistant bubbles and double-counted usage.

- ToolCallPayload.parentToolCallId carries the pointer to downstream
  consumers so they can group subagent inner tools under their parent.
- claudeCode.ts reads raw.parent_tool_use_id and:
  * skips main-agent step boundary on subagent message.id changes
  * skips model tracking for subagent events (the result event has
    the authoritative usage, would double-count otherwise)
  * drops subagent text / reasoning in this adapter pass — the
    subagent's final answer is delivered via the outer tool_result;
    verified against a real CC trace where 76 subagent assistant
    events carried only tool_use, zero text / thinking
  * stamps parentToolCallId onto subagent tool_use payloads
- 6 new unit tests cover lineage propagation, no newStep for subagent
  message.id changes, no turn_metadata emission, text/reasoning drop,
  main-agent resuming step boundary, and subagent tool_result
  passthrough.

Refs LOBE-7319, LOBE-7260

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  feat(types): foundation types for CC Task block (LOBE-7392)

Sets up the data shape for rendering CC subagent spawns as inline
`task` blocks inside the parent assistantGroup, replacing the
role:'task' message intermediary that was previously proposed in
PR #13928. Pure data layer — no DB schema migration, no new
columns.

- TaskBlock + AssistantContentBlock.tasks?: derived view that the
  MessageTransformer will populate by joining Threads onto the
  parent message's tool_use entries (follow-up commit). Carries
  threadId, subagentType, description, status — enough for the
  folded inline header without re-fetching the thread on every
  render pass.
- ThreadMetadata gains sourceToolCallId, subagentType, description.
  sourceToolCallId disambiguates parallel subagents that share a
  sourceMessageId (one assistant turn can spawn multiple Task
  tool_uses in one batch).
- CreateThreadParams.id + zod schema field + thread router
  passthrough lets clients allocate the threadId synchronously
  before the create mutation resolves. The CC adapter emits
  Task tool_use synchronously while the create call is async, so
  having the id up-front lets us persist subagent inner messages
  with the right threadId without a queue or blocking the stream.
- ClaudeCodeApiName.Task + TaskArgs match the CC tool_use shape
  (description, prompt, subagent_type) so executor / renderer can
  type the input safely.

Refs LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ♻️ refactor: extract subagent assistant handler + drop ThreadMetadata.description

Two review-feedback cleanups on the LOBE-7392 foundation:

1. **Adapter — early-return + shared helper.** The main-agent path no
   longer carries `if (!isSubagentEvent)` guards; subagent events short-
   circuit into a dedicated `handleSubagentAssistant` that only extracts
   `tool_use` blocks, and both paths share a new `emitToolChunk` helper
   for the `tools_calling` + `tool_start` emission. Adding a new
   subagent suppress-rule (no model / no text / no step) now lives in
   one method instead of sprinkling guards across the main handler.

2. **ThreadMetadata — drop `description`, use `Thread.title`.** Thread
   already has a `title` column; storing the CC Task `description`
   input there is the canonical spot and removes the redundant metadata
   field. `TaskBlock.description` is collapsed into `TaskBlock.title`
   (single source), and the MessageTransformer will populate it from
   `thread.title` at read time. Also adds `status?: ThreadStatus` on
   `TaskBlock` so the renderer gets the processing / completed / failed
   state without a separate lookup.

Behavior unchanged — all 56 adapter tests still pass.

Refs LOBE-7392, LOBE-7319

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(thread-router): translate id-collision into CONFLICT error

ThreadModel.create uses onConflictDoNothing() and returns undefined
when a caller-provided id collides with an existing row. With the
new client-side id passthrough (introduced in 16d73261f9 to let the
CC subagent executor allocate threadId synchronously), the original
router would silently insert a follow-up message with
threadId: undefined and return { threadId: undefined } — a data-
integrity regression flagged in PR review.

Translates the model's undefined return into TRPCError(CONFLICT) at
the router boundary so callers see an explicit error and can
regenerate their id and retry. The model layer is untouched —
onConflictDoNothing remains the right primitive for server-generated
ids where collisions are unreachable; the new validation only
applies when the router is the entry point.

- ensureThreadCreated helper extracted; both createThread and
  createThreadWithMessage routes funnel through it
- New thread model tests document the conflict behavior and
  caller-provided id passthrough that the router relies on (16/16
  pass)

Refs LOBE-7392

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 feat(chat-minimap): user-message peek with in-place hover preview

- Filter ticks to user messages; fall back to last user when viewport is on assistant reply
- Replace per-tick popovers with one in-place panel that crossfades from rail center
- Drop arrow nav buttons (hover panel makes them redundant)
- Smooth sqrt width curve (5–16px) so short messages cluster naturally

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 💄 style(claude-code-todo): chip-style detail in inspector, plain header in render

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

*  revert(heterogeneous-agents): pull CC adapter subagent-lineage changes

The CC subagent-lineage adapter work (parent_tool_use_id routing,
parentToolCallId on ToolCallPayload, dedicated handleSubagentAssistant /
emitToolChunk helpers, 6 subagent tests) would ship before the thread
backend changes in this PR are deployed — online flows would see the new
payload field with no server to receive it.

Holding this PR to thread-router + foundation types only. The adapter
work is preserved on feat/lobe-7392-cc-adapter-followup and will ship
as a separate PR after this one is deployed.

Refs LOBE-7392, LOBE-7319

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 01:27:01 +08:00
Arvin Xu
b4aa51baaa
🐛 fix: hetero-agent ToolSearch content + bot IM reply + titlebar polish (#13998)
Some checks are pending
E2E CI / Check Duplicate Run (push) Waiting to run
E2E CI / Test Web App (push) Blocked by required conditions
Release Desktop Canary / Calculate Canary Version (push) Waiting to run
Release Desktop Canary / Code quality check (push) Blocked by required conditions
Release Desktop Canary / Build Desktop App (push) Blocked by required conditions
Release Desktop Canary / Merge macOS Release Files (push) Blocked by required conditions
Release Desktop Canary / Publish Canary Release (push) Blocked by required conditions
Release Desktop Canary / Publish to S3 (push) Blocked by required conditions
Release Desktop Canary / Cleanup Old Canary Releases (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
* 💄 style(electron): use colorBgElevated for active title-bar tab

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🔒 fix(bot): show operation id instead of raw error in IM failure reply

Replace the error message content in bot-facing failure replies with the
operation id so end users don't see raw runtime errors; errors are still
logged server-side for debugging and correlation via operation id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* 🐛 fix(hetero-agent): extract tool_name from ToolSearch tool_reference blocks

CC CLI returns ToolSearch results as `tool_reference` content blocks with
only a `tool_name` field — no `text`/`content` — so the generic array
mapper collapsed every entry to '' and persisted empty content, keeping
the UI tool StatusIndicator stuck on the spinner (LOBE-7369).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 23:11:34 +08:00
Arvin Xu
16df8350fe
🐛 fix(user-panel): remove consecutive dividers in user panel menu (#13990)
When businessMenuItems (from cloud deployment) returns items that
include a trailing divider, and getDesktopApp prepends its own divider,
two dividers appear back-to-back between Credits and Get Desktop App.

Add a post-filter on mainItems that strips any consecutive divider,
regardless of which module injected them.
2026-04-20 22:29:24 +08:00
Innei
a08b3e396f
feat(agent-page): bind documentId to URL and introduce HeaderSlot
Some checks are pending
E2E CI / Check Duplicate Run (push) Waiting to run
E2E CI / Test Web App (push) Blocked by required conditions
Test CI / Check Duplicate Run (push) Waiting to run
Test CI / Test Packages (push) Blocked by required conditions
Test CI / Test App (shard 1/3) (push) Blocked by required conditions
Test CI / Test App (shard 2/3) (push) Blocked by required conditions
Test CI / Test App (shard 3/3) (push) Blocked by required conditions
Test CI / Merge and Upload App Coverage (push) Blocked by required conditions
Test CI / Test Desktop App (push) Blocked by required conditions
Test CI / Test Database (push) Blocked by required conditions
- Add nested /agent/:aid/:topicId/page/:docId route with PageRedirect for bare /page
- Introduce useAutoCreateTopicDocument with module-level inflight de-dup
- Lift Portal + WorkingSidebar to (chat) layout; keep ChatHeader in left column
- Sidebar document clicks on page route navigate to /page/:docId instead of opening Portal
- Add HeaderSlot (context + createPortal) as a reusable header injection point
- Mount AutoSaveHint via HeaderSlot; register Files hotkey scope in TopicCanvas so Cmd+S triggers manual save
- Sync desktopRouter.config.tsx and desktopRouter.config.desktop.tsx
- Extend RecentlyViewed plugin to round-trip optional docId segment
2026-04-20 22:28:00 +08:00
Innei
a59a9c4943
feat(onboarding): structured hunk ops for updateDocument (#13989)
*  feat(onboarding): structured hunk ops for updateDocument

Extend `updateDocument` (and the underlying `@lobechat/markdown-patch`) with
explicit hunk modes so agents can unambiguously express deletes and inserts
instead of encoding them as clever search/replace pairs.

Modes: `replace` (default, backward-compatible), `delete`, `deleteLines`,
`insertAt`, `replaceLines`. Line-based modes use 1-based inclusive ranges
and are applied after content-based hunks, sorted by anchor line descending
so earlier lines stay stable. New error codes: `LINE_OUT_OF_RANGE`,
`INVALID_LINE_RANGE`, `LINE_OVERLAP`.

Onboarding document injection now prefixes each line with its 1-based number
(cat -n style) so the agent can cite line numbers when issuing line-based
hunks. Tool description, system role, and per-phase action hints updated to
teach the new shape.

* 🐛 fix(onboarding): align patchOnboardingDocument zod schema with structured hunks

The tRPC input schema still accepted only the legacy `{search, replace}` shape,
so agent calls using the new `insertAt`/`delete`/`deleteLines`/`replaceLines`
hunk modes were rejected before reaching `applyMarkdownPatch`. Switch to a
z.union matching MarkdownPatchHunk.

* 🐛 fix(markdown-patch): validate line ranges before overlap detection

Previously the overlap loop ran before per-hunk range validation, so an
invalid range (e.g. startLine=0 or endLine<startLine) combined with another
line hunk would be misreported as LINE_OVERLAP instead of the real
LINE_OUT_OF_RANGE / INVALID_LINE_RANGE. Validate each line hunk against the
baseline line count first, then run overlap detection on valid ranges only.
2026-04-20 21:17:28 +08:00
Innei
a939962fa1
feat(env): add Kimi Coding Plan API environment variables (#13997)
*  feat(env): add Kimi Coding Plan API environment variables

Made-with: Cursor

* 📝 docs(env): document Kimi Coding Plan env vars in .env.example
2026-04-20 21:06:40 +08:00
Arvin Xu
bb59b7391e
🚀 release: sync main branch to canary (#13995)
Automatic sync from main to canary. Merge conflicts detected.

**Resolution steps:**
```bash
git fetch origin
git checkout sync/main-to-canary-20260420-24659236264
git merge origin/main
# Resolve conflicts
git add -A && git commit
git push
```

> Do NOT merge canary into a main-based branch — always merge main INTO
the canary-based branch to keep a clean commit graph.
2026-04-20 20:03:28 +08:00
Arvin Xu
038070285a resolve merge conflicts 2026-04-20 17:41:43 +08:00
lobehubbot
a0303b7c18 chore: merge main into canary (has conflicts to resolve) 2026-04-20 09:34:54 +00:00
Arvin Xu
3bcd581e7c
👷 build(database): add topic status and tasks automation mode (#13994) 2026-04-20 17:34:13 +08:00
Tsuki
bacf422890
🐛 fix: remove desktop tracker legacy imports (#13993) 2026-04-20 15:39:12 +08:00
YuTengjing
eb99190f9f
feat(chat-input): gate prompt optimize by image output capability (#13992) 2026-04-20 15:04:12 +08:00
LiJian
18042b7d31
🐛 fix: remove systemRole truncation in getAgentDetail (#13988)
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>
2026-04-20 14:26:16 +08:00
Tsuki
5dd7cd7408
feat: add x ads tracking entry points (#13986)
*  feat: add x ads tracking entry points

* 🔨 chore: bump analytics to v1.6.2

* 🐛 fix: add auth analytics provider entry
2026-04-20 14:12:14 +08:00
Arvin Xu
ed64e2b8af
feat(electron): add Cmd+W/Cmd+T tab shortcuts with misc desktop polish (#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>
2026-04-20 12:38:54 +08:00
Arvin Xu
e7236c0169
🐛 fix(user): validate avatar URL and scope old-avatar deletion to owner (#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>
2026-04-20 09:58:14 +08:00
YuTengjing
fb471123fc
feat: support model alias mapping for image and video runtimes (#13896) 2026-04-20 09:38:56 +08:00
Arvin Xu
a0471d5906
feat(chat-input): branch ahead/behind badge + GitCtr refactor (#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 #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>
2026-04-20 02:11:43 +08:00
Innei
3bd7f1f146
🐛 fix(electron): align TabBar left padding with NavPanel width on initial load (#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.
2026-04-20 01:46:05 +08:00
Innei
730169e6b6
feat(electron): add + button to TabBar for new topic in active context (#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.
2026-04-20 01:04:51 +08:00
Innei
6b6915d147
feat(onboarding): add preset agent naming suggestions (#13931)
*  feat(onboarding): add preset agent naming suggestions

* 🐛 fix(test): align AgentDocumentsGroup test assertions with title-first rendering

#13940 changed DocumentItem to prefer title over filename, but the
AgentDocumentsGroup tests from #13924 were still asserting on filename
strings. Update all text matchers to use titles (Brief / Example).
2026-04-20 00:54:11 +08:00
Rdmclin2
0213656565
🐛 fix: message gateway (#13979)
* 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
2026-04-20 00:17:57 +08:00
Arvin Xu
8240e8685d
🐛 fix(desktop): repo-type detection for submodule/worktree + chat & sidebar polish (#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>
2026-04-19 23:56:39 +08:00
Arvin Xu
46df77ac3f
💄 style(tab-bar): blend inactive tabs with titlebar, show close icon by default (#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>
2026-04-19 21:53:22 +08:00
Arvin Xu
6ca5fc4bdc
feat(hetero-agent): Claude Code runtime, cwd, and sidebar polish (#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>
2026-04-19 20:33:11 +08:00
Arvin Xu
77fd0f13f0
🐛 fix(hetero-agent): persist streamed text alongside tool writes; collapse workflow summary (#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

#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>
2026-04-19 17:13:46 +08:00
Arvin Xu
ccbb75da06
♻️ refactor(hetero-agent): persist per-step usage to each step assistant message (#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>
2026-04-19 16:19:18 +08:00
Innei
7c3071d590
feat: add TopicCanvas and TitleSection components for topic management
- Introduced TopicCanvas component to serve as a document canvas for topics, integrating an editor and title section.
- Added TitleSection component for managing topic titles and emojis, enhancing user interaction with a dedicated UI.
- Updated FloatingChatPanel to accommodate the new TopicCanvas, ensuring a cohesive layout in the topic page.
- Enhanced tests to verify the integration of TopicCanvas within the topic page route.

Signed-off-by: Innei <tukon479@gmail.com>
2026-04-19 12:31:11 +08:00
Innei
1d22a36820
fix: update @lobehub/ui to version 5.9.1 and refactor FloatingChatPanel to use FloatingSheet component
- Updated the @lobehub/ui dependency in package.json to version 5.9.1.
- Refactored FloatingChatPanel to utilize the new FloatingSheet component, enhancing its layout and state management.
- Introduced a new ChatLayout component for better organization of chat-related UI elements.
- Adjusted routing configuration to incorporate the new ChatLayout for agent chat pages.

Signed-off-by: Innei <tukon479@gmail.com>
2026-04-19 02:31:03 +08:00
Innei
2711aa9191
feat(desktop): add dedicated topic popup window with cross-window sync (#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>
2026-04-19 02:15:29 +08:00