Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/expand-running-bash-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Show the command in running Bash tool cards and allow expanding it with Ctrl+O before the result arrives.
14 changes: 14 additions & 0 deletions apps/kimi-code/src/tui/components/messages/tool-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,20 @@ export class ToolCallComponent extends Container {
for (const line of lines) {
this.addChild(new Text(line, 2, 0));
}
} else if (name === 'Bash' && this.result === undefined) {
// While a long-running Bash call is in-flight (args finalized, no result
// yet), surface its command in the body so the user can see what is
// running and expand it with ctrl+o. Once the result lands, buildContent's
// shellExecutionResultRenderer takes over command rendering.
const command = str(this.toolCall.args['command']);
if (command.length === 0) return;
this.addChild(
new ShellExecutionComponent({
command,
showCommand: true,
commandPreviewLines: this.expanded ? undefined : COMMAND_PREVIEW_LINES,
Comment thread
liruifengv marked this conversation as resolved.
}),
);
}
}

Expand Down
44 changes: 44 additions & 0 deletions apps/kimi-code/test/tui/components/messages/tool-call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,50 @@ describe('ToolCallComponent', () => {
expect(out).not.toContain('streamed-only');
});

describe('in-flight Bash command preview (args finalized, no result yet)', () => {
const longCommand = Array.from({ length: 15 }, (_, i) => `echo step${String(i + 1)}`).join(
'\n',
);

it('shows the truncated command while running and reveals the rest when expanded', () => {
const component = new ToolCallComponent(
{ id: 'call_bash_running', name: 'Bash', args: { command: longCommand } },
undefined,
);

const collapsed = strip(component.render(100).join('\n'));
expect(collapsed).toContain('Using Bash');
expect(collapsed).toContain('echo step1');
expect(collapsed).toContain('echo step10');
expect(collapsed).not.toContain('echo step11');

component.setExpanded(true);

const expanded = strip(component.render(100).join('\n'));
expect(expanded).toContain('echo step11');
expect(expanded).toContain('echo step15');
});

it('yields command rendering to the result renderer once the result lands', () => {
const component = new ToolCallComponent(
{ id: 'call_bash_done', name: 'Bash', args: { command: longCommand } },
undefined,
);

// Sanity: while running, the in-flight preview shows the command.
expect(strip(component.render(100).join('\n'))).toContain('$ echo step1');

component.setResult({ tool_call_id: 'call_bash_done', output: 'done', is_error: false });

// Collapsed result view delegates to shellExecutionResultRenderer, which
// hides the command — so the in-flight buildCallPreview preview must be
// gone, otherwise the command would render twice when expanded.
const out = strip(component.render(100).join('\n'));
expect(out).toContain('Used Bash');
expect(out).not.toContain('$ echo step1');
});
});

it('hides tool output bodies that start with a <system tag', () => {
const reminderOutput =
'<system-reminder>\nThe task tools have not been used recently.\n</system-reminder>';
Expand Down
Loading