Skip to content

Commit 43cbe93

Browse files
committed
Emit event when a task ask requires interaction
1 parent 44086e4 commit 43cbe93

File tree

5 files changed

+78
-10
lines changed

5 files changed

+78
-10
lines changed

packages/types/src/events.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { z } from "zod"
22

3-
import { clineMessageSchema, tokenUsageSchema } from "./message.js"
3+
import { clineMessageSchema, tokenUsageSchema, clineAskSchema } from "./message.js"
44
import { toolNamesSchema, toolUsageSchema } from "./tool.js"
55

66
/**
@@ -29,6 +29,7 @@ export enum RooCodeEventName {
2929
Message = "message",
3030
TaskModeSwitched = "taskModeSwitched",
3131
TaskAskResponded = "taskAskResponded",
32+
TaskAskRequiresInteraction = "taskAskRequiresInteraction",
3233

3334
// Task Analytics
3435
TaskTokenUsageUpdated = "taskTokenUsageUpdated",
@@ -74,6 +75,7 @@ export const rooCodeEventsSchema = z.object({
7475
]),
7576
[RooCodeEventName.TaskModeSwitched]: z.tuple([z.string(), z.string()]),
7677
[RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]),
78+
[RooCodeEventName.TaskAskRequiresInteraction]: z.tuple([z.string(), clineAskSchema, z.string()]),
7779

7880
[RooCodeEventName.TaskToolFailed]: z.tuple([z.string(), toolNamesSchema, z.string()]),
7981
[RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]),
@@ -163,6 +165,11 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [
163165
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAskResponded],
164166
taskId: z.number().optional(),
165167
}),
168+
z.object({
169+
eventName: z.literal(RooCodeEventName.TaskAskRequiresInteraction),
170+
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskAskRequiresInteraction],
171+
taskId: z.number().optional(),
172+
}),
166173

167174
// Task Analytics
168175
z.object({

packages/types/src/task.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from "zod"
22

33
import { RooCodeEventName } from "./events.js"
4-
import { type ClineMessage, type BlockingAsk, type TokenUsage } from "./message.js"
4+
import { type ClineMessage, type BlockingAsk, type TokenUsage, type ClineAsk } from "./message.js"
55
import { type ToolUsage, type ToolName } from "./tool.js"
66
import type { StaticAppProperties, GitProperties, TelemetryProperties } from "./telemetry.js"
77

@@ -100,6 +100,7 @@ export type TaskEvents = {
100100
[RooCodeEventName.Message]: [{ action: "created" | "updated"; message: ClineMessage }]
101101
[RooCodeEventName.TaskModeSwitched]: [taskId: string, mode: string]
102102
[RooCodeEventName.TaskAskResponded]: []
103+
[RooCodeEventName.TaskAskRequiresInteraction]: [taskId: string, askType: ClineAsk, reason: string]
103104

104105
// Task Analytics
105106
[RooCodeEventName.TaskToolFailed]: [taskId: string, tool: ToolName, error: string]

src/core/webview/webviewMessageHandler.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import * as yaml from "yaml"
88

99
import {
1010
type Language,
11-
type ProviderSettings,
1211
type GlobalState,
1312
type ClineMessage,
13+
type ClineAsk,
1414
TelemetryEventName,
15+
RooCodeEventName,
1516
} from "@roo-code/types"
1617
import { CloudService } from "@roo-code/cloud"
1718
import { TelemetryService } from "@roo-code/telemetry"
@@ -347,6 +348,19 @@ export const webviewMessageHandler = async (
347348
break
348349
case "askResponse":
349350
provider.getCurrentTask()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
351+
break
352+
case "askRequiresInteraction":
353+
if (message.askType && message.reason) {
354+
const task = provider.getCurrentTask()
355+
356+
task?.emit(
357+
RooCodeEventName.TaskAskRequiresInteraction,
358+
task.taskId,
359+
message.askType as ClineAsk,
360+
message.reason,
361+
)
362+
}
363+
350364
break
351365
case "autoCondenseContext":
352366
await updateGlobalState("autoCondenseContext", message.bool)

src/shared/WebviewMessage.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface WebviewMessage {
4949
| "webviewDidLaunch"
5050
| "newTask"
5151
| "askResponse"
52+
| "askRequiresInteraction"
5253
| "terminalOperation"
5354
| "clearTask"
5455
| "didShowAnnouncement"
@@ -218,6 +219,8 @@ export interface WebviewMessage {
218219
context?: string
219220
dataUri?: string
220221
askResponse?: ClineAskResponse
222+
askType?: string
223+
reason?: string
221224
apiConfiguration?: ProviderSettings
222225
images?: string[]
223226
bool?: boolean

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
7777
ref,
7878
) => {
7979
const isMountedRef = useRef(true)
80+
8081
const [audioBaseUri] = useState(() => {
8182
const w = window as any
8283
return w.AUDIO_BASE_URI || ""
8384
})
85+
8486
const { t } = useAppTranslation()
8587
const { t: tSettings } = useTranslation("settings")
8688
const modeShortcutText = `${isMac ? "⌘" : "Ctrl"} + . ${t("chat:forNextMode")}, ${isMac ? "⌘" : "Ctrl"} + Shift + . ${t("chat:forPreviousMode")}`
89+
8790
const {
8891
clineMessages: messages,
8992
currentTaskItem,
@@ -1612,12 +1615,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16121615
return
16131616
}
16141617

1615-
// Then check for auto-approve
1618+
// Then check for auto-approve.
16161619
if (lastMessage?.ask && isAutoApproved(lastMessage)) {
1617-
// Special handling for follow-up questions
1620+
// Special handling for follow-up questions.
16181621
if (lastMessage.ask === "followup") {
1619-
// Handle invalid JSON
1622+
// Handle invalid JSON.
16201623
let followUpData: FollowUpData = {}
1624+
16211625
try {
16221626
followUpData = JSON.parse(lastMessage.text || "{}") as FollowUpData
16231627
} catch (error) {
@@ -1626,27 +1630,30 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16261630
}
16271631

16281632
if (followUpData && followUpData.suggest && followUpData.suggest.length > 0) {
1629-
// Wait for the configured timeout before auto-selecting the first suggestion
1633+
// Wait for the configured timeout before auto-selecting the first suggestion.
16301634
await new Promise<void>((resolve) => {
16311635
autoApproveTimeoutRef.current = setTimeout(() => {
16321636
autoApproveTimeoutRef.current = null
16331637
resolve()
16341638
}, followupAutoApproveTimeoutMs)
16351639
})
16361640

1637-
// Check if user responded manually
1641+
// Check if user responded manually.
16381642
if (userRespondedRef.current) {
16391643
return
16401644
}
16411645

1642-
// Get the first suggestion
1646+
// Get the first suggestion.
16431647
const firstSuggestion = followUpData.suggest[0]
16441648

1645-
// Handle the suggestion click
1649+
// Handle the suggestion click.
16461650
handleSuggestionClickInRow(firstSuggestion)
16471651
return
16481652
}
16491653
} else if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) {
1654+
// When auto-approval is enabled for write operations, there
1655+
// is a configurable delay (writeDelayMs) before automatically
1656+
// approving these changes.
16501657
await new Promise<void>((resolve) => {
16511658
autoApproveTimeoutRef.current = setTimeout(() => {
16521659
autoApproveTimeoutRef.current = null
@@ -1660,8 +1667,43 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16601667
setSendingDisabled(true)
16611668
setClineAsk(undefined)
16621669
setEnableButtons(false)
1670+
} else if (lastMessage?.ask) {
1671+
// Ask requires user interaction - not auto-approved or auto-rejected.
1672+
const reason = (() => {
1673+
if (autoApprovalEnabled !== true) {
1674+
return "auto_approval_disabled"
1675+
}
1676+
1677+
// Determine more specific reasons based on the ask type.
1678+
switch (lastMessage.ask) {
1679+
case "command":
1680+
return "command_requires_manual_approval"
1681+
case "tool":
1682+
return "tool_requires_manual_approval"
1683+
case "browser_action_launch":
1684+
return "browser_action_requires_manual_approval"
1685+
case "use_mcp_server":
1686+
return "mcp_requires_manual_approval"
1687+
case "followup":
1688+
return "followup_requires_manual_response"
1689+
case "completion_result":
1690+
return "completion_requires_manual_confirmation"
1691+
case "api_req_failed":
1692+
return "api_failure_requires_manual_decision"
1693+
case "mistake_limit_reached":
1694+
return "mistake_limit_requires_manual_guidance"
1695+
case "auto_approval_max_req_reached":
1696+
return "approval_limit_reached"
1697+
default:
1698+
return "manual_approval_required"
1699+
}
1700+
})()
1701+
1702+
// Notify the extension host that this ask requires user interaction.
1703+
vscode.postMessage({ type: "askRequiresInteraction", askType: lastMessage.ask, reason: reason })
16631704
}
16641705
}
1706+
16651707
autoApproveOrReject()
16661708

16671709
return () => {
@@ -1686,6 +1728,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
16861728
allowedCommands,
16871729
deniedCommands,
16881730
mcpServers,
1731+
autoApprovalEnabled,
16891732
isAutoApproved,
16901733
lastMessage,
16911734
writeDelayMs,

0 commit comments

Comments
 (0)