hide prompted tool-use payloads from the conversation log
A permission-gated tool or AskUserQuestion already surfaces as a prompt in the composer zone, so its raw tool-use card was redundant noise. The session now tracks which tool_use_ids surfaced as a prompt; the conversation view hides those tool-use cards. AskUserQuestion also hides its result (the chosen answer is logged separately); permission-tool results are kept — that's the useful outcome. The pane rebuilds the view on each prompt change so the payload vanishes the moment its prompt appears. T-176, T-177, D-78. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -267,37 +267,41 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
child: ClideText(_error!, muted: true),
|
||||
);
|
||||
} else if (_conversation != null) {
|
||||
body = Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ConversationView(
|
||||
controller: _conversation!,
|
||||
emptyState: ClaudeBanner(
|
||||
role: widget.isPrimary ? 'primary' : 'session ${widget.secondaryIndex}',
|
||||
workspace: _repoRoot,
|
||||
statusLine: _statusLine,
|
||||
// Rebuild conversation + composer zone together on each prompt change so
|
||||
// the view hides a prompted tool-use card the moment its prompt appears
|
||||
// (D-78), and the composer zone swaps to the prompt UI.
|
||||
body = StreamBuilder<ToolPrompt?>(
|
||||
stream: _session?.pendingPromptStream,
|
||||
initialData: _session?.pendingPrompt,
|
||||
builder: (context, snap) {
|
||||
final prompt = snap.data;
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ConversationView(
|
||||
controller: _conversation!,
|
||||
hiddenToolUseIds: _session?.promptedToolUseIds ?? const <String>{},
|
||||
emptyState: ClaudeBanner(
|
||||
role: widget.isPrimary ? 'primary' : 'session ${widget.secondaryIndex}',
|
||||
workspace: _repoRoot,
|
||||
statusLine: _statusLine,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// The composer zone: an open prompt (permission / AskUserQuestion)
|
||||
// takes this space and hides the text input until it's answered, so
|
||||
// interaction stays out of the conversation stream (D-78).
|
||||
StreamBuilder<ToolPrompt?>(
|
||||
stream: _session?.pendingPromptStream,
|
||||
initialData: _session?.pendingPrompt,
|
||||
builder: (context, snap) {
|
||||
final prompt = snap.data;
|
||||
if (prompt != null && _session != null) {
|
||||
return ToolPromptCard(prompt: prompt, onResolve: _session!.resolvePrompt);
|
||||
}
|
||||
return ClaudeComposer(
|
||||
enabled: _session != null,
|
||||
onSubmit: _send,
|
||||
pasteResolver: () => resolveClipboardAttachment(const NativeClipboard()),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
// An open prompt takes the composer's space and hides the text
|
||||
// input until it's answered, so interaction stays out of the
|
||||
// conversation stream (D-78).
|
||||
if (prompt != null && _session != null)
|
||||
ToolPromptCard(prompt: prompt, onResolve: _session!.resolvePrompt)
|
||||
else
|
||||
ClaudeComposer(
|
||||
enabled: _session != null,
|
||||
onSubmit: _send,
|
||||
pasteResolver: () => resolveClipboardAttachment(const NativeClipboard()),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
body = const Center(child: ClideText('starting…', muted: true));
|
||||
|
||||
@@ -25,10 +25,16 @@ class ConversationView extends StatefulWidget {
|
||||
required this.controller,
|
||||
this.wrapInSelectionArea = true,
|
||||
this.emptyState,
|
||||
this.hiddenToolUseIds = const <String>{},
|
||||
});
|
||||
|
||||
final ConversationController controller;
|
||||
|
||||
/// tool_use_ids whose raw tool-use card should be hidden because the call
|
||||
/// surfaced as a prompt (permission / AskUserQuestion) — D-78. The result is
|
||||
/// still shown (it's the useful answer); only the request payload is hidden.
|
||||
final Set<String> hiddenToolUseIds;
|
||||
|
||||
/// Whether to wrap the list in its own [ClideSelectionArea]. The team
|
||||
/// grid sets this false and wraps all tiles in one shared area so
|
||||
/// selection spans tiles — nesting SelectionAreas is illegal (T-140).
|
||||
@@ -67,6 +73,28 @@ class _ConversationViewState extends State<ConversationView> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Hide tool-use payloads that surfaced as a prompt (D-78): AskUserQuestion
|
||||
/// (its tool-use *and* result echo are noise — the prompt + the logged answer
|
||||
/// cover it), and any permission-prompted tool-use (keep its result — that's
|
||||
/// the useful answer).
|
||||
List<ConversationItem> _visibleItems(List<ConversationItem> items) {
|
||||
final hidden = widget.hiddenToolUseIds;
|
||||
final auqIds = {
|
||||
for (final it in items)
|
||||
if (it is AssistantToolUse && it.name == 'AskUserQuestion') it.toolUseId,
|
||||
};
|
||||
bool drop(ConversationItem it) {
|
||||
if (it is AssistantToolUse) return it.name == 'AskUserQuestion' || hidden.contains(it.toolUseId);
|
||||
if (it is ToolResultMessage) return auqIds.contains(it.toolUseId); // AUQ result only; keep permission results
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
for (final it in items)
|
||||
if (!drop(it)) it,
|
||||
];
|
||||
}
|
||||
|
||||
void _onChanged() {
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
@@ -81,7 +109,7 @@ class _ConversationViewState extends State<ConversationView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
final items = widget.controller.items;
|
||||
final items = _visibleItems(widget.controller.items);
|
||||
|
||||
if (items.isEmpty) {
|
||||
return ColoredBox(
|
||||
|
||||
@@ -173,6 +173,13 @@ class StreamJsonSession {
|
||||
final _queue = <ToolPrompt>[];
|
||||
final _pendingCtl = StreamController<ToolPrompt?>.broadcast();
|
||||
|
||||
/// tool_use_ids that surfaced as a prompt — the view hides their raw
|
||||
/// tool-use card (it showed as a prompt) but keeps the result (D-78).
|
||||
final _promptedToolUses = <String>{};
|
||||
|
||||
/// Read-only view of [_promptedToolUses] for the conversation view.
|
||||
Set<String> get promptedToolUseIds => _promptedToolUses;
|
||||
|
||||
/// The prompt currently awaiting a decision (queue head), or null.
|
||||
ToolPrompt? get pendingPrompt => _queue.isEmpty ? null : _queue.first;
|
||||
|
||||
@@ -228,6 +235,8 @@ class StreamJsonSession {
|
||||
if (request['subtype'] == 'can_use_tool') {
|
||||
final toolName = request['tool_name'] as String? ?? '';
|
||||
final input = (request['input'] as Map?)?.cast<String, dynamic>() ?? <String, dynamic>{};
|
||||
final tuid = request['tool_use_id'] as String? ?? '';
|
||||
if (tuid.isNotEmpty) _promptedToolUses.add(tuid);
|
||||
_queue.add(ToolPrompt(
|
||||
promptId: rid,
|
||||
toolName: toolName,
|
||||
|
||||
Reference in New Issue
Block a user