diff --git a/.gitignore b/.gitignore index 76ca8866f..e4cb12ea2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,8 +43,3 @@ coverage/ # Misc *.pem .vercel - -# Runtime agent data -packages/server/agents.json -packages/server/agents.json.temporary -packages/server/.agents.json.tmp-* diff --git a/packages/server/.env.example b/packages/server/.env.example index 4ea23184b..174d94e4a 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -8,5 +8,12 @@ TTS_MODEL=tts-1 # Available models: tts-1 (faster), tts-1-hd (higher quality) # Server Configuration +# Location for runtime state (agents.json, etc.). Defaults to ~/.paseo +PASEO_HOME=~/.paseo + +# Voice server port (overrides PORT). Defaults to 6767 +PASEO_PORT=6767 + +# Legacy web server port used by the legacy Express app PORT=3000 NODE_ENV=development diff --git a/packages/server/src/server/agent/agent-registry.ts b/packages/server/src/server/agent/agent-registry.ts index cd84ce04b..0ca4bd171 100644 --- a/packages/server/src/server/agent/agent-registry.ts +++ b/packages/server/src/server/agent/agent-registry.ts @@ -1,11 +1,10 @@ import { randomUUID } from "node:crypto"; -import { existsSync } from "node:fs"; import { promises as fs } from "node:fs"; import path from "node:path"; -import { fileURLToPath } from "node:url"; import { z } from "zod"; import { AgentStatusSchema } from "../messages.js"; +import { resolvePaseoHome } from "../config.js"; import { toStoredAgentRecord } from "./agent-projections.js"; import type { ManagedAgent } from "./agent-manager.js"; import type { AgentSessionConfig } from "./agent-sdk-types.js"; @@ -57,8 +56,7 @@ export class AgentRegistry { private filePath: string; constructor(filePath?: string) { - this.filePath = - filePath ?? path.join(resolveServerPackageRoot(), "agents.json"); + this.filePath = filePath ?? path.join(resolvePaseoHome(), "agents.json"); } async load(): Promise { @@ -188,22 +186,6 @@ export class AgentRegistry { } } -function resolveServerPackageRoot(): string { - let currentDir = path.dirname(fileURLToPath(import.meta.url)); - while (true) { - if (existsSync(path.join(currentDir, "package.json"))) { - return currentDir; - } - const parentDir = path.dirname(currentDir); - if (parentDir === currentDir) { - throw new Error( - "[AgentRegistry] Failed to locate server package root for agents.json" - ); - } - currentDir = parentDir; - } -} - async function writeFileAtomically(targetPath: string, payload: string) { const directory = path.dirname(targetPath); const tempPath = path.join( diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 1960901b5..1d7792d39 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -38,6 +38,7 @@ import type { ListPersistedAgentsOptions, PersistedAgentDescriptor, } from "../agent-sdk-types.js"; +import { resolvePaseoPort } from "../../config.js"; const CLAUDE_CAPABILITIES: AgentCapabilityFlags = { supportsStreaming: true, @@ -92,7 +93,7 @@ type ClaudeAgentSessionOptions = { }; const DEFAULT_AGENT_CONTROL_MCP: AgentControlMcpConfig = { - url: "http://127.0.0.1:6767/mcp/agents", + url: `http://127.0.0.1:${resolvePaseoPort()}/mcp/agents`, headers: { Authorization: "Basic bW86Ym8=", }, diff --git a/packages/server/src/server/config.ts b/packages/server/src/server/config.ts new file mode 100644 index 000000000..9bdce5138 --- /dev/null +++ b/packages/server/src/server/config.ts @@ -0,0 +1,37 @@ +import os from "node:os"; +import path from "node:path"; +import { mkdirSync } from "node:fs"; + +let cachedHomeDir: string | null = null; +let cachedPort: number | null = null; + +function expandHomeDir(input: string): string { + if (input.startsWith("~/")) { + return path.join(os.homedir(), input.slice(2)); + } + if (input === "~") { + return os.homedir(); + } + return input; +} + +export function resolvePaseoHome(): string { + if (cachedHomeDir) { + return cachedHomeDir; + } + const raw = process.env.PASEO_HOME ?? process.env.PASEO_HOME_DIR ?? "~/.paseo"; + const expanded = path.resolve(expandHomeDir(raw)); + mkdirSync(expanded, { recursive: true }); + cachedHomeDir = expanded; + return cachedHomeDir; +} + +export function resolvePaseoPort(): number { + if (cachedPort !== null) { + return cachedPort; + } + const raw = process.env.PASEO_PORT ?? process.env.PORT ?? "6767"; + const parsed = Number.parseInt(raw, 10); + cachedPort = Number.isFinite(parsed) ? parsed : 6767; + return cachedPort; +} diff --git a/packages/server/src/server/index.ts b/packages/server/src/server/index.ts index a0206b689..25dc98dcc 100644 --- a/packages/server/src/server/index.ts +++ b/packages/server/src/server/index.ts @@ -11,6 +11,7 @@ import { AgentManager } from "./agent/agent-manager.js"; import { AgentRegistry } from "./agent/agent-registry.js"; import { ClaudeAgentClient } from "./agent/providers/claude-agent.js"; import { CodexAgentClient } from "./agent/providers/codex-agent.js"; +import { resolvePaseoPort } from "./config.js"; import { initializeTitleGenerator } from "../services/agent-title-generator.js"; import { attachAgentRegistryPersistence } from "./persistence-hooks.js"; import { createAgentMcpServer } from "./agent/mcp-server.js"; @@ -73,7 +74,7 @@ function createServer() { } async function main() { - const port = parseInt(process.env.PORT || "6767", 10); + const port = resolvePaseoPort(); const agentMcpRoute = "/mcp/agents"; const agentMcpUrl = `http://127.0.0.1:${port}${agentMcpRoute}`; const [agentMcpUser, agentMcpPassword] = diff --git a/test-mcp-call.mjs b/test-mcp-call.mjs deleted file mode 100755 index c34f977c0..000000000 --- a/test-mcp-call.mjs +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env node - -const CLAUDE_AGENT_ID = 'f7644b18-7fb9-4491-80f3-788d0d27a119'; -const CODEX_AGENT_ID = 'd7c360ea-1579-4091-b224-ee8442400823'; - -const url = 'http://127.0.0.1:6767/mcp/agents'; -const auth = Buffer.from('mo:bo').toString('base64'); - -async function makeRequest(method, params) { - console.log(`\n[TEST] Calling ${method} with params:`, JSON.stringify(params, null, 2)); - const startTime = Date.now(); - - try { - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Basic ${auth}` - }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: Date.now(), - method: 'tools/call', - params: { - name: method, - arguments: params - } - }) - }); - - const elapsed = Date.now() - startTime; - console.log(`[TEST] Response received in ${elapsed}ms`); - - const data = await response.json(); - console.log(`[TEST] Response:`, JSON.stringify(data, null, 2)); - return data; - } catch (error) { - const elapsed = Date.now() - startTime; - console.error(`[TEST] Request failed after ${elapsed}ms:`, error.message); - throw error; - } -} - -async function initialize() { - console.log('[TEST] Initializing MCP session...'); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Basic ${auth}` - }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 1, - method: 'initialize', - params: { - protocolVersion: '2024-11-05', - capabilities: {}, - clientInfo: { - name: 'test-client', - version: '1.0.0' - } - } - }) - }); - const data = await response.json(); - console.log('[TEST] Initialized:', JSON.stringify(data, null, 2)); -} - -async function main() { - console.log('=== Testing MCP Agent Control Tools ===\n'); - - // Initialize session - await initialize(); - - // Test 1: Get status of Codex agent (should work) - console.log('\n--- Test 1: get_agent_status on Codex agent ---'); - await makeRequest('get_agent_status', { agentId: CODEX_AGENT_ID }); - - // Test 2: Get status of Claude agent (might hang) - console.log('\n--- Test 2: get_agent_status on Claude agent ---'); - await makeRequest('get_agent_status', { agentId: CLAUDE_AGENT_ID }); - - console.log('\n=== All tests complete ==='); -} - -main().catch(console.error);