Skip to content
4 changes: 4 additions & 0 deletions packages/cloud/src/TelemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ abstract class BaseTelemetryClient implements TelemetryClient {

public abstract capture(event: TelemetryEvent): Promise<void>

public captureException(_error: Error, _additionalProperties?: Record<string, unknown>): void {
// No-op - exception capture is only supported by PostHog
}

public setProvider(provider: TelemetryPropertiesProvider): void {
this.providerRef = new WeakRef(provider)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/telemetry/src/BaseTelemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export abstract class BaseTelemetryClient implements TelemetryClient {

public abstract capture(event: TelemetryEvent): Promise<void>

public abstract captureException(error: Error, additionalProperties?: Record<string, unknown>): void

public setProvider(provider: TelemetryPropertiesProvider): void {
this.providerRef = new WeakRef(provider)
}
Expand Down
16 changes: 16 additions & 0 deletions packages/telemetry/src/PostHogTelemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ export class PostHogTelemetryClient extends BaseTelemetryClient {
})
}

public override captureException(error: Error, additionalProperties?: Record<string, unknown>): void {
if (!this.isTelemetryEnabled()) {
if (this.debug) {
console.info(`[PostHogTelemetryClient#captureException] Skipping exception: ${error.message}`)
}

return
}

if (this.debug) {
console.info(`[PostHogTelemetryClient#captureException] ${error.message}`)
}

this.client.captureException(error, this.distinctId, additionalProperties)
}

/**
* Updates the telemetry state based on user preferences and VSCode settings.
* Only enables telemetry if both VSCode global telemetry is enabled and
Expand Down
13 changes: 13 additions & 0 deletions packages/telemetry/src/TelemetryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ export class TelemetryService {
this.clients.forEach((client) => client.capture({ event: eventName, properties }))
}

/**
* Captures an exception using PostHog's error tracking
* @param error The error to capture
* @param additionalProperties Additional properties to include with the exception
*/
public captureException(error: Error, additionalProperties?: Record<string, unknown>): void {
if (!this.isReady) {
return
}

this.clients.forEach((client) => client.captureException(error, additionalProperties))
}

public captureTaskCreated(taskId: string): void {
this.captureEvent(TelemetryEventName.TASK_CREATED, { taskId })
}
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export interface TelemetryClient {

setProvider(provider: TelemetryPropertiesProvider): void
capture(options: TelemetryEvent): Promise<void>
captureException(error: Error, additionalProperties?: Record<string, unknown>): void
updateTelemetryState(isOptedIn: boolean): void
isTelemetryEnabled(): boolean
shutdown(): Promise<void>
Expand Down
2 changes: 0 additions & 2 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ import { Task } from "../task/Task"
import { codebaseSearchTool } from "../tools/CodebaseSearchTool"
import { experiments, EXPERIMENT_IDS } from "../../shared/experiments"
import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool"
import { isNativeProtocol } from "@roo-code/types"
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"

/**
* Processes and presents assistant message content to the user interface.
Expand Down
19 changes: 17 additions & 2 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import { getMessagesSinceLastSummary, summarizeConversation, getEffectiveApiHist
import { MessageQueueService } from "../message-queue/MessageQueueService"
import { AutoApprovalHandler, checkAutoApproval } from "../auto-approval"
import { MessageManager } from "../message-manager"
import { validateAndFixToolResultIds } from "./validateToolResultIds"

const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds
Expand Down Expand Up @@ -811,7 +812,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {

this.apiConversationHistory.push(messageWithTs)
} else {
const messageWithTs = { ...message, ts: Date.now() }
// For user messages, validate and fix tool_result IDs against the previous assistant message
// This is a centralized catch-all that handles all tool_use/tool_result ID mismatches
const previousAssistantMessage = this.apiConversationHistory
.slice()
.reverse()
.find((msg) => msg.role === "assistant")
const validatedMessage = validateAndFixToolResultIds(message, previousAssistantMessage)
const messageWithTs = { ...validatedMessage, ts: Date.now() }
this.apiConversationHistory.push(messageWithTs)
}

Expand Down Expand Up @@ -849,7 +857,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
role: "user",
content: this.userMessageContent,
}
const userMessageWithTs = { ...userMessage, ts: Date.now() }

// Validate and fix tool_result IDs against the previous assistant message
const previousAssistantMessage = this.apiConversationHistory
.slice()
.reverse()
.find((msg) => msg.role === "assistant")
const validatedMessage = validateAndFixToolResultIds(userMessage, previousAssistantMessage)
const userMessageWithTs = { ...validatedMessage, ts: Date.now() }
this.apiConversationHistory.push(userMessageWithTs as ApiMessage)

await this.saveApiConversationHistory()
Expand Down
Loading
Loading