diff --git a/packages/server/src/server/agent/mcp-server.test.ts b/packages/server/src/server/agent/mcp-server.test.ts index f8f763684..05a9f3b6d 100644 --- a/packages/server/src/server/agent/mcp-server.test.ts +++ b/packages/server/src/server/agent/mcp-server.test.ts @@ -22,6 +22,7 @@ import { import type { CreatePaseoWorktreeWorkflowFn } from "../worktree-session.js"; import { WorkspaceGitServiceImpl } from "../workspace-git-service.js"; import type { GitHubService } from "../../services/github-service.js"; +import type { TerminalManager } from "../../terminal/terminal-manager.js"; interface LooseSafeParseResult { success: boolean; @@ -136,6 +137,23 @@ function createTestDeps(): TestDeps { }; } +function createTerminalManagerStub(overrides: Partial = {}): TerminalManager { + return { + getTerminals: vi.fn().mockResolvedValue([]), + createTerminal: vi.fn(), + registerCwdEnv: vi.fn(), + getTerminal: vi.fn(), + getTerminalState: vi.fn().mockResolvedValue(null), + killTerminal: vi.fn(), + killTerminalAndWait: vi.fn().mockResolvedValue(undefined), + captureTerminal: vi.fn().mockResolvedValue({ lines: [], totalLines: 0 }), + listDirectories: vi.fn().mockReturnValue([]), + killAll: vi.fn(), + subscribeTerminalsChanged: vi.fn().mockReturnValue(() => {}), + ...overrides, + } as unknown as TerminalManager; +} + function createProviderDefinition(overrides: Partial): ProviderDefinition { const provider = overrides.id ?? "claude"; return { @@ -339,6 +357,53 @@ function createPaseoWorktreeForMcpTest(options: { }; } +describe("terminal MCP tools", () => { + const logger = createTestLogger(); + + it("captures terminal output through the terminal manager authority", async () => { + const { agentManager, agentStorage } = createTestDeps(); + const captureTerminal = vi.fn().mockResolvedValue({ + lines: ["from worker scrollback"], + totalLines: 42, + }); + const terminalManager = createTerminalManagerStub({ + getTerminal: vi.fn().mockReturnValue({ + id: "term-1", + name: "daemon", + cwd: process.cwd(), + getState: vi.fn().mockReturnValue({ scrollback: [], grid: [[]] }), + }), + captureTerminal, + }); + const server = await createAgentMcpServer({ + agentManager, + agentStorage, + terminalManager, + logger, + }); + const tool = registeredTool(server, "capture_terminal"); + + const response = await tool.callback({ + terminalId: "term-1", + scrollback: true, + stripAnsi: false, + start: -10, + end: -1, + }); + + expect(captureTerminal).toHaveBeenCalledWith("term-1", { + start: 0, + end: -1, + stripAnsi: false, + }); + expect(response.structuredContent).toEqual({ + terminalId: "term-1", + lines: ["from worker scrollback"], + totalLines: 42, + }); + }); +}); + describe("create_agent MCP tool", () => { const logger = createTestLogger(); const existingCwd = process.cwd(); diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index 5ba6a6d4a..b158a0c22 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -39,7 +39,6 @@ import { scheduleAgentMetadataGeneration } from "./agent-metadata-generator.js"; import type { VoiceCallerContext, VoiceSpeakHandler } from "../voice-types.js"; import { expandUserPath, isSameOrDescendantPath, resolvePathFromBase } from "../path-utils.js"; import type { TerminalManager } from "../../terminal/terminal-manager.js"; -import { captureTerminalLines } from "../../terminal/terminal-capture.js"; import type { AgentWorktreeSetupContinuation, CreatePaseoWorktreeSetupContinuationInput, @@ -1433,12 +1432,11 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom throw new Error("Terminal manager is not configured"); } - const terminal = terminalManager.getTerminal(terminalId); - if (!terminal) { + if (!terminalManager.getTerminal(terminalId)) { throw new Error(`Terminal ${terminalId} not found`); } - const capture = captureTerminalLines(terminal, { + const capture = await terminalManager.captureTerminal(terminalId, { start: scrollback ? 0 : start, end, stripAnsi, diff --git a/packages/server/src/terminal/terminal-manager.ts b/packages/server/src/terminal/terminal-manager.ts index 9741bb139..2a01b2252 100644 --- a/packages/server/src/terminal/terminal-manager.ts +++ b/packages/server/src/terminal/terminal-manager.ts @@ -1,10 +1,5 @@ -import { - captureTerminalLines, - createTerminal, - type CaptureTerminalLinesResult, - type TerminalSession, - type TerminalStateSnapshot, -} from "./terminal.js"; +import { createTerminal, type TerminalSession, type TerminalStateSnapshot } from "./terminal.js"; +import { captureTerminalLines, type CaptureTerminalLinesResult } from "./terminal-capture.js"; import { resolve, sep, win32, posix } from "node:path"; export interface TerminalListItem { diff --git a/packages/server/src/terminal/terminal.ts b/packages/server/src/terminal/terminal.ts index 401758876..84bcbeee4 100644 --- a/packages/server/src/terminal/terminal.ts +++ b/packages/server/src/terminal/terminal.ts @@ -8,11 +8,6 @@ import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import { createExternalProcessEnv } from "../server/paseo-env.js"; import type { TerminalCell, TerminalState } from "../shared/messages.js"; -export { - captureTerminalLines, - type CaptureTerminalLinesOptions, - type CaptureTerminalLinesResult, -} from "./terminal-capture.js"; const { Terminal } = xterm; const require = createRequire(import.meta.url); diff --git a/packages/server/src/terminal/worker-terminal-manager.ts b/packages/server/src/terminal/worker-terminal-manager.ts index 2fa7a92c2..fc30088ee 100644 --- a/packages/server/src/terminal/worker-terminal-manager.ts +++ b/packages/server/src/terminal/worker-terminal-manager.ts @@ -4,13 +4,13 @@ import { randomUUID } from "node:crypto"; import type { TerminalState } from "../shared/messages.js"; import type { ClientMessage, - CaptureTerminalLinesResult, ServerMessage, TerminalCommandFinishedInfo, TerminalExitInfo, TerminalSession, TerminalStateSnapshot, } from "./terminal.js"; +import type { CaptureTerminalLinesResult } from "./terminal-capture.js"; import type { TerminalListItem, TerminalManager,