Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions extensions/cli/src/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
42 changes: 42 additions & 0 deletions extensions/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<void> {
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
Expand All @@ -101,13 +131,21 @@ 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)}`);
logger.error("Unhandled Promise Rejection", error, {
...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
Expand All @@ -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
});
Expand Down
Loading