mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Keep completed Assistant tool output inspectable
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -344,11 +344,17 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (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<ToolExecutionBlockProps> = (props) =>
|
||||
{durationLabel()}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={hiddenOutputSummary()}>
|
||||
<Show when={hiddenOutputBadgeSummary()}>
|
||||
<span
|
||||
class="shrink-0 rounded border border-border-subtle bg-surface px-1.5 py-0.5 text-[9px] font-medium"
|
||||
title="Open the completed turn to inspect tool output"
|
||||
aria-label={`Tool output available: ${hiddenOutputSummary()}`}
|
||||
aria-label={`Tool output available: ${hiddenOutputBadgeSummary()}`}
|
||||
>
|
||||
{hiddenOutputBadgeLabel()}
|
||||
</span>
|
||||
@@ -527,11 +533,11 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Show>
|
||||
<Show when={hiddenOutputSummary()}>
|
||||
<Show when={hiddenOutputBadgeSummary()}>
|
||||
<span
|
||||
class="shrink-0 rounded border border-border-subtle bg-surface-alt px-1.5 py-0.5 text-[9px] font-medium text-muted"
|
||||
title="Open tool details to inspect output"
|
||||
aria-label={`Tool output available: ${hiddenOutputSummary()}`}
|
||||
aria-label={`Tool output available: ${hiddenOutputBadgeSummary()}`}
|
||||
>
|
||||
{hiddenOutputBadgeLabel()}
|
||||
</span>
|
||||
@@ -544,7 +550,7 @@ export const ToolExecutionBlock: Component<ToolExecutionBlockProps> = (props) =>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={outputPreview()}>
|
||||
<Show when={showInlineOutputPreview()}>
|
||||
<pre
|
||||
class="border-t border-border-subtle bg-surface-alt px-3 py-2 font-mono text-[11px] leading-5 text-base-content whitespace-pre-wrap break-words"
|
||||
aria-label="Tool output preview"
|
||||
|
||||
@@ -195,6 +195,7 @@ describe('ToolExecutionBlock', () => {
|
||||
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(() => <ToolExecutionBlock tool={makeTool({ output: 'hello world' })} />);
|
||||
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(() => (
|
||||
<ToolExecutionBlock
|
||||
tool={makeTool({
|
||||
@@ -521,21 +530,22 @@ describe('ToolExecutionBlock', () => {
|
||||
));
|
||||
|
||||
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(() => <ToolExecutionBlock tool={makeTool({ output: 'x'.repeat(160) })} />);
|
||||
|
||||
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', () => {
|
||||
|
||||
Reference in New Issue
Block a user