From 069879620f128ac66fdae3593ec4d6a5072e88d2 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Mon, 8 Jun 2026 12:09:47 +0200 Subject: [PATCH] attribute sidechain prose/thinking to the agent, not claude (T-265); close T-267 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A sub-agent's prose rendered as "claude" and its thinking as "thinking", identical to the main assistant, because the label logic ignored isSidechain — presenting sub-agent output as if the main Claude said it. Now a sidechain AssistantTextMessage is labelled "agent" with a muted stripe (never the coral claudeAccent brand), and sidechain thinking is "agent thinking". Main-thread items are unchanged. Tests: sidechain prose/thinking relabel, main-thread unchanged; golden contrasting the muted agent stripe with the coral claude stripe. This completes epic T-267 (conversation rendering streamlining): fold success result (T-262), fold agent prompt (T-263), nest the agent run (T-264), the shared holder primitive (T-266), and this attribution fix. Co-Authored-By: Claude Opus 4.8 (1M context) --- .pql/changelog/ticket_history/2026-06.sql | 3 ++ .pql/changelog/tickets/2026-06.sql | 37 ++++++++++++++++++ CHANGELOG.md | 4 ++ lib/builtin/claude/src/conversation_view.dart | 9 +++-- .../claude/conversation_view_test.dart | 26 ++++++++++++ .../conversation_card_goldens_test.dart | 33 ++++++++++++++++ .../linux/conversation_card_attribution.png | Bin 0 -> 15402 bytes 7 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 test/goldens/goldens/linux/conversation_card_attribution.png diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index cb8c6364..6c2e2431 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -819,3 +819,6 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-266', 'status', 'in_progress', 'done', NULL, '2026-06-08 09:04:12', '2026-06-08 09:04:12', '2026-06-08 09:04:12', NULL, 'fecc7bc78e18d5dd3c613d6583371fc3', 1) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 09:07:02', '2026-06-08 09:07:02', '2026-06-08 09:07:02', NULL, 'd82d792cf5ef8e77c581bea27f2cd8e3', 1) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-264', 'status', 'in_progress', 'done', NULL, '2026-06-08 09:25:50', '2026-06-08 09:25:50', '2026-06-08 09:25:50', NULL, '274e5d99e4db192a6b4cf6902f12df45', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'status', 'backlog', 'in_progress', NULL, '2026-06-08 09:27:49', '2026-06-08 09:27:49', '2026-06-08 09:27:49', NULL, '60970c3937a5ecf1c99960aa0f3f40d8', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'status', 'in_progress', 'done', NULL, '2026-06-08 10:09:14', '2026-06-08 10:09:14', '2026-06-08 10:09:14', NULL, 'e737c0595ca3bb11cd6dbf3f1b4a914c', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'status', 'in_progress', 'done', NULL, '2026-06-08 10:09:21', '2026-06-08 10:09:21', '2026-06-08 10:09:21', NULL, '4e89e3629e224a95f2a3edf0ed4317c4', 1) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index fcd5b0b2..a9815270 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -1972,3 +1972,40 @@ Scope: Depends on T-263 (parentUuid parsing + Agent card as container). Pairs with the sidechain-prose relabel ticket. Note the holder-card restyle + auto-scroll race is a separate design discussion (see the holder-card ticket). Refs: D-78. Builds on T-263. Related: T-262, T-230 (activity card grouping).', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:25', '2026-06-08 09:25:50', NULL, 'e3afb1ab7a0530c0b8dbd5775cc3803d', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'story', 'T-267', 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. + +Current code (lib/builtin/claude/src/conversation_view.dart _ConversationTurn build ~L221): AssistantTextMessage → ConversationCard(accent: claudeAccent, label: "claude"); AssistantThinkingMessage (~L227) → label "thinking". Neither inspects ConversationItem.isSidechain (transcript_reader.dart ~L46), which is present but unused. + +Design: when isSidechain is true, attribute the assistant items to the AGENT rather than the main Claude — e.g. label "agent" (or the agent/sub-agent name where resolvable) with de-emphasised styling, NOT the coral "claude" brand accent (claudeAccent is the main-thread brand, reserved for the real Claude). Thinking inside a sidechain attributed/muted likewise. + +Scope: +- Branch the AssistantTextMessage / AssistantThinkingMessage rendering on isSidechain. +- Pick the sidechain label + styling (distinct from "claude"; reuse muted tokens, not claudeAccent). +- Keep behavior identical for main-thread items. + +Tests: relabel of sidechain assistant prose/thinking (never "claude"/coral); main-thread unchanged; golden for an attributed agent prose card. + +Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'in_progress', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-08 09:27:49', NULL, '450360f9eec6f076e6db381633ce253c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-265', 'story', 'T-267', 'Relabel sidechain assistant prose/thinking — attribute to agent, not claude', 'Mirror of T-263 on the OUTPUT side. A sub-agent runs as a sidechain; its assistant prose renders with the label "claude" and its thinking with "thinking", identically to the MAIN assistant — because the label logic does not check isSidechain. So a sub-agents output is presented as if the main Claude said it. Same misattribution T-263 fixes for the prompt (the "you" problem), mirrored on the assistant side. + +Current code (lib/builtin/claude/src/conversation_view.dart _ConversationTurn build ~L221): AssistantTextMessage → ConversationCard(accent: claudeAccent, label: "claude"); AssistantThinkingMessage (~L227) → label "thinking". Neither inspects ConversationItem.isSidechain (transcript_reader.dart ~L46), which is present but unused. + +Design: when isSidechain is true, attribute the assistant items to the AGENT rather than the main Claude — e.g. label "agent" (or the agent/sub-agent name where resolvable) with de-emphasised styling, NOT the coral "claude" brand accent (claudeAccent is the main-thread brand, reserved for the real Claude). Thinking inside a sidechain attributed/muted likewise. + +Scope: +- Branch the AssistantTextMessage / AssistantThinkingMessage rendering on isSidechain. +- Pick the sidechain label + styling (distinct from "claude"; reuse muted tokens, not claudeAccent). +- Keep behavior identical for main-thread items. + +Tests: relabel of sidechain assistant prose/thinking (never "claude"/coral); main-thread unchanged; golden for an attributed agent prose card. + +Pairs with the nest-the-agent-run ticket (attribution + containment) and T-263 (prompt side). Refs: D-78.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:40:33', '2026-06-08 10:09:14', NULL, 'cfed081ba171d068e3ab6a2016c9c6d4', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-267', 'epic', NULL, 'Conversation rendering streamlining — fold, attribute, contain', 'Home for the cohesive set of work that streamlines how the Claude conversation log renders, so a heavy agent turn reads clearly and nothing is mislabeled or duplicated. Three recurring moves bind these tickets: (1) FOLD a redundant standalone item into its owning card (success result → tool card; agent prompt → agent card); (2) fix MISLEADING ATTRIBUTION (sub-agent prompt shown as "you"; sub-agent prose shown as "claude"); (3) settle the CONTAINER / interaction model (holder card that contains sub-cards, nested agent run, autoscroll-vs-reach-the-control). + +Common substrate across the children: the flat ConversationItem list and its renderers in lib/builtin/claude/src/conversation_view.dart, the pure grouping pass in activity_cluster.dart, the ConversationCard template in conversation_card.dart, and the so-far-unused ConversationItem.isSidechain / parentUuid transcript fields (transcript_reader.dart). + +Children: T-262 (merge tool-call + success result into one card with header status check), T-263 (fold sub-agent prompt into the Agent card; relabel you → agent prompt), T-264 (nest the whole sub-agent run under its Agent card via parentUuid), T-265 (relabel sidechain assistant prose/thinking — agent, not claude), T-266 (restyle activity/holder card as a container of sub-cards + fix the collapse-control scroll race). Sequencing: T-262/T-263 first, T-264/T-265 build the sidechain story, T-266 settles the shared container model last. + +Refs: D-78 (interaction zone / display-only conversation widgets). Built on T-168 (per-tool body rendering) and T-230 (activity card). + +SEQUENCING UPDATE (after T-266 refinement): the shared container/holder primitive now lives in T-266 and is CONSUMED by T-264 (nested agent run), so T-266 is no longer "last" — its primitive lands before/with T-264. T-264 is now blocked by T-266. Revised order: T-262 / T-263 (fold success result, fold agent prompt) → T-266 (shared holder/container primitive + activity-card restyle) → T-264 (nest the whole agent run on that primitive) → T-265 (relabel sidechain prose) can land anytime alongside.', 'done', 'medium', NULL, NULL, 'D-78', '2026-06-07 08:42:53', '2026-06-08 10:09:21', NULL, 'bb82ba1e08ca8009613521e59e548e77', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb4bffb..48fadfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ### Changed +- A **sub-agent's prose and thinking** are now attributed to the **`agent`** (a + muted stripe), not the main-thread coral **`claude`** — so a sub-agent's + output is no longer presented as if the main Claude said it. Main-thread items + are unchanged. (T-265) - A **sub-agent's whole run** — its prose, thinking, and tool calls — now nests in an **`agent run` holder under its Agent card** instead of spilling loose into the main thread, so you can tell where the sub-agent's work begins and diff --git a/lib/builtin/claude/src/conversation_view.dart b/lib/builtin/claude/src/conversation_view.dart index 8de230f8..b3240440 100644 --- a/lib/builtin/claude/src/conversation_view.dart +++ b/lib/builtin/claude/src/conversation_view.dart @@ -354,16 +354,19 @@ class _ConversationTurn extends StatelessWidget { copyText: i.text, body: ClideMarkdown(i.text), ), + // Sub-agent (sidechain) prose is NOT the main Claude — attribute it to the + // agent with a muted accent, never the coral "claude" brand (T-265). The + // coral claudeAccent is reserved for the real main-thread Claude. AssistantTextMessage() => ConversationCard( - accent: claudeAccent, - label: 'claude', + accent: i.isSidechain ? tokens.globalTextMuted : claudeAccent, + label: i.isSidechain ? 'agent' : 'claude', copyText: i.text, body: ClideMarkdown(i.text), ), AssistantThinkingMessage() => ConversationCard( variant: ConversationCardVariant.bare, accent: tokens.globalTextMuted, - label: 'thinking', + label: i.isSidechain ? 'agent thinking' : 'thinking', copyText: i.thinking, collapsible: true, collapsedByDefault: true, diff --git a/test/builtin/claude/conversation_view_test.dart b/test/builtin/claude/conversation_view_test.dart index 6a5c7250..0378d2d0 100644 --- a/test/builtin/claude/conversation_view_test.dart +++ b/test/builtin/claude/conversation_view_test.dart @@ -393,6 +393,32 @@ void main() { expect(find.bySemanticsLabel('agent run, 2 steps, collapsed'), findsNothing); }); + testWidgets('sidechain assistant prose is attributed to "agent", not "claude" (T-265)', (tester) async { + // An orphan sidechain prose (no resolvable Agent) renders inline, still + // attributed to the agent — never the main-thread coral "claude". + await pumpWith(tester, [ + AssistantTextMessage(uuid: 's', timestamp: _t, isSidechain: true, text: 'sub-agent says hi'), + ]); + expect(find.text('agent'), findsOneWidget); + expect(find.text('claude'), findsNothing); + }); + + testWidgets('sidechain thinking is attributed to "agent thinking" (T-265)', (tester) async { + await pumpWith(tester, [ + AssistantThinkingMessage(uuid: 's', timestamp: _t, isSidechain: true, thinking: 'hmm let me think'), + ]); + expect(find.text('agent thinking'), findsOneWidget); + expect(find.text('thinking'), findsNothing); + }); + + testWidgets('main-thread prose + thinking keep "claude"/"thinking" (T-265 unchanged)', (tester) async { + await pumpWith(tester, [_asst('main says hi'), _think('main thought')]); + expect(find.text('claude'), findsOneWidget); + expect(find.text('thinking'), findsOneWidget); + expect(find.text('agent'), findsNothing); + expect(find.text('agent thinking'), findsNothing); + }); + testWidgets('a permission-prompted tool-use is hidden but its result is kept', (tester) async { await pumpWith( tester, diff --git a/test/goldens/conversation_card_goldens_test.dart b/test/goldens/conversation_card_goldens_test.dart index e8e53539..fa15e088 100644 --- a/test/goldens/conversation_card_goldens_test.dart +++ b/test/goldens/conversation_card_goldens_test.dart @@ -1,5 +1,6 @@ import 'package:alchemist/alchemist.dart'; import 'package:clide/builtin/claude/src/conversation_card.dart'; +import 'package:clide/builtin/claude/src/conversation_view.dart' show claudeAccent; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,6 +13,38 @@ void main() { setUp(() async => f = await KernelFixture.create()); tearDown(() async => f.dispose()); + goldenTest( + 'ConversationCard attribution (T-265): agent prose is muted, claude prose is coral', + fileName: 'conversation_card_attribution', + builder: () => GoldenTestGroup( + columns: 1, + children: [ + GoldenTestScenario( + name: 'claude (main thread) — coral brand stripe', + child: _wrap( + f, + const ConversationCard( + accent: claudeAccent, + label: 'claude', + body: Text('Here is the main-thread answer.', textDirection: TextDirection.ltr), + ), + ), + ), + GoldenTestScenario( + name: 'agent (sidechain) — muted stripe, not coral', + child: _wrap( + f, + ConversationCard( + accent: const Color(0xFF8B8B8B), + label: 'agent', + body: const Text('Here is the sub-agent answer.', textDirection: TextDirection.ltr), + ), + ), + ), + ], + ), + ); + goldenTest( 'ConversationCard merged tool card (T-262): status mark + folded result segment', fileName: 'conversation_card_merged', diff --git a/test/goldens/goldens/linux/conversation_card_attribution.png b/test/goldens/goldens/linux/conversation_card_attribution.png new file mode 100644 index 0000000000000000000000000000000000000000..8824f556407ca7b345ec87e94bd83c95698bfca2 GIT binary patch literal 15402 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 literal 0 HcmV?d00001