Skip to content
Open
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
49 changes: 44 additions & 5 deletions extensions/cli/src/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { messageQueue } from "../stream/messageQueue.js";
import { constructSystemMessage } from "../systemMessage.js";
import { telemetryService } from "../telemetry/telemetryService.js";
import { reportFailureTool } from "../tools/reportFailure.js";
import { gracefulExit } from "../util/exit.js";
import { gracefulExit, updateAgentMetadata } from "../util/exit.js";
import { formatError } from "../util/formatError.js";
import { getGitDiffSnapshot } from "../util/git.js";
import { logger } from "../util/logger.js";
Expand Down Expand Up @@ -351,9 +351,18 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
}

// Give a moment for the response to be sent
const handleExitResponse = () => {
server.close(() => {
const handleExitResponse = async () => {
server.close(async () => {
telemetryService.stopActiveTime();

// Update metadata one final time before exiting
try {
const history = services.chatHistory?.getHistory();
await updateAgentMetadata(history);
} catch (err) {
logger.debug("Failed to update metadata (non-critical)", err as any);
}

gracefulExit(0).catch((err) => {
logger.error(`Graceful exit failed: ${formatError(err)}`);
process.exit(1);
Expand Down Expand Up @@ -466,6 +475,18 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
// No direct persistence here; ChatHistoryService handles persistence when appropriate

state.lastActivity = Date.now();

// Update metadata after successful agent turn
try {
const history = services.chatHistory?.getHistory();
await updateAgentMetadata(history);
} catch (metadataErr) {
// Non-critical: log but don't fail the agent execution
logger.debug(
"Failed to update metadata after turn (non-critical)",
metadataErr as any,
);
}
} catch (e: any) {
if (e.name === "AbortError") {
logger.debug("Response interrupted");
Expand Down Expand Up @@ -522,8 +543,17 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
);
state.serverRunning = false;
stopStorageSync();
server.close(() => {
server.close(async () => {
telemetryService.stopActiveTime();

// Update metadata one final time before exiting
try {
const history = services.chatHistory?.getHistory();
await updateAgentMetadata(history);
} catch (err) {
logger.debug("Failed to update metadata (non-critical)", err as any);
}

gracefulExit(0).catch((err) => {
logger.error(`Graceful exit failed: ${formatError(err)}`);
process.exit(1);
Expand All @@ -545,8 +575,17 @@ export async function serve(prompt?: string, options: ServeOptions = {}) {
clearInterval(inactivityChecker);
inactivityChecker = null;
}
server.close(() => {
server.close(async () => {
telemetryService.stopActiveTime();

// Update metadata one final time before exiting
try {
const history = services.chatHistory?.getHistory();
await updateAgentMetadata(history);
} catch (err) {
logger.debug("Failed to update metadata (non-critical)", err as any);
}

gracefulExit(0).catch((err) => {
logger.error(`Graceful exit failed: ${formatError(err)}`);
process.exit(1);
Expand Down
67 changes: 67 additions & 0 deletions extensions/cli/src/util/exit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,74 @@
import type { ChatHistoryItem } from "core/index.js";

import { sentryService } from "../sentry.js";
import { telemetryService } from "../telemetry/telemetryService.js";

import { getGitDiffSnapshot } from "./git.js";
import { logger } from "./logger.js";
import {
calculateDiffStats,
extractSummary,
getAgentIdFromArgs,
postAgentMetadata,
} from "./metadata.js";

/**
* Update agent session metadata in control plane
* Collects diff stats and conversation summary, posts to control plane
* This is called both during execution (after each turn) and before exit
*
* @param history - Chat history to extract summary from (optional, will fetch if not provided)
*/
export async function updateAgentMetadata(
history?: ChatHistoryItem[],
): Promise<void> {
try {
const agentId = getAgentIdFromArgs();
if (!agentId) {
logger.debug("No agent ID found, skipping metadata update");
return;
}

const metadata: Record<string, any> = {};

// Calculate diff stats
try {
const { diff, repoFound } = await getGitDiffSnapshot();
if (repoFound && diff) {
const { additions, deletions } = calculateDiffStats(diff);
if (additions > 0 || deletions > 0) {
metadata.additions = additions;
metadata.deletions = deletions;
}
}
} catch (err) {
logger.debug("Failed to calculate diff stats (non-critical)", err as any);
}

// Extract summary from conversation
if (history && history.length > 0) {
try {
const summary = extractSummary(history);
if (summary) {
metadata.summary = summary;
}
} catch (err) {
logger.debug(
"Failed to extract conversation summary (non-critical)",
err as any,
);
}
}

// Post metadata if we have any
if (Object.keys(metadata).length > 0) {
await postAgentMetadata(agentId, metadata);
}
} catch (err) {
// Non-critical: log but don't fail the process
logger.debug("Error in updateAgentMetadata (ignored)", err as any);
}
}

/**
* Exit the process after flushing telemetry and error reporting.
Expand Down
Loading
Loading