From 98ee858f7f0b734839fe9564eeeb23fd720d97ce Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 7 Jun 2026 23:41:32 +0100 Subject: [PATCH] Keep completed Assistant tool output inspectable --- .../v6/internal/subsystems/ai-runtime.md | 10 +++-- .../components/AI/Chat/ToolExecutionBlock.tsx | 20 ++++++--- .../__tests__/ToolExecutionBlock.test.tsx | 43 ++++++++++++------- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 34bf652db..2d79f4722 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -65,10 +65,12 @@ updates running tool metadata from the execution context in `packages/opencode/src/session/tools.ts` lines 53-66, and suppresses completed successful tool detail when `showDetails` is off in `packages/tui/src/routes/session/index.tsx` lines 1704-1779. Pulse adapts that -by compacting only successful completed tool rows while the same Assistant turn -is still streaming and newer concrete activity has arrived. Pending tools, -skipped/canceled tools, failed tools, and completed-turn tool details must -remain visible/inspectable. +by keeping successful completed tool rows command/status visible, compacting +them while the same Assistant turn is still streaming and newer concrete +activity has arrived, and keeping successful raw output behind the details +disclosure by default once the turn completes. Pending tools, skipped/canceled +tools, failed tools, and completed-turn tool details must remain +visible/inspectable; failed output may render inline because it is actionable. Assistant live workflow status follows OpenCode's current-state timeline model: history may persist for audit/session state, but live transcript rows diff --git a/frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx b/frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx index 35f42f0d8..5ec3832cd 100644 --- a/frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx +++ b/frontend-modern/src/components/AI/Chat/ToolExecutionBlock.tsx @@ -344,11 +344,17 @@ export const ToolExecutionBlock: Component = (props) => return preview; }); const outputPreview = createMemo(() => formatOutputPreview(outputText())); + const showInlineOutputPreview = createMemo( + () => props.tool.success === false && outputPreview().length > 0, + ); const hiddenOutputSummary = createMemo(() => - outputPreview() ? '' : formatHiddenOutputSummary(outputText()), + showInlineOutputPreview() ? '' : formatHiddenOutputSummary(outputText()), + ); + const hiddenOutputBadgeSummary = createMemo(() => + settlingFastCompletion() ? '' : hiddenOutputSummary(), ); const hiddenOutputBadgeLabel = createMemo(() => - hiddenOutputSummary() ? 'output available' : '', + hiddenOutputBadgeSummary() ? 'output available' : '', ); const hasInput = createMemo(() => detailInputText().trim().length > 0); const hasOutput = createMemo(() => hasReadableToolOutput(outputText())); @@ -441,11 +447,11 @@ export const ToolExecutionBlock: Component = (props) => {durationLabel()} - + {hiddenOutputBadgeLabel()} @@ -527,11 +533,11 @@ export const ToolExecutionBlock: Component = (props) => aria-hidden="true" /> - + {hiddenOutputBadgeLabel()} @@ -544,7 +550,7 @@ export const ToolExecutionBlock: Component = (props) => - +
 {
     expect(runningRow).toHaveTextContent('uptime');
     expect(screen.getByLabelText('running')).toBeInTheDocument();
     expect(screen.queryByLabelText(/Tool duration/)).not.toBeInTheDocument();
+    expect(screen.queryByLabelText(/Tool output available/)).not.toBeInTheDocument();
 
     await vi.advanceTimersByTimeAsync(FAST_TOOL_SETTLE_TEST_MS);
 
@@ -204,6 +205,9 @@ describe('ToolExecutionBlock', () => {
     expect(row).toHaveTextContent('uptime');
     expect(row).toHaveTextContent('<1s');
     expect(screen.getByLabelText('completed')).toBeInTheDocument();
+    expect(screen.getByLabelText('Tool output available: 10 chars output')).toHaveTextContent(
+      'output available',
+    );
     expect(screen.queryByLabelText('Assistant tool running')).not.toBeInTheDocument();
   });
 
@@ -319,7 +323,10 @@ describe('ToolExecutionBlock', () => {
 
     expect(screen.getByText('Inspect devices on current resource')).toBeInTheDocument();
     expect(screen.getByLabelText('Tool command')).toHaveTextContent('$ ls /dev | wc -l');
-    expect(screen.getByLabelText('Tool output preview')).toHaveTextContent('42');
+    expect(screen.queryByLabelText('Tool output preview')).not.toBeInTheDocument();
+    expect(screen.getByLabelText('Tool output available: 2 chars output')).toHaveTextContent(
+      'output available',
+    );
     expect(screen.queryByText(/"target_host"/)).not.toBeInTheDocument();
   });
 
@@ -484,13 +491,15 @@ describe('ToolExecutionBlock', () => {
 
   // --- Output display ---
 
-  it('previews successful short plain-text output while keeping full details available', () => {
+  it('keeps successful short plain-text output behind details by default', () => {
     render(() => );
-    expect(screen.getByLabelText('Tool output preview')).toHaveTextContent('hello world');
-    expect(screen.queryByText('11 chars output')).not.toBeInTheDocument();
+    expect(screen.queryByLabelText('Tool output preview')).not.toBeInTheDocument();
+    expect(screen.getByLabelText('Tool output available: 11 chars output')).toHaveTextContent(
+      'output available',
+    );
     fireEvent.click(getToolDetailsTrigger());
     expect(screen.getByText('Output')).toBeInTheDocument();
-    expect(screen.getAllByText('hello world')).toHaveLength(2);
+    expect(screen.getByText('hello world')).toBeInTheDocument();
   });
 
   it('does not preview structured JSON output by default', () => {
@@ -511,7 +520,7 @@ describe('ToolExecutionBlock', () => {
     expect(getToolDetailsTrigger()).toBeInTheDocument();
   });
 
-  it('previews successful plain-text output with a bounded visible snippet', () => {
+  it('keeps successful multi-line plain-text output behind details by default', () => {
     const { container } = render(() => (
        {
     ));
 
     const text = container.textContent || '';
-    expect(screen.getByLabelText('Tool output preview')).toHaveTextContent(
-      'line 1 line 2 line 3 line 4 ...',
+    expect(screen.queryByLabelText('Tool output preview')).not.toBeInTheDocument();
+    expect(screen.getByLabelText('Tool output available: 5 lines output')).toHaveTextContent(
+      'output available',
     );
+    expect(text).not.toContain('line 1');
     expect(text).not.toContain('line 5');
-    expect(screen.queryByText('5 lines output')).not.toBeInTheDocument();
     expect(getToolDetailsTrigger()).toBeInTheDocument();
   });
 
-  it('previews long successful single-line output without showing raw output counts', () => {
+  it('keeps long successful single-line output behind details by default', () => {
     render(() => );
 
-    const preview = screen.getByLabelText('Tool output preview');
-    expect(preview).toHaveTextContent(/\.\.\.$/);
-    expect(preview.textContent || '').not.toHaveLength(160);
-    expect(screen.queryByText('160 chars output')).not.toBeInTheDocument();
+    expect(screen.queryByLabelText('Tool output preview')).not.toBeInTheDocument();
+    expect(screen.getByLabelText('Tool output available: 160 chars output')).toHaveTextContent(
+      'output available',
+    );
   });
 
   it('previews failed plain-text output while keeping full details available', () => {
@@ -562,7 +572,10 @@ describe('ToolExecutionBlock', () => {
 
     expect(screen.getByText('Inspect devices on current resource')).toBeInTheDocument();
     expect(screen.getByLabelText('Tool command')).toHaveTextContent('$ ls /dev | wc -l');
-    expect(screen.getByLabelText('Tool output preview')).toHaveTextContent('42');
+    expect(screen.queryByLabelText('Tool output preview')).not.toBeInTheDocument();
+    expect(screen.getByLabelText('Tool output available: 2 chars output')).toHaveTextContent(
+      'output available',
+    );
   });
 
   it('hides output that is only whitespace', () => {