mirror of
https://github.com/cline/cline.git
synced 2026-09-11 16:42:40 +08:00
fix: show running state for auto-approved commands
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user