mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
fix(server): emit valid MCP list_agents payloads
Explicit `undefined` keys on optional snapshot fields were converted to `null` by `ensureValidJson`, failing `AgentSnapshotPayloadSchema` validation on the wire. Stop writing those keys so they remain absent, which is what `.optional()` expects. Also adds a test that parses `list_agents` output through the schema to catch this class of bug.
This commit is contained in:
@@ -94,7 +94,7 @@ export function toAgentPayload(
|
||||
model: agent.config.model ?? null,
|
||||
thinkingOptionId,
|
||||
effectiveThinkingOptionId,
|
||||
runtimeInfo,
|
||||
...(runtimeInfo ? { runtimeInfo } : {}),
|
||||
createdAt: agent.createdAt.toISOString(),
|
||||
updatedAt: agent.updatedAt.toISOString(),
|
||||
lastUserMessageAt: agent.lastUserMessageAt ? agent.lastUserMessageAt.toISOString() : null,
|
||||
@@ -192,8 +192,6 @@ export function buildStoredAgentPayload(
|
||||
availableModes: [],
|
||||
pendingPermissions: [],
|
||||
persistence: toAgentPersistenceHandle(logger, providerRegistry, record.persistence),
|
||||
lastUsage: undefined,
|
||||
lastError: undefined,
|
||||
title: record.title ?? record.config?.title ?? null,
|
||||
requiresAttention: record.requiresAttention ?? false,
|
||||
attentionReason: record.attentionReason ?? null,
|
||||
|
||||
@@ -2,12 +2,14 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTestLogger } from "../../test-utils/test-logger.js";
|
||||
import { createAgentMcpServer } from "./mcp-server.js";
|
||||
import type { AgentManager, ManagedAgent } from "./agent-manager.js";
|
||||
import type { AgentStorage, StoredAgentRecord } from "./agent-storage.js";
|
||||
import type { ProviderDefinition } from "./provider-registry.js";
|
||||
import { AgentSnapshotPayloadSchema } from "../../shared/messages.js";
|
||||
|
||||
type TestDeps = {
|
||||
agentManager: AgentManager;
|
||||
@@ -702,6 +704,59 @@ describe("agent snapshot MCP serialization", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("emits list_agents payloads that satisfy the declared output schema", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
const liveAgent = {
|
||||
id: "live-agent",
|
||||
provider: "claude",
|
||||
cwd: "/tmp/live-project",
|
||||
config: {},
|
||||
runtimeInfo: undefined,
|
||||
createdAt: new Date("2026-04-11T00:00:00.000Z"),
|
||||
updatedAt: new Date("2026-04-11T00:00:00.000Z"),
|
||||
lastUserMessageAt: null,
|
||||
lifecycle: "idle",
|
||||
capabilities: {
|
||||
supportsStreaming: false,
|
||||
supportsSessionPersistence: false,
|
||||
supportsDynamicModes: false,
|
||||
supportsMcpServers: true,
|
||||
supportsReasoningStream: false,
|
||||
supportsToolInvocations: true,
|
||||
},
|
||||
currentModeId: null,
|
||||
availableModes: [],
|
||||
features: [],
|
||||
pendingPermissions: new Map(),
|
||||
persistence: null,
|
||||
labels: {},
|
||||
attention: { requiresAttention: false },
|
||||
} as unknown as ManagedAgent;
|
||||
spies.agentManager.listAgents.mockReturnValue([liveAgent]);
|
||||
spies.agentStorage.list.mockResolvedValue([
|
||||
createStoredRecord({ id: "stored-non-archived", archivedAt: null }),
|
||||
createStoredRecord({ id: "stored-archived", archivedAt: "2026-04-12T00:00:00.000Z" }),
|
||||
]);
|
||||
|
||||
const server = await createAgentMcpServer({
|
||||
agentManager,
|
||||
agentStorage,
|
||||
logger,
|
||||
providerRegistry: {
|
||||
claude: createProviderDefinition({}),
|
||||
} as any,
|
||||
});
|
||||
const tool = (server as any)._registeredTools["list_agents"];
|
||||
const response = await tool.callback({ includeArchived: true });
|
||||
|
||||
const parsed = z.array(AgentSnapshotPayloadSchema).safeParse(response.structuredContent.agents);
|
||||
if (!parsed.success) {
|
||||
throw new Error(
|
||||
`list_agents response failed AgentSnapshotPayloadSchema: ${JSON.stringify(parsed.error.issues, null, 2)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("loads archived agents before reading get_agent_activity", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
const record = createStoredRecord({ id: "archived-activity-agent" });
|
||||
|
||||
@@ -167,7 +167,7 @@ export function toAgentPersistenceHandle(
|
||||
return {
|
||||
provider,
|
||||
sessionId: handle.sessionId,
|
||||
nativeHandle: handle.nativeHandle,
|
||||
metadata: handle.metadata,
|
||||
...(handle.nativeHandle !== undefined ? { nativeHandle: handle.nativeHandle } : {}),
|
||||
...(handle.metadata !== undefined ? { metadata: handle.metadata } : {}),
|
||||
} satisfies AgentPersistenceHandle;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user