β¨ style(thread): sync id allocation + ChatMiniMap polish#14000
Merged
Conversation
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>
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
π‘ Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16d73261f9
βΉοΈ 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".
Codecov Reportβ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## canary #14000 +/- ##
========================================
Coverage 66.81% 66.81%
========================================
Files 2100 2100
Lines 179129 179130 +1
Branches 21115 21898 +783
========================================
+ Hits 119677 119678 +1
Misses 59328 59328
Partials 124 124
Flags with carried forward coverage won't be shown. Click here to find out more.
π New features to boost your workflow:
|
β¦.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>
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 16d7326 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>
- 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>
β¦er in render Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
4 tasks
arvinxx
added a commit
that referenced
this pull request
Apr 20, 2026
When CC emits a `Task` tool_use, the executor now sync-allocates a threadId and creates a Thread for the subagent, routing subsequent subagent inner tool_uses (tagged with `parentToolCallId` by the adapter) into that thread instead of the main assistant's tools[]. The main assistant's tools[] stays clean β it only carries the outer Task tool_use β while subagent inner tool messages persist under the Thread (via `threadId`) and stay invisible to the main topic timeline. A downstream MessageTransformer join (next slice) will surface them as an inline `TaskBlock` on the parent assistant's content. - `ThreadService.createThread` helper calls the sync-id-allocation path shipped in #14000. - `generateThreadId` mirrors the server's `idGenerator('threads', 16)` shape (`thd_<16 chars>`) so caller-provided ids match the schema pattern, avoiding surprise if a debug dump mixes sources. - `persistNewToolCalls` splits fresh tools into mainTools / subagentTools: Phase 1 (pre-register assistant.tools[]) and Phase 3 (backfill result_msg_id) run for mainTools only. Phase 1b creates the Thread for each CC Task tool_use β guarded on `context.topicId` (required for Thread creation; missing means this isn't a topic-scoped run and the Task falls back to normal tool rendering). Phase 2 writes tool messages for both groups, attaching `threadId` to subagent writes. Orphaned subagent events (parent Task never registered) warn + drop instead of silently 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. - Exposes `TaskArgs` / `SkillArgs` / `ToolSearchArgs` from the claude-code builtin-tool package index so the executor can type `JSON.parse(tool.arguments)` as `TaskArgs` (for title + subagent_type). - Adds `@lobechat/builtin-tool-claude-code` to the root workspace deps list (already transitively present via builtin-tools, but the root's tsgo resolution needs it declared explicitly). 5 new tests cover thread creation on Task, threadId propagation onto subagent tool messages, main assistant.tools[] isolation, orphan drop + warn, and topicId-missing fallback. Refs LOBE-7319, LOBE-7392 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arvinxx
added a commit
that referenced
this pull request
Apr 20, 2026
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>
arvinxx
added a commit
that referenced
this pull request
Apr 21, 2026
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>
arvinxx
added a commit
that referenced
this pull request
Apr 21, 2026
* β¨ 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 042e48c.
* β»οΈ 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>
github-actions Bot
added a commit
to techflowasia/verve-lobe-client
that referenced
this pull request
May 18, 2026
β¦4001) * β¨ 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 lobehub#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 042e48c. * β»οΈ 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>
github-actions Bot
added a commit
to techflowasia/verve-lobe-client
that referenced
this pull request
May 18, 2026
β¦4001) * β¨ 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 lobehub#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 042e48c. * β»οΈ 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Backend + type foundation for LOBE-7392 β render Claude Code subagent (Agent / Task) spawns as an inline
taskblock inside the parent assistantGroup, replacing therole: 'task'ChatItem approach from #13928.This PR ships the server-side thread-router changes and inert type foundations only. The CC adapter implementation (
parent_tool_use_idrouting, subagent lineage) is split to #14001 so the backend here can be deployed before any client starts emitting the new adapter payload field.What changed
Thread router β synchronous id allocation (required by the follow-up adapter)
CreateThreadParams.id+ zod schema field + router passthrough β lets clients allocatethreadIdsynchronously before the create mutation resolves. The CC adapter (β¨ feat(heterogeneous-agent): support CC subagent renderingΒ #14001) emitsTasktool_use synchronously while the create call is async; having the id up-front lets us persist subagent inner messages with the rightthreadIdwithout queueing or blocking the stream.CONFLICTTRPC error instead of leaking the raw DB unique-violation.Foundation types (inert β no consumer in this PR; wired in #14001 and later)
TaskBlock+AssistantContentBlock.tasks?β derived inline view the MessageTransformer will populate by joining Threads onto the parent message's tool_use entries. No DB persistence, no schema migration.ThreadMetadata.sourceToolCallId+subagentTypeβ disambiguates parallel subagents that share asourceMessageId.ClaudeCodeApiName.Task+TaskArgsβ the CC tool_use shape (description,prompt,subagent_type).UI polish (bundled on this branch, unrelated to CC Task)
ChatMiniMapβ user-message peek preview on hover.TodoWriteβ chip-style detail row in inspector, plain header in render.Why no migration
block.tasks[]is a derived view computed at read time from thethreadstable; nothing new gets persisted.ThreadMetadatais already ajsonbcolumn, so the new fields don't need a schema change. Each assistant message has at most a handful of subagent threads, and queries go through the topic-id-indexed scope, so no new index either.Test plan
bunx vitest run packages/database/src/models/__tests__/thread.test.tsβ 16/16 pass (id passthrough + id-collision CONFLICT coverage, existing flows not regressed)π€ Generated with Claude Code