diff --git a/packages/server/src/server/agent/agent-manager.test.ts b/packages/server/src/server/agent/agent-manager.test.ts index c64dfbf75..527d4c939 100644 --- a/packages/server/src/server/agent/agent-manager.test.ts +++ b/packages/server/src/server/agent/agent-manager.test.ts @@ -4,6 +4,7 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { randomUUID } from "node:crypto"; +import { createTestLogger } from "../../test-utils/test-logger.js"; import { AgentManager } from "./agent-manager.js"; import { AgentRegistry } from "./agent-registry.js"; import type { @@ -101,15 +102,18 @@ class TestAgentSession implements AgentSession { } describe("AgentManager", () => { + const logger = createTestLogger(); + test("normalizeConfig does not inject default model when omitted", async () => { const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-")); const registryPath = join(workdir, "agents.json"); - const registry = new AgentRegistry(registryPath); + const registry = new AgentRegistry(registryPath, logger); const manager = new AgentManager({ clients: { codex: new TestAgentClient(), }, registry, + logger, idFactory: () => "agent-without-model", }); @@ -124,12 +128,13 @@ describe("AgentManager", () => { test("createAgent persists provided title before returning", async () => { const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-")); const registryPath = join(workdir, "agents.json"); - const registry = new AgentRegistry(registryPath); + const registry = new AgentRegistry(registryPath, logger); const manager = new AgentManager({ clients: { codex: new TestAgentClient(), }, registry, + logger, idFactory: () => "agent-with-title", }); @@ -150,12 +155,13 @@ describe("AgentManager", () => { test("createAgent populates runtimeInfo after session creation", async () => { const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-")); const registryPath = join(workdir, "agents.json"); - const registry = new AgentRegistry(registryPath); + const registry = new AgentRegistry(registryPath, logger); const manager = new AgentManager({ clients: { codex: new TestAgentClient(), }, registry, + logger, idFactory: () => "agent-with-runtime-info", }); @@ -174,12 +180,13 @@ describe("AgentManager", () => { test("runAgent refreshes runtimeInfo after completion", async () => { const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-")); const registryPath = join(workdir, "agents.json"); - const registry = new AgentRegistry(registryPath); + const registry = new AgentRegistry(registryPath, logger); const manager = new AgentManager({ clients: { codex: new TestAgentClient(), }, registry, + logger, idFactory: () => "agent-with-run-runtime", }); diff --git a/packages/server/src/server/agent/agent-registry.test.ts b/packages/server/src/server/agent/agent-registry.test.ts index 43c271852..f98d5ba5e 100644 --- a/packages/server/src/server/agent/agent-registry.test.ts +++ b/packages/server/src/server/agent/agent-registry.test.ts @@ -3,6 +3,7 @@ import os from "node:os"; import path from "node:path"; import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs"; +import { createTestLogger } from "../../test-utils/test-logger.js"; import { AgentRegistry } from "./agent-registry.js"; import type { ManagedAgent } from "./agent-manager.js"; import type { @@ -94,11 +95,12 @@ describe("AgentRegistry", () => { let tmpDir: string; let filePath: string; let registry: AgentRegistry; + const logger = createTestLogger(); beforeEach(() => { tmpDir = mkdtempSync(path.join(os.tmpdir(), "agent-registry-")); filePath = path.join(tmpDir, "agents.json"); - registry = new AgentRegistry(filePath); + registry = new AgentRegistry(filePath, logger); }); afterEach(() => { @@ -129,7 +131,7 @@ describe("AgentRegistry", () => { expect(record.lastModeId).toBe("coding"); expect(record.lastStatus).toBe("idle"); - const reloaded = new AgentRegistry(filePath); + const reloaded = new AgentRegistry(filePath, logger); const [persisted] = await reloaded.list(); expect(persisted.cwd).toBe("/tmp/project"); expect(persisted.config?.extra?.claude).toMatchObject({ maxThinkingTokens: 1024 }); @@ -172,7 +174,7 @@ describe("AgentRegistry", () => { const current = await registry.get("agent-2"); expect(current?.title).toBe("Fix Login Bug"); - const reloaded = new AgentRegistry(filePath); + const reloaded = new AgentRegistry(filePath, logger); const persisted = await reloaded.get("agent-2"); expect(persisted?.title).toBe("Fix Login Bug"); }); @@ -239,7 +241,7 @@ describe("AgentRegistry", () => { `${JSON.stringify(payload, null, 2)}\nGARBAGE-TRAILING` ); - const reloaded = new AgentRegistry(filePath); + const reloaded = new AgentRegistry(filePath, logger); const result = await reloaded.list(); expect(result).toHaveLength(1); expect(result[0]?.title).toBe("Recovered agent"); diff --git a/packages/server/src/server/agent/mcp-server.test.ts b/packages/server/src/server/agent/mcp-server.test.ts index 5f49388d6..4bcdf9ced 100644 --- a/packages/server/src/server/agent/mcp-server.test.ts +++ b/packages/server/src/server/agent/mcp-server.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; +import { createTestLogger } from "../../test-utils/test-logger.js"; import { createAgentMcpServer } from "./mcp-server.js"; import type { AgentManager, ManagedAgent } from "./agent-manager.js"; import type { AgentRegistry } from "./agent-registry.js"; @@ -45,9 +46,11 @@ function createTestDeps(): TestDeps { } describe("create_agent MCP tool", () => { + const logger = createTestLogger(); + it("requires a concise title no longer than 40 characters", async () => { const { agentManager, agentRegistry } = createTestDeps(); - const server = await createAgentMcpServer({ agentManager, agentRegistry }); + const server = await createAgentMcpServer({ agentManager, agentRegistry, logger }); const tool = (server as any)._registeredTools["create_agent"]; expect(tool).toBeDefined(); @@ -84,7 +87,7 @@ describe("create_agent MCP tool", () => { availableModes: [], } as ManagedAgent); - const server = await createAgentMcpServer({ agentManager, agentRegistry }); + const server = await createAgentMcpServer({ agentManager, agentRegistry, logger }); const tool = (server as any)._registeredTools["create_agent"]; await tool.callback({ cwd: "/tmp/repo", diff --git a/packages/server/src/server/agent/providers/claude-agent.test.ts b/packages/server/src/server/agent/providers/claude-agent.test.ts index 950edcf2f..061494731 100644 --- a/packages/server/src/server/agent/providers/claude-agent.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.test.ts @@ -15,6 +15,7 @@ import express from "express"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; +import { createTestLogger } from "../../../test-utils/test-logger.js"; import { ClaudeAgentClient, convertClaudeHistoryEntry } from "./claude-agent.js"; import { useTempClaudeConfigDir } from "../../test-utils/claude-config.js"; import { @@ -106,16 +107,18 @@ type AgentMcpServerHandle = { }; async function startAgentMcpServer(): Promise { + const testLogger = createTestLogger(); const app = express(); app.use(express.json()); const httpServer = createHTTPServer(app); const registryDir = mkdtempSync(path.join(os.tmpdir(), "agent-mcp-registry-")); const registryPath = path.join(registryDir, "agents.json"); - const agentRegistry = new AgentRegistry(registryPath); + const agentRegistry = new AgentRegistry(registryPath, testLogger); const agentManager = new AgentManager({ clients: {}, registry: agentRegistry, + logger: testLogger, }); let allowedHosts: string[] | undefined; @@ -126,6 +129,7 @@ async function startAgentMcpServer(): Promise { agentManager, agentRegistry, callerAgentId, + logger: testLogger, }); const transport = new StreamableHTTPServerTransport({ @@ -221,6 +225,7 @@ async function startAgentMcpServer(): Promise { } describe("ClaudeAgentClient (SDK integration)", () => { + const logger = createTestLogger(); let agentMcpServer: AgentMcpServerHandle; let restoreClaudeConfigDir: (() => void) | null = null; const buildConfig = ( @@ -257,7 +262,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "responds with text", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024 }); const session = await client.createSession(config); @@ -278,7 +283,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "streams reasoning chunks", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 2048 }); const session = await client.createSession(config); @@ -310,7 +315,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "emits a single assistant message in the hydrated stream", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 2048 }); const session = await client.createSession(config); const updates: StreamHydrationUpdate[] = []; @@ -344,7 +349,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "shows the command inside pending tool calls", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 2048 }); const session = await client.createSession(config); @@ -381,7 +386,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "tracks permission + tool lifecycle when editing a file", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024 }); const session = await client.createSession(config); @@ -459,7 +464,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "permission flow parity - allows command after approval", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024, modeId: "default" }); const session = await client.createSession(config); const filePath = path.join(cwd, "permission.txt"); @@ -524,7 +529,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "permission flow parity - denies command execution", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024, modeId: "default" }); const session = await client.createSession(config); const filePath = path.join(cwd, "permission.txt"); @@ -590,7 +595,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "permission flow parity - aborts on interrupt response", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024, modeId: "default" }); const session = await client.createSession(config); const filePath = path.join(cwd, "permission.txt"); @@ -661,7 +666,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "interrupts a long-running bash command before it finishes", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 2048 }); let session: Awaited> | null = null; let runStartedAt: number | null = null; @@ -719,7 +724,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "supports multi-turn conversations", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 2048 }); const session = await client.createSession(config); @@ -741,7 +746,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "resumes a persisted session with context preserved", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024 }); const session = await client.createSession(config); @@ -822,7 +827,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "updates session modes", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024 }); const session = await client.createSession(config); @@ -847,7 +852,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "handles plan mode approval flow", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 2048 }); const session = await client.createSession(config); await session.setMode("plan"); @@ -889,7 +894,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "hydrates persisted tool call results into the UI stream", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 4096 }); const session = await client.createSession(config); const prompt = [ @@ -1020,7 +1025,7 @@ describe("ClaudeAgentClient (SDK integration)", () => { "hydrates user messages from persisted history", async () => { const cwd = tmpCwd(); - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const config = buildConfig(cwd, { maxThinkingTokens: 1024 }); const promptMarker = `HYDRATED_USER_${Date.now().toString(36)}`; @@ -1284,10 +1289,12 @@ function rawContainsText(raw: unknown, text: string, depth = 0): boolean { // in daemon.e2e.test.ts which exercises the full flow through the WebSocket API. describe("ClaudeAgentClient.listModels", () => { + const logger = createTestLogger(); + test( "returns models with required fields", async () => { - const client = new ClaudeAgentClient(); + const client = new ClaudeAgentClient({ logger }); const models = await client.listModels(); // HARD ASSERT: Returns an array diff --git a/packages/server/src/server/agent/wait-for-agent-tracker.test.ts b/packages/server/src/server/agent/wait-for-agent-tracker.test.ts index 3a2657eb0..53c2f401f 100644 --- a/packages/server/src/server/agent/wait-for-agent-tracker.test.ts +++ b/packages/server/src/server/agent/wait-for-agent-tracker.test.ts @@ -1,9 +1,12 @@ import { describe, expect, it, vi } from "vitest"; +import { createTestLogger } from "../../test-utils/test-logger.js"; import { WaitForAgentTracker } from "./wait-for-agent-tracker.js"; describe("WaitForAgentTracker", () => { + const logger = createTestLogger(); + it("registers and cancels waiters per agent", () => { - const tracker = new WaitForAgentTracker(); + const tracker = new WaitForAgentTracker(logger); const cancelA = vi.fn(); const cancelB = vi.fn(); @@ -26,7 +29,7 @@ describe("WaitForAgentTracker", () => { }); it("supports cancelling all waiters", () => { - const tracker = new WaitForAgentTracker(); + const tracker = new WaitForAgentTracker(logger); const cancelA = vi.fn(); const cancelB = vi.fn(); diff --git a/packages/server/src/test-utils/test-logger.ts b/packages/server/src/test-utils/test-logger.ts new file mode 100644 index 000000000..b8f00f4b9 --- /dev/null +++ b/packages/server/src/test-utils/test-logger.ts @@ -0,0 +1,5 @@ +import pino from "pino"; + +export function createTestLogger(): pino.Logger { + return pino({ level: "silent" }); +}