diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index 8f57f0ac7..da0884edd 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -9,7 +9,7 @@ import type { } from "./agent-sdk-types.js"; import type { AgentManager, - AgentSnapshot, + ManagedAgent, WaitForAgentResult, } from "./agent-manager.js"; import { @@ -19,6 +19,7 @@ import { AgentSnapshotPayloadSchema, serializeAgentSnapshot, } from "../messages.js"; +import { toAgentPayload } from "./agent-projections.js"; import { curateAgentActivity } from "./activity-curator.js"; import { AGENT_PROVIDER_DEFINITIONS } from "./provider-manifest.js"; import { AgentRegistry } from "./agent-registry.js"; @@ -90,7 +91,7 @@ async function resolveAgentTitle( async function serializeSnapshotWithMetadata( agentRegistry: AgentRegistry, - snapshot: AgentSnapshot + snapshot: ManagedAgent ) { const title = await resolveAgentTitle(agentRegistry, snapshot.id); return serializeAgentSnapshot(snapshot, { title }); @@ -232,7 +233,7 @@ export async function createAgentMcpServer( structuredContent: ensureValidJson({ agentId: snapshot.id, type: provider, - status: snapshot.status, + status: snapshot.lifecycle, cwd: snapshot.cwd, currentModeId: snapshot.currentModeId, availableModes: snapshot.availableModes, @@ -394,7 +395,7 @@ export async function createAgentMcpServer( const responseData = { success: true, - status: snapshot?.status ?? "idle", + status: snapshot?.lifecycle ?? "idle", lastMessage: null, permission: null, }; @@ -435,7 +436,7 @@ export async function createAgentMcpServer( return { content: [], structuredContent: ensureValidJson({ - status: snapshot.status, + status: snapshot.lifecycle, snapshot: structuredSnapshot, }), }; @@ -616,13 +617,14 @@ export async function createAgentMcpServer( }, }, async () => { - const permissions = agentManager.listAgents().flatMap((agent) => - agent.pendingPermissions.map((request) => ({ + const permissions = agentManager.listAgents().flatMap((agent) => { + const payload = toAgentPayload(agent); + return payload.pendingPermissions.map((request) => ({ agentId: agent.id, - status: agent.status, + status: payload.status, request, - })) - ); + })); + }); return { content: [], diff --git a/packages/server/src/server/messages.ts b/packages/server/src/server/messages.ts index 23e0475fa..07da81618 100644 --- a/packages/server/src/server/messages.ts +++ b/packages/server/src/server/messages.ts @@ -1,8 +1,9 @@ import { z } from "zod"; import { AGENT_LIFECYCLE_STATUSES, - type AgentSnapshot, + type ManagedAgent, } from "./agent/agent-manager.js"; +import { toAgentPayload } from "./agent/agent-projections.js"; import type { AgentCapabilityFlags, AgentModelDefinition, @@ -16,16 +17,6 @@ import type { AgentUsage, } from "./agent/agent-sdk-types.js"; -export type AgentSnapshotPayload = Omit< - AgentSnapshot, - "createdAt" | "updatedAt" | "lastUserMessageAt" -> & { - createdAt: string; - updatedAt: string; - lastUserMessageAt: string | null; - title: string | null; -}; - const AGENT_PROVIDERS: [AgentProvider, AgentProvider] = ["claude", "codex"]; const AgentProviderSchema = z.enum(AGENT_PROVIDERS); @@ -230,96 +221,17 @@ export const AgentSnapshotPayloadSchema = z.object({ title: z.string().nullable(), }); -function sanitizeOptionalJson(value: unknown): unknown { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - if (Array.isArray(value)) { - return value - .map((item) => sanitizeOptionalJson(item)) - .filter((item) => item !== undefined); - } - if (value instanceof Date) { - return value.toISOString(); - } - if (typeof value === "object") { - const result: Record = {}; - for (const [key, val] of Object.entries(value as Record)) { - const sanitized = sanitizeOptionalJson(val); - if (sanitized !== undefined) { - result[key] = sanitized; - } - } - return Object.keys(result).length ? result : undefined; - } - return value; -} - -function sanitizeOptionalJsonValue(value: T | undefined): T | undefined { - const sanitized = sanitizeOptionalJson(value); - return sanitized === undefined ? undefined : (sanitized as T); -} - -function sanitizePersistenceHandle( - handle: AgentPersistenceHandle | null -): AgentPersistenceHandle | null { - if (!handle) { - return null; - } - const sanitized: AgentPersistenceHandle = { - provider: handle.provider, - sessionId: handle.sessionId, - }; - if (handle.nativeHandle !== undefined) { - sanitized.nativeHandle = handle.nativeHandle; - } - const metadata = sanitizeOptionalJsonValue(handle.metadata); - if (metadata !== undefined) { - sanitized.metadata = metadata; - } - return sanitized; -} +export type AgentSnapshotPayload = z.infer; export type AgentStreamEventPayload = z.infer< typeof AgentStreamEventPayloadSchema >; export function serializeAgentSnapshot( - snapshot: AgentSnapshot, + agent: ManagedAgent, options?: { title?: string | null } ): AgentSnapshotPayload { - const payload: AgentSnapshotPayload = { - id: snapshot.id, - provider: snapshot.provider, - cwd: snapshot.cwd, - model: snapshot.model, - createdAt: snapshot.createdAt.toISOString(), - updatedAt: snapshot.updatedAt.toISOString(), - lastUserMessageAt: snapshot.lastUserMessageAt - ? snapshot.lastUserMessageAt.toISOString() - : null, - status: snapshot.status, - sessionId: snapshot.sessionId, - capabilities: snapshot.capabilities, - currentModeId: snapshot.currentModeId, - availableModes: snapshot.availableModes, - pendingPermissions: snapshot.pendingPermissions, - persistence: sanitizePersistenceHandle(snapshot.persistence), - title: options?.title ?? null, - }; - - const lastUsage = sanitizeOptionalJsonValue(snapshot.lastUsage); - if (lastUsage !== undefined) { - payload.lastUsage = lastUsage; - } - if (snapshot.lastError !== undefined) { - payload.lastError = snapshot.lastError; - } - - return payload; + return toAgentPayload(agent, options); } export function serializeAgentStreamEvent( diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index f54938c24..8e13aea60 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -12,7 +12,6 @@ import { OpenRouterProviderOptions, } from "@openrouter/ai-sdk-provider"; import { - serializeAgentSnapshot, serializeAgentStreamEvent, type AgentSnapshotPayload, type SessionInboundMessage, @@ -39,7 +38,8 @@ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { createTerminalMcpServer } from "./terminal-mcp/index.js"; import { fetchProviderModelCatalog } from "./agent/model-catalog.js"; import { AgentManager } from "./agent/agent-manager.js"; -import type { AgentSnapshot } from "./agent/agent-manager.js"; +import type { ManagedAgent } from "./agent/agent-manager.js"; +import { toAgentPayload } from "./agent/agent-projections.js"; import type { AgentPermissionResponse, AgentPromptInput, @@ -234,7 +234,7 @@ export class Session { private unsubscribeAgentEvents: (() => void) | null = null; private pendingAgentInitializations: Map< string, - Promise + Promise > = new Map(); constructor( @@ -325,7 +325,7 @@ export class Session { throw new Error(`Agent ${agentId} not found`); } - if (snapshot.status !== "running") { + if (snapshot.lifecycle !== "running") { return; } @@ -532,10 +532,10 @@ export class Session { } private async buildAgentPayload( - agent: AgentSnapshot + agent: ManagedAgent ): Promise { const title = await this.getStoredAgentTitle(agent.id); - return serializeAgentSnapshot(agent, { title }); + return toAgentPayload(agent, { title }); } private buildStoredAgentPayload(record: StoredAgentRecord): AgentSnapshotPayload { @@ -576,7 +576,7 @@ export class Session { }; } - private async ensureAgentLoaded(agentId: string): Promise { + private async ensureAgentLoaded(agentId: string): Promise { const existing = this.agentManager.getAgent(agentId); if (existing) { return existing; @@ -594,7 +594,7 @@ export class Session { } const handle = toAgentPersistenceHandle(record.persistence); - let snapshot: AgentSnapshot; + let snapshot: ManagedAgent; if (handle) { snapshot = await this.agentManager.resumeAgent( handle, @@ -619,7 +619,7 @@ export class Session { } } - private async forwardAgentState(agent: AgentSnapshot): Promise { + private async forwardAgentState(agent: ManagedAgent): Promise { try { const payload = await this.buildAgentPayload(agent); this.emit({ @@ -1189,7 +1189,7 @@ export class Session { payload: { status: "agent_initialized", agentId, - agentStatus: snapshot.status, + agentStatus: snapshot.lifecycle, requestId, timelineSize, }, @@ -1197,7 +1197,7 @@ export class Session { } console.log( - `[Session ${this.clientId}] Agent ${agentId} initialized with ${timelineSize} timeline item(s); status=${snapshot.status}` + `[Session ${this.clientId}] Agent ${agentId} initialized with ${timelineSize} timeline item(s); status=${snapshot.lifecycle}` ); } catch (error: any) { console.error( @@ -1370,7 +1370,7 @@ export class Session { ); try { - let snapshot: AgentSnapshot; + let snapshot: ManagedAgent; const existing = this.agentManager.getAgent(agentId); if (existing) { await this.interruptAgentIfRunning(agentId); @@ -2048,7 +2048,7 @@ export class Session { } } - private emitAgentTimelineSnapshot(agent: AgentSnapshot): number { + private emitAgentTimelineSnapshot(agent: ManagedAgent): number { const timeline = this.agentManager.getTimeline(agent.id); const events = timeline.map((item) => ({ event: serializeAgentStreamEvent({