fix: show running state for auto-approved commands

This commit is contained in:
Arafatkatze
2026-06-03 15:00:59 -07:00
parent 39f5e564f6
commit a6cd094460
5 changed files with 125 additions and 12 deletions
+1
View File
@@ -21,6 +21,7 @@ const aliasResolverPlugin = {
const aliases = {
"@": path.resolve(__dirname, "src"),
"@core": path.resolve(__dirname, "src/core"),
"@generated": path.resolve(__dirname, "src/generated"),
"@integrations": path.resolve(__dirname, "src/integrations"),
"@services": path.resolve(__dirname, "src/services"),
"@shared": path.resolve(__dirname, "src/shared"),
@@ -1,4 +1,4 @@
import { ClineMessage } from "./ExtensionMessage"
import type { ClineMessage } from "./ExtensionMessage"
/**
* Combines sequences of command and command_output messages in an array of ClineMessages.
@@ -1,4 +1,3 @@
import { COMMAND_OUTPUT_STRING } from "@shared/combineCommandSequences"
import {
ClineApiReqInfo,
ClineAskQuestion,
@@ -65,6 +64,7 @@ import SearchResultsDisplay from "./SearchResultsDisplay"
import SubagentStatusRow from "./SubagentStatusRow"
import { ThinkingRow } from "./ThinkingRow"
import UserMessage from "./UserMessage"
import { getCommandRowState } from "./commandRowState"
const HEADER_CLASSNAMES = "flex items-center gap-2.5 mb-3"
@@ -216,14 +216,8 @@ export const ChatRowContent = memo(
const type = message.type === "ask" ? message.ask : message.say
const isCommandMessage = type === "command"
// Check if command has output to determine if it's actually executing
const commandHasOutput = message.text?.includes(COMMAND_OUTPUT_STRING) ?? false
// A command is executing if it has output but hasn't completed yet
const isCommandExecuting = isCommandMessage && !message.commandCompleted && commandHasOutput
// A command is pending if it hasn't started (no output) and hasn't completed
const isCommandPending = isCommandMessage && isLast && !message.commandCompleted && !commandHasOutput
const isCommandCompleted = isCommandMessage && message.commandCompleted === true
const { isCommandMessage, isCommandCompleted, isCommandExecuting, isCommandPending, title: commandTitle } =
getCommandRowState(message, isLast, isRequestInProgress)
const isMcpServerResponding = isLast && lastModifiedMessage?.say === "mcp_server_request_started"
@@ -324,8 +318,8 @@ export const ChatRowContent = memo(
]
case "command":
return [
<TerminalIcon className="text-foreground size-2" />,
<span className="font-bold text-foreground">Cline wants to execute this command:</span>,
isCommandExecuting ? <ProgressIndicator /> : <TerminalIcon className="text-foreground size-2" />,
<span className="font-bold text-foreground">{commandTitle}</span>,
]
case "use_mcp_server":
const mcpServerUse = JSON.parse(message.text || "{}") as ClineAskUseMcpServer
@@ -366,6 +360,7 @@ export const ChatRowContent = memo(
apiRequestFailedMessage,
isCommandExecuting,
isCommandPending,
isCommandCompleted,
apiReqCancelReason,
isMcpServerResponding,
message.text,
@@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest"
import { getCommandRowState } from "./commandRowState"
describe("getCommandRowState", () => {
it("shows an auto-approved command with no output yet as running", () => {
const state = getCommandRowState(
{
partial: true,
say: "command",
text: "sleep 30",
type: "say",
},
true,
true,
)
expect(state.isCommandExecuting).toBe(true)
expect(state.isCommandPending).toBe(false)
expect(state.title).toBe("Cline is executing this command:")
})
it("keeps a command approval prompt pending before execution", () => {
const state = getCommandRowState(
{
ask: "command",
text: "npm install",
type: "ask",
},
true,
)
expect(state.isCommandExecuting).toBe(false)
expect(state.isCommandPending).toBe(true)
expect(state.title).toBe("Cline wants to execute this command:")
})
it("treats command rows with output as running until marked completed", () => {
const state = getCommandRowState(
{
ask: "command",
text: "npm test\nOutput:\nrunning tests",
type: "ask",
},
true,
)
expect(state.isCommandExecuting).toBe(true)
expect(state.isCommandPending).toBe(false)
expect(state.title).toBe("Cline is executing this command:")
})
it("shows completed command rows as executed", () => {
const state = getCommandRowState(
{
commandCompleted: true,
say: "command",
text: "echo ok\nOutput:\nok",
type: "say",
},
true,
)
expect(state.isCommandCompleted).toBe(true)
expect(state.isCommandExecuting).toBe(false)
expect(state.title).toBe("Cline executed this command:")
})
})
@@ -0,0 +1,50 @@
import { COMMAND_OUTPUT_STRING } from "@shared/combineCommandSequences"
export interface CommandRowMessage {
ask?: string
commandCompleted?: boolean
partial?: boolean
say?: string
text?: string
type?: string
}
export interface CommandRowState {
isCommandCompleted: boolean
isCommandExecuting: boolean
isCommandMessage: boolean
isCommandPending: boolean
title: string | undefined
}
export function getCommandRowState(
message: CommandRowMessage,
isLast: boolean,
isRequestInProgress?: boolean,
): CommandRowState {
const type = message.type === "ask" ? message.ask : message.say
const isCommandMessage = type === "command"
const commandHasOutput = message.text?.includes(COMMAND_OUTPUT_STRING) ?? false
const isCommandCompleted = isCommandMessage && message.commandCompleted === true
const isCommandExecuting =
isCommandMessage &&
!isCommandCompleted &&
(commandHasOutput ||
(message.type === "say" && (message.partial === true || (isLast && isRequestInProgress === true))))
const isCommandPending = isCommandMessage && isLast && !isCommandCompleted && !isCommandExecuting
const title = !isCommandMessage
? undefined
: isCommandCompleted
? "Cline executed this command:"
: isCommandExecuting
? "Cline is executing this command:"
: "Cline wants to execute this command:"
return {
isCommandCompleted,
isCommandExecuting,
isCommandMessage,
isCommandPending,
title,
}
}