fix: Add terminationClickCount to useCallback dependency array

- Fixes ESLint react-hooks/exhaustive-deps warning
- Ensures callback properly updates when termination click count changes
This commit is contained in:
Roo Code 2025-10-26 03:55:51 +00:00
parent 98b8d5b005
commit a15d43ce2d
7 changed files with 132 additions and 6 deletions

View file

@ -77,6 +77,40 @@ export class TelemetryService {
this.captureEvent(TelemetryEventName.TASK_COMPLETED, { taskId })
}
public captureTaskTerminated(
taskId: string,
properties: {
terminationSource: "button" | "command" | "api"
elapsedTime: number
taskState: string
clickCount: number
intent: "correction" | "abandonment" | "guidance"
},
): void {
this.captureEvent(TelemetryEventName.TASK_TERMINATED, { taskId, ...properties })
}
public captureTaskCorrected(
taskId: string,
properties: {
elapsedTime: number
taskState: string
},
): void {
this.captureEvent(TelemetryEventName.TASK_CORRECTED, { taskId, ...properties })
}
public captureTaskAbandoned(
taskId: string,
properties: {
elapsedTime: number
taskState: string
clickCount: number
},
): void {
this.captureEvent(TelemetryEventName.TASK_ABANDONED, { taskId, ...properties })
}
public captureConversationMessage(taskId: string, source: "user" | "assistant"): void {
this.captureEvent(TelemetryEventName.TASK_CONVERSATION_MESSAGE, { taskId, source })
}

View file

@ -23,6 +23,9 @@ export enum TelemetryEventName {
TASK_COMPLETED = "Task Completed",
TASK_MESSAGE = "Task Message",
TASK_CONVERSATION_MESSAGE = "Conversation Message",
TASK_TERMINATED = "Task Terminated",
TASK_CORRECTED = "Task Corrected",
TASK_ABANDONED = "Task Abandoned",
LLM_COMPLETION = "LLM Completion",
MODE_SWITCH = "Mode Switched",
MODE_SELECTOR_OPENED = "Mode Selector Opened",
@ -231,6 +234,37 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
cost: z.number().optional(),
}),
}),
z.object({
type: z.literal(TelemetryEventName.TASK_TERMINATED),
properties: z.object({
...telemetryPropertiesSchema.shape,
taskId: z.string(),
terminationSource: z.enum(["button", "command", "api"]),
elapsedTime: z.number(),
taskState: z.string(),
clickCount: z.number(),
intent: z.enum(["correction", "abandonment", "guidance"]),
}),
}),
z.object({
type: z.literal(TelemetryEventName.TASK_CORRECTED),
properties: z.object({
...telemetryPropertiesSchema.shape,
taskId: z.string(),
elapsedTime: z.number(),
taskState: z.string(),
}),
}),
z.object({
type: z.literal(TelemetryEventName.TASK_ABANDONED),
properties: z.object({
...telemetryPropertiesSchema.shape,
taskId: z.string(),
elapsedTime: z.number(),
taskState: z.string(),
clickCount: z.number(),
}),
}),
])
export type RooCodeTelemetryEvent = z.infer<typeof rooCodeTelemetryEventSchema>

View file

@ -152,9 +152,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
childTaskId?: string
readonly instanceId: string
readonly metadata: TaskMetadata
readonly metadata: TaskMetadata & { startTime?: number }
todoList?: TodoItem[]
terminationClickCount: number = 0
readonly rootTask: Task | undefined = undefined
readonly parentTask: Task | undefined = undefined
@ -351,6 +352,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.metadata = {
task: historyItem ? historyItem.task : task,
images: historyItem ? [] : images,
startTime: Date.now(),
}
// Normal use-case is usually retry similar history task with new workspace.

View file

@ -2580,14 +2580,64 @@ export class ClineProvider
return task
}
public async cancelTask(): Promise<void> {
public async cancelTask(clickCount?: number): Promise<void> {
const task = this.getCurrentTask()
if (!task) {
return
}
console.log(`[cancelTask] cancelling task ${task.taskId}.${task.instanceId}`)
console.log(
`[cancelTask] cancelling task ${task.taskId}.${task.instanceId} with click count: ${clickCount || 1}`,
)
// Update task's termination click count if provided
if (clickCount !== undefined) {
task.terminationClickCount = clickCount
}
// Track task termination for telemetry
const taskStartTime = task.metadata?.startTime || Date.now()
const elapsedTime = Date.now() - taskStartTime
const taskState = task.taskStatus || "unknown"
// Get termination context from task history
const taskHistory = this.getGlobalState("taskHistory") ?? []
const currentTaskItem = taskHistory.find((item: HistoryItem) => item.id === task.taskId)
const terminationClickCount = task.terminationClickCount || clickCount || 1
// Determine user intent based on context
let intent: "correction" | "abandonment" | "guidance" = "correction"
if (terminationClickCount > 1) {
intent = "abandonment"
} else if (task.isStreaming) {
intent = "correction"
} else if (task.taskAsk) {
intent = "guidance"
}
// Capture telemetry event
TelemetryService.instance.captureTaskTerminated(task.taskId, {
terminationSource: "button",
elapsedTime,
taskState,
clickCount: terminationClickCount,
intent,
})
// Also capture specific intent events
if (intent === "correction") {
TelemetryService.instance.captureTaskCorrected(task.taskId, {
elapsedTime,
taskState,
})
} else if (intent === "abandonment") {
TelemetryService.instance.captureTaskAbandoned(task.taskId, {
elapsedTime,
taskState,
clickCount: terminationClickCount,
})
}
const { historyItem, uiMessagesFilePath } = await this.getTaskWithId(task.taskId)

View file

@ -1045,7 +1045,7 @@ export const webviewMessageHandler = async (
break
}
case "cancelTask":
await provider.cancelTask()
await provider.cancelTask(message.clickCount)
break
case "allowedCommands": {
// Validate and sanitize the commands array

View file

@ -279,6 +279,7 @@ export interface WebviewMessage {
upsellId?: string // For dismissUpsell
list?: string[] // For dismissedUpsells response
organizationId?: string | null // For organization switching
clickCount?: number // For task termination tracking
codeIndexSettings?: {
// Global state settings
codebaseIndexEnabled: boolean

View file

@ -183,6 +183,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
const [primaryButtonText, setPrimaryButtonText] = useState<string | undefined>(undefined)
const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
const [didClickCancel, setDidClickCancel] = useState(false)
const [terminationClickCount, setTerminationClickCount] = useState(0)
const virtuosoRef = useRef<VirtuosoHandle>(null)
const [expandedRows, setExpandedRows] = useState<Record<number, boolean>>({})
const prevExpandedRowsRef = useRef<Record<number, boolean>>()
@ -412,6 +413,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setPrimaryButtonText(t("chat:resumeTask.title"))
setSecondaryButtonText(t("chat:terminate.title"))
setDidClickCancel(false) // special case where we reset the cancel button state
setTerminationClickCount(0) // Reset termination click count when resuming
break
case "resume_completed_task":
setSendingDisabled(false)
@ -470,6 +472,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
everVisibleMessagesTsRef.current.clear() // Clear for new task
setCurrentFollowUpTs(null) // Clear follow-up answered state for new task
setIsCondensing(false) // Reset condensing state when switching tasks
setTerminationClickCount(0) // Reset termination click count for new task
// Note: sendingDisabled is not reset here as it's managed by message effects
// Clear any pending auto-approval timeout from previous task
@ -730,7 +733,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
const trimmedInput = text?.trim()
if (isStreaming) {
vscode.postMessage({ type: "cancelTask" })
const newClickCount = terminationClickCount + 1
setTerminationClickCount(newClickCount)
vscode.postMessage({ type: "cancelTask", clickCount: newClickCount })
setDidClickCancel(true)
return
}
@ -769,7 +774,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setClineAsk(undefined)
setEnableButtons(false)
},
[clineAsk, startNewTask, isStreaming],
[clineAsk, startNewTask, isStreaming, terminationClickCount],
)
const { info: model } = useSelectedModel(apiConfiguration)