Skip to content

Commit a15d43c

Browse files
committed
fix: Add terminationClickCount to useCallback dependency array
- Fixes ESLint react-hooks/exhaustive-deps warning - Ensures callback properly updates when termination click count changes
1 parent 98b8d5b commit a15d43c

File tree

7 files changed

+132
-6
lines changed

7 files changed

+132
-6
lines changed

packages/telemetry/src/TelemetryService.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,40 @@ export class TelemetryService {
7777
this.captureEvent(TelemetryEventName.TASK_COMPLETED, { taskId })
7878
}
7979

80+
public captureTaskTerminated(
81+
taskId: string,
82+
properties: {
83+
terminationSource: "button" | "command" | "api"
84+
elapsedTime: number
85+
taskState: string
86+
clickCount: number
87+
intent: "correction" | "abandonment" | "guidance"
88+
},
89+
): void {
90+
this.captureEvent(TelemetryEventName.TASK_TERMINATED, { taskId, ...properties })
91+
}
92+
93+
public captureTaskCorrected(
94+
taskId: string,
95+
properties: {
96+
elapsedTime: number
97+
taskState: string
98+
},
99+
): void {
100+
this.captureEvent(TelemetryEventName.TASK_CORRECTED, { taskId, ...properties })
101+
}
102+
103+
public captureTaskAbandoned(
104+
taskId: string,
105+
properties: {
106+
elapsedTime: number
107+
taskState: string
108+
clickCount: number
109+
},
110+
): void {
111+
this.captureEvent(TelemetryEventName.TASK_ABANDONED, { taskId, ...properties })
112+
}
113+
80114
public captureConversationMessage(taskId: string, source: "user" | "assistant"): void {
81115
this.captureEvent(TelemetryEventName.TASK_CONVERSATION_MESSAGE, { taskId, source })
82116
}

packages/types/src/telemetry.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export enum TelemetryEventName {
2323
TASK_COMPLETED = "Task Completed",
2424
TASK_MESSAGE = "Task Message",
2525
TASK_CONVERSATION_MESSAGE = "Conversation Message",
26+
TASK_TERMINATED = "Task Terminated",
27+
TASK_CORRECTED = "Task Corrected",
28+
TASK_ABANDONED = "Task Abandoned",
2629
LLM_COMPLETION = "LLM Completion",
2730
MODE_SWITCH = "Mode Switched",
2831
MODE_SELECTOR_OPENED = "Mode Selector Opened",
@@ -231,6 +234,37 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
231234
cost: z.number().optional(),
232235
}),
233236
}),
237+
z.object({
238+
type: z.literal(TelemetryEventName.TASK_TERMINATED),
239+
properties: z.object({
240+
...telemetryPropertiesSchema.shape,
241+
taskId: z.string(),
242+
terminationSource: z.enum(["button", "command", "api"]),
243+
elapsedTime: z.number(),
244+
taskState: z.string(),
245+
clickCount: z.number(),
246+
intent: z.enum(["correction", "abandonment", "guidance"]),
247+
}),
248+
}),
249+
z.object({
250+
type: z.literal(TelemetryEventName.TASK_CORRECTED),
251+
properties: z.object({
252+
...telemetryPropertiesSchema.shape,
253+
taskId: z.string(),
254+
elapsedTime: z.number(),
255+
taskState: z.string(),
256+
}),
257+
}),
258+
z.object({
259+
type: z.literal(TelemetryEventName.TASK_ABANDONED),
260+
properties: z.object({
261+
...telemetryPropertiesSchema.shape,
262+
taskId: z.string(),
263+
elapsedTime: z.number(),
264+
taskState: z.string(),
265+
clickCount: z.number(),
266+
}),
267+
}),
234268
])
235269

236270
export type RooCodeTelemetryEvent = z.infer<typeof rooCodeTelemetryEventSchema>

src/core/task/Task.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
152152
childTaskId?: string
153153

154154
readonly instanceId: string
155-
readonly metadata: TaskMetadata
155+
readonly metadata: TaskMetadata & { startTime?: number }
156156

157157
todoList?: TodoItem[]
158+
terminationClickCount: number = 0
158159

159160
readonly rootTask: Task | undefined = undefined
160161
readonly parentTask: Task | undefined = undefined
@@ -351,6 +352,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
351352
this.metadata = {
352353
task: historyItem ? historyItem.task : task,
353354
images: historyItem ? [] : images,
355+
startTime: Date.now(),
354356
}
355357

356358
// Normal use-case is usually retry similar history task with new workspace.

src/core/webview/ClineProvider.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,14 +2580,64 @@ export class ClineProvider
25802580
return task
25812581
}
25822582

2583-
public async cancelTask(): Promise<void> {
2583+
public async cancelTask(clickCount?: number): Promise<void> {
25842584
const task = this.getCurrentTask()
25852585

25862586
if (!task) {
25872587
return
25882588
}
25892589

2590-
console.log(`[cancelTask] cancelling task ${task.taskId}.${task.instanceId}`)
2590+
console.log(
2591+
`[cancelTask] cancelling task ${task.taskId}.${task.instanceId} with click count: ${clickCount || 1}`,
2592+
)
2593+
2594+
// Update task's termination click count if provided
2595+
if (clickCount !== undefined) {
2596+
task.terminationClickCount = clickCount
2597+
}
2598+
2599+
// Track task termination for telemetry
2600+
const taskStartTime = task.metadata?.startTime || Date.now()
2601+
const elapsedTime = Date.now() - taskStartTime
2602+
const taskState = task.taskStatus || "unknown"
2603+
2604+
// Get termination context from task history
2605+
const taskHistory = this.getGlobalState("taskHistory") ?? []
2606+
const currentTaskItem = taskHistory.find((item: HistoryItem) => item.id === task.taskId)
2607+
const terminationClickCount = task.terminationClickCount || clickCount || 1
2608+
2609+
// Determine user intent based on context
2610+
let intent: "correction" | "abandonment" | "guidance" = "correction"
2611+
if (terminationClickCount > 1) {
2612+
intent = "abandonment"
2613+
} else if (task.isStreaming) {
2614+
intent = "correction"
2615+
} else if (task.taskAsk) {
2616+
intent = "guidance"
2617+
}
2618+
2619+
// Capture telemetry event
2620+
TelemetryService.instance.captureTaskTerminated(task.taskId, {
2621+
terminationSource: "button",
2622+
elapsedTime,
2623+
taskState,
2624+
clickCount: terminationClickCount,
2625+
intent,
2626+
})
2627+
2628+
// Also capture specific intent events
2629+
if (intent === "correction") {
2630+
TelemetryService.instance.captureTaskCorrected(task.taskId, {
2631+
elapsedTime,
2632+
taskState,
2633+
})
2634+
} else if (intent === "abandonment") {
2635+
TelemetryService.instance.captureTaskAbandoned(task.taskId, {
2636+
elapsedTime,
2637+
taskState,
2638+
clickCount: terminationClickCount,
2639+
})
2640+
}
25912641

25922642
const { historyItem, uiMessagesFilePath } = await this.getTaskWithId(task.taskId)
25932643

src/core/webview/webviewMessageHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ export const webviewMessageHandler = async (
10451045
break
10461046
}
10471047
case "cancelTask":
1048-
await provider.cancelTask()
1048+
await provider.cancelTask(message.clickCount)
10491049
break
10501050
case "allowedCommands": {
10511051
// Validate and sanitize the commands array

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ export interface WebviewMessage {
279279
upsellId?: string // For dismissUpsell
280280
list?: string[] // For dismissedUpsells response
281281
organizationId?: string | null // For organization switching
282+
clickCount?: number // For task termination tracking
282283
codeIndexSettings?: {
283284
// Global state settings
284285
codebaseIndexEnabled: boolean

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
183183
const [primaryButtonText, setPrimaryButtonText] = useState<string | undefined>(undefined)
184184
const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
185185
const [didClickCancel, setDidClickCancel] = useState(false)
186+
const [terminationClickCount, setTerminationClickCount] = useState(0)
186187
const virtuosoRef = useRef<VirtuosoHandle>(null)
187188
const [expandedRows, setExpandedRows] = useState<Record<number, boolean>>({})
188189
const prevExpandedRowsRef = useRef<Record<number, boolean>>()
@@ -412,6 +413,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
412413
setPrimaryButtonText(t("chat:resumeTask.title"))
413414
setSecondaryButtonText(t("chat:terminate.title"))
414415
setDidClickCancel(false) // special case where we reset the cancel button state
416+
setTerminationClickCount(0) // Reset termination click count when resuming
415417
break
416418
case "resume_completed_task":
417419
setSendingDisabled(false)
@@ -470,6 +472,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
470472
everVisibleMessagesTsRef.current.clear() // Clear for new task
471473
setCurrentFollowUpTs(null) // Clear follow-up answered state for new task
472474
setIsCondensing(false) // Reset condensing state when switching tasks
475+
setTerminationClickCount(0) // Reset termination click count for new task
473476
// Note: sendingDisabled is not reset here as it's managed by message effects
474477

475478
// Clear any pending auto-approval timeout from previous task
@@ -730,7 +733,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
730733
const trimmedInput = text?.trim()
731734

732735
if (isStreaming) {
733-
vscode.postMessage({ type: "cancelTask" })
736+
const newClickCount = terminationClickCount + 1
737+
setTerminationClickCount(newClickCount)
738+
vscode.postMessage({ type: "cancelTask", clickCount: newClickCount })
734739
setDidClickCancel(true)
735740
return
736741
}
@@ -769,7 +774,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
769774
setClineAsk(undefined)
770775
setEnableButtons(false)
771776
},
772-
[clineAsk, startNewTask, isStreaming],
777+
[clineAsk, startNewTask, isStreaming, terminationClickCount],
773778
)
774779

775780
const { info: model } = useSelectedModel(apiConfiguration)

0 commit comments

Comments
 (0)