fix(coding-agent): show bash tool elapsed time at bottom closes #2406
This commit is contained in:
@@ -105,6 +105,8 @@ export class ToolExecutionComponent extends Container {
|
|||||||
private writeHighlightCache?: WriteHighlightCache;
|
private writeHighlightCache?: WriteHighlightCache;
|
||||||
// When true, this component intentionally renders no lines
|
// When true, this component intentionally renders no lines
|
||||||
private hideComponent = false;
|
private hideComponent = false;
|
||||||
|
private bashStartedAt?: number;
|
||||||
|
private bashElapsedInterval?: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
toolName: string,
|
toolName: string,
|
||||||
@@ -158,6 +160,38 @@ export class ToolExecutionComponent extends Container {
|
|||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markExecutionStarted(): void {
|
||||||
|
if (this.toolName !== "bash" || this.bashStartedAt !== undefined) return;
|
||||||
|
this.bashStartedAt = Date.now();
|
||||||
|
this.ensureBashElapsedTimer();
|
||||||
|
this.updateDisplay();
|
||||||
|
this.ui.requestRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureBashElapsedTimer(): void {
|
||||||
|
if (this.toolName !== "bash" || !this.isPartial || this.bashStartedAt === undefined || this.bashElapsedInterval)
|
||||||
|
return;
|
||||||
|
this.bashElapsedInterval = setInterval(() => {
|
||||||
|
this.updateDisplay();
|
||||||
|
this.ui.requestRender();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopBashElapsedTimer(): void {
|
||||||
|
if (!this.bashElapsedInterval) return;
|
||||||
|
clearInterval(this.bashElapsedInterval);
|
||||||
|
this.bashElapsedInterval = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBashDurationMs(): number | undefined {
|
||||||
|
if (this.toolName !== "bash" || this.bashStartedAt === undefined) return undefined;
|
||||||
|
return Date.now() - this.bashStartedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatDuration(ms: number): string {
|
||||||
|
return `${(ms / 1000).toFixed(1)}s`;
|
||||||
|
}
|
||||||
|
|
||||||
private highlightSingleLine(line: string, lang: string): string {
|
private highlightSingleLine(line: string, lang: string): string {
|
||||||
const highlighted = highlightCode(line, lang);
|
const highlighted = highlightCode(line, lang);
|
||||||
return highlighted[0] ?? "";
|
return highlighted[0] ?? "";
|
||||||
@@ -308,6 +342,13 @@ export class ToolExecutionComponent extends Container {
|
|||||||
): void {
|
): void {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.isPartial = isPartial;
|
this.isPartial = isPartial;
|
||||||
|
if (this.toolName === "bash") {
|
||||||
|
if (isPartial) {
|
||||||
|
this.ensureBashElapsedTimer();
|
||||||
|
} else {
|
||||||
|
this.stopBashElapsedTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.toolName === "write" && !isPartial) {
|
if (this.toolName === "write" && !isPartial) {
|
||||||
const rawPath = str(this.args?.file_path ?? this.args?.path);
|
const rawPath = str(this.args?.file_path ?? this.args?.path);
|
||||||
const fileContent = str(this.args?.content);
|
const fileContent = str(this.args?.content);
|
||||||
@@ -582,6 +623,14 @@ export class ToolExecutionComponent extends Container {
|
|||||||
this.contentBox.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
|
this.contentBox.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bashDurationMs = this.getBashDurationMs();
|
||||||
|
if (bashDurationMs !== undefined) {
|
||||||
|
const label = this.isPartial ? "Elapsed" : "Took";
|
||||||
|
this.contentBox.addChild(
|
||||||
|
new Text(`\n${theme.fg("muted", `${label} ${this.formatDuration(bashDurationMs)}`)}`, 0, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTextOutput(): string {
|
private getTextOutput(): string {
|
||||||
|
|||||||
@@ -2260,8 +2260,9 @@ export class InteractiveMode {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "tool_execution_start": {
|
case "tool_execution_start": {
|
||||||
if (!this.pendingTools.has(event.toolCallId)) {
|
let component = this.pendingTools.get(event.toolCallId);
|
||||||
const component = new ToolExecutionComponent(
|
if (!component) {
|
||||||
|
component = new ToolExecutionComponent(
|
||||||
event.toolName,
|
event.toolName,
|
||||||
event.args,
|
event.args,
|
||||||
{
|
{
|
||||||
@@ -2273,8 +2274,9 @@ export class InteractiveMode {
|
|||||||
component.setExpanded(this.toolOutputExpanded);
|
component.setExpanded(this.toolOutputExpanded);
|
||||||
this.chatContainer.addChild(component);
|
this.chatContainer.addChild(component);
|
||||||
this.pendingTools.set(event.toolCallId, component);
|
this.pendingTools.set(event.toolCallId, component);
|
||||||
this.ui.requestRender();
|
|
||||||
}
|
}
|
||||||
|
component.markExecutionStarted();
|
||||||
|
this.ui.requestRender();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user