diff --git a/extensions/cli/src/commands/serve.ts b/extensions/cli/src/commands/serve.ts index 2b371846221..684ae7e6e6d 100644 --- a/extensions/cli/src/commands/serve.ts +++ b/extensions/cli/src/commands/serve.ts @@ -9,6 +9,7 @@ import { prependPrompt } from "src/util/promptProcessor.js"; import { getAccessToken, getAssistantSlug } from "../auth/workos.js"; import { runEnvironmentInstallSafe } from "../environment/environmentHandler.js"; import { processCommandFlags } from "../flags/flagProcessor.js"; +import { setAgentId } from "../index.js"; import { toolPermissionManager } from "../permissions/permissionManager.js"; import { getService, @@ -50,6 +51,9 @@ interface ServeOptions extends ExtendedCommandOptions { export async function serve(prompt?: string, options: ServeOptions = {}) { await posthogService.capture("sessionStart", {}); + // Set agent ID for error reporting if provided + setAgentId(options.id); + // Check if prompt should come from stdin instead of parameter let actualPrompt = prompt; if (!prompt) { diff --git a/extensions/cli/src/index.ts b/extensions/cli/src/index.ts index 76fef86d0e7..fe9c849d57e 100644 --- a/extensions/cli/src/index.ts +++ b/extensions/cli/src/index.ts @@ -20,6 +20,7 @@ import { configureConsoleForHeadless, safeStderr } from "./init.js"; import { sentryService } from "./sentry.js"; import { addCommonOptions, mergeParentOptions } from "./shared-options.js"; import { posthogService } from "./telemetry/posthogService.js"; +import { post } from "./util/apiClient.js"; import { gracefulExit } from "./util/exit.js"; import { logger } from "./util/logger.js"; import { readStdinSync } from "./util/stdin.js"; @@ -31,6 +32,9 @@ let showExitMessage: boolean; let exitMessageCallback: (() => void) | null; let lastCtrlCTime: number; +// Agent ID for serve mode - set when serve command is invoked with --id +let agentId: string | undefined; + // Initialize state immediately to avoid temporal dead zone issues with exported functions (function initializeTUIState() { tuiUnmount = null; @@ -39,6 +43,11 @@ let lastCtrlCTime: number; lastCtrlCTime = 0; })(); +// Set the agent ID for error reporting (called by serve command) +export function setAgentId(id: string | undefined) { + agentId = id; +} + // Register TUI cleanup function for graceful shutdown export function setTUIUnmount(unmount: () => void) { tuiUnmount = unmount; @@ -89,6 +98,27 @@ export function shouldShowExitMessage(): boolean { return showExitMessage; } +// Helper to report unhandled errors to the API when running in serve mode +async function reportUnhandledErrorToApi(error: Error): Promise { + if (!agentId) { + // Not running in serve mode with an agent ID, skip API reporting + return; + } + + try { + await post(`agents/${agentId}/status`, { + status: "FAILED", + errorMessage: `Unhandled error: ${error.message}`, + }); + logger.debug(`Reported unhandled error to API for agent ${agentId}`); + } catch (apiError) { + // If API reporting fails, just log it - don't crash + logger.debug( + `Failed to report error to API: ${apiError instanceof Error ? apiError.message : String(apiError)}`, + ); + } +} + // Add global error handlers to prevent uncaught errors from crashing the process process.on("unhandledRejection", (reason, promise) => { // Extract useful information from the reason @@ -101,6 +131,10 @@ process.on("unhandledRejection", (reason, promise) => { // If reason is an Error, use it directly for better stack traces if (reason instanceof Error) { logger.error("Unhandled Promise Rejection", reason, errorDetails); + // Report to API if running in serve mode + reportUnhandledErrorToApi(reason).catch(() => { + // Silently fail if API reporting errors - already logged in helper + }); } else { // Convert non-Error reasons to Error for consistent handling const error = new Error(`Unhandled rejection: ${String(reason)}`); @@ -108,6 +142,10 @@ process.on("unhandledRejection", (reason, promise) => { ...errorDetails, originalReason: String(reason), }); + // Report to API if running in serve mode + reportUnhandledErrorToApi(error).catch(() => { + // Silently fail if API reporting errors - already logged in helper + }); } // Note: Sentry capture is handled by logger.error() above @@ -116,6 +154,10 @@ process.on("unhandledRejection", (reason, promise) => { process.on("uncaughtException", (error) => { logger.error("Uncaught Exception:", error); + // Report to API if running in serve mode + reportUnhandledErrorToApi(error).catch(() => { + // Silently fail if API reporting errors - already logged in helper + }); // Note: Sentry capture is handled by logger.error() above // Don't exit the process, just log the error });