refactor: replace agent-control MCP with labels system and Paseo MCP

This commit is contained in:
Mohamed Boudra
2026-01-29 11:31:15 +07:00
parent dfda6daeed
commit c015fdbef1
40 changed files with 171 additions and 797 deletions
@@ -246,7 +246,6 @@ function normalizeAgentSnapshot(
requiresAttention: snapshot.requiresAttention ?? false, requiresAttention: snapshot.requiresAttention ?? false,
attentionReason: snapshot.attentionReason ?? null, attentionReason: snapshot.attentionReason ?? null,
attentionTimestamp, attentionTimestamp,
parentAgentId: snapshot.parentAgentId,
archivedAt, archivedAt,
}; };
} }
@@ -62,11 +62,6 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
} }
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId; const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
for (const agent of agents.values()) { for (const agent of agents.values()) {
// Use agent's own lastActivityAt field directly
// Skip child agents - only show root agents on homepage
if (agent.parentAgentId) {
continue;
}
const nextAgent: AggregatedAgent = { const nextAgent: AggregatedAgent = {
id: agent.id, id: agent.id,
serverId, serverId,
@@ -79,7 +74,6 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
requiresAttention: agent.requiresAttention, requiresAttention: agent.requiresAttention,
attentionReason: agent.attentionReason, attentionReason: agent.attentionReason,
attentionTimestamp: agent.attentionTimestamp, attentionTimestamp: agent.attentionTimestamp,
parentAgentId: agent.parentAgentId,
archivedAt: agent.archivedAt, archivedAt: agent.archivedAt,
}; };
allAgents.push(nextAgent); allAgents.push(nextAgent);
-2
View File
@@ -98,7 +98,6 @@ export interface Agent {
requiresAttention?: boolean; requiresAttention?: boolean;
attentionReason?: "finished" | "error" | "permission" | null; attentionReason?: "finished" | "error" | "permission" | null;
attentionTimestamp?: Date | null; attentionTimestamp?: Date | null;
parentAgentId?: string | null;
archivedAt?: Date | null; archivedAt?: Date | null;
} }
@@ -839,7 +838,6 @@ export const useSessionStore = create<SessionStore>()(
requiresAttention: agent.requiresAttention ?? false, requiresAttention: agent.requiresAttention ?? false,
attentionReason: agent.attentionReason ?? null, attentionReason: agent.attentionReason ?? null,
attentionTimestamp: agent.attentionTimestamp ?? null, attentionTimestamp: agent.attentionTimestamp ?? null,
parentAgentId: agent.parentAgentId,
}); });
} }
return entries; return entries;
@@ -12,6 +12,5 @@ export interface AgentDirectoryEntry {
requiresAttention?: boolean; requiresAttention?: boolean;
attentionReason?: "finished" | "error" | "permission" | null; attentionReason?: "finished" | "error" | "permission" | null;
attentionTimestamp?: Date | null; attentionTimestamp?: Date | null;
parentAgentId?: string | null;
archivedAt?: Date | null; archivedAt?: Date | null;
} }
@@ -16,7 +16,6 @@ function makeAgent(overrides: Partial<AggregatedAgent> = {}): AggregatedAgent {
requiresAttention: overrides.requiresAttention ?? false, requiresAttention: overrides.requiresAttention ?? false,
attentionReason: overrides.attentionReason ?? null, attentionReason: overrides.attentionReason ?? null,
attentionTimestamp: overrides.attentionTimestamp ?? null, attentionTimestamp: overrides.attentionTimestamp ?? null,
parentAgentId: overrides.parentAgentId ?? null,
} as AggregatedAgent; } as AggregatedAgent;
} }
@@ -1162,12 +1162,8 @@ const TOOL_NAME_MAP: Record<string, string> = {
read_file: "Read", read_file: "Read",
apply_patch: "Edit", apply_patch: "Edit",
paseo_worktree_setup: "Setup", paseo_worktree_setup: "Setup",
"agent-control.set_title": "Set title",
"agent-control.set_branch": "Set branch",
set_title: "Set title", set_title: "Set title",
set_branch: "Set branch", set_branch: "Set branch",
"mcp__agent-control__set_title": "Set title",
"mcp__agent-control__set_branch": "Set branch",
thinking: "Thinking", thinking: "Thinking",
}; };
+1 -1
View File
@@ -55,7 +55,7 @@ Notes:
### `packages/cli/src/commands/agent/inspect.ts` ### `packages/cli/src/commands/agent/inspect.ts`
Inline types: Inline types:
- `AgentSnapshotLike` (snapshot fields + `lastUsage`, `capabilities`, `availableModes`, `pendingPermissions`, `parentAgentId`) - `AgentSnapshotLike` (snapshot fields + `lastUsage`, `capabilities`, `availableModes`, `pendingPermissions`)
Recommended server types: Recommended server types:
- `AgentSnapshotPayload` (overall snapshot shape). **Not exported**. - `AgentSnapshotPayload` (overall snapshot shape). **Not exported**.
+5 -1
View File
@@ -37,9 +37,11 @@ export function createCli(): Command {
// Primary agent commands (top-level) // Primary agent commands (top-level)
program program
.command('ls') .command('ls')
.description('List agents. By default shows running agents in current directory.') .description('List agents. By default shows background agents (without ui=true) in current directory.')
.option('-a, --all', 'Include all statuses (not just running)') .option('-a, --all', 'Include all statuses (not just running)')
.option('-g, --global', 'Show agents from all directories (not just current)') .option('-g, --global', 'Show agents from all directories (not just current)')
.option('--label <key=value>', 'Filter by label (can be used multiple times)', collectMultiple, [])
.option('--ui', 'Show only UI agents (equivalent to --label ui=true)')
.option('--json', 'Output in JSON format') .option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)') .option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action((options, command) => { .action((options, command) => {
@@ -62,6 +64,8 @@ export function createCli(): Command {
.option('--base <branch>', 'Base branch for worktree (default: current branch)') .option('--base <branch>', 'Base branch for worktree (default: current branch)')
.option('--image <path>', 'Attach image(s) to the initial prompt (can be used multiple times)', collectMultiple, []) .option('--image <path>', 'Attach image(s) to the initial prompt (can be used multiple times)', collectMultiple, [])
.option('--cwd <path>', 'Working directory (default: current)') .option('--cwd <path>', 'Working directory (default: current)')
.option('--label <key=value>', 'Add label(s) to the agent (can be used multiple times)', collectMultiple, [])
.option('--ui', 'Mark as UI agent (equivalent to --label ui=true)')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)') .option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runRunCommand)) .action(withOutput(runRunCommand))
+10 -1
View File
@@ -14,12 +14,19 @@ import { withOutput } from '../../output/index.js'
export function createAgentCommand(): Command { export function createAgentCommand(): Command {
const agent = new Command('agent').description('Manage agents (advanced operations)') const agent = new Command('agent').description('Manage agents (advanced operations)')
// Helper function to collect multiple option values into an array
const collectMultiple = (value: string, previous: string[]): string[] => {
return previous.concat([value])
}
// Primary agent commands (same as top-level) // Primary agent commands (same as top-level)
agent agent
.command('ls') .command('ls')
.description('List agents. By default shows running agents in current directory.') .description('List agents. By default shows background agents (without ui=true) in current directory.')
.option('-a, --all', 'Include all statuses (not just running)') .option('-a, --all', 'Include all statuses (not just running)')
.option('-g, --global', 'Show agents from all directories (not just current)') .option('-g, --global', 'Show agents from all directories (not just current)')
.option('--label <key=value>', 'Filter by label (can be used multiple times)', collectMultiple, [])
.option('--ui', 'Show only UI agents (equivalent to --label ui=true)')
.option('--json', 'Output in JSON format') .option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)') .option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action((options, command) => { .action((options, command) => {
@@ -39,6 +46,8 @@ export function createAgentCommand(): Command {
.option('--model <model>', 'Model to use (e.g., claude-sonnet-4-20250514, claude-3-5-haiku-20241022)') .option('--model <model>', 'Model to use (e.g., claude-sonnet-4-20250514, claude-3-5-haiku-20241022)')
.option('--mode <mode>', 'Provider-specific mode (e.g., plan, default, bypass)') .option('--mode <mode>', 'Provider-specific mode (e.g., plan, default, bypass)')
.option('--cwd <path>', 'Working directory (default: current)') .option('--cwd <path>', 'Working directory (default: current)')
.option('--label <key=value>', 'Add label(s) to the agent (can be used multiple times)', collectMultiple, [])
.option('--ui', 'Mark as UI agent (equivalent to --label ui=true)')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)') .option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runRunCommand)) .action(withOutput(runRunCommand))
@@ -34,7 +34,6 @@ interface AgentInspect {
id: string id: string
tool: string tool: string
}> }>
parentAgentId: string | null
} }
/** Key-value row for table display */ /** Key-value row for table display */
@@ -122,7 +121,6 @@ function toInspectData(snapshot: AgentSnapshotPayload): AgentInspect {
id: p.id, id: p.id,
tool: p.name ?? 'unknown', tool: p.name ?? 'unknown',
})), })),
parentAgentId: snapshot.parentAgentId ?? null,
} }
} }
@@ -177,11 +175,6 @@ function toInspectRows(agent: AgentInspect): InspectRow[] {
rows.push({ key: 'PendingPermissions', value: '[]' }) rows.push({ key: 'PendingPermissions', value: '[]' })
} }
rows.push({
key: 'ParentAgentId',
value: agent.parentAgentId ?? 'null',
})
return rows return rows
} }
+44
View File
@@ -82,6 +82,10 @@ export interface AgentLsOptions extends CommandOptions {
status?: string status?: string
/** Filter by specific cwd (overrides default cwd filtering) */ /** Filter by specific cwd (overrides default cwd filtering) */
cwd?: string cwd?: string
/** Filter by labels (key=value format) */
label?: string[]
/** Filter to UI agents only (equivalent to --label ui=true) */
ui?: boolean
} }
/** /**
@@ -145,6 +149,46 @@ export async function runLsCommand(
}) })
} }
// Label filtering:
// Parse --label flags and --ui flag
// --ui is equivalent to --label ui=true
// By default (no --ui flag), show background agents (those WITHOUT ui=true)
const labelFilters: Record<string, string> = {}
if (options.label) {
for (const labelStr of options.label) {
const eqIndex = labelStr.indexOf('=')
if (eqIndex !== -1) {
const key = labelStr.slice(0, eqIndex)
const value = labelStr.slice(eqIndex + 1)
labelFilters[key] = value
}
}
}
// Add ui=true filter if --ui flag is set
if (options.ui) {
labelFilters['ui'] = 'true'
}
// Apply label filtering
if (Object.keys(labelFilters).length > 0) {
// Filter to agents that have ALL specified labels (AND semantics)
agents = agents.filter((a) => {
const agentLabels = (a as any).labels as Record<string, string> | undefined
for (const [key, value] of Object.entries(labelFilters)) {
if (!agentLabels || agentLabels[key] !== value) {
return false
}
}
return true
})
} else {
// Default: show background agents only (those without ui=true)
agents = agents.filter((a) => {
const agentLabels = (a as any).labels as Record<string, string> | undefined
return !agentLabels || agentLabels['ui'] !== 'true'
})
}
await client.close() await client.close()
// Sort agents: running first, then idle, then others; within each group, most recent first // Sort agents: running first, then idle, then others; within each group, most recent first
+28
View File
@@ -37,6 +37,8 @@ export interface AgentRunOptions extends CommandOptions {
base?: string base?: string
image?: string[] image?: string[]
cwd?: string cwd?: string
label?: string[]
ui?: boolean
} }
function toRunResult(agent: AgentSnapshotPayload): AgentRunResult { function toRunResult(agent: AgentSnapshotPayload): AgentRunResult {
@@ -127,6 +129,31 @@ export async function runRunCommand(
} }
: undefined : undefined
// Build labels from --label and --ui flags
// --ui is syntactic sugar for --label ui=true
// If explicit --label ui=... is provided, it takes precedence over --ui
const labels: Record<string, string> = {}
if (options.label) {
for (const labelStr of options.label) {
const eqIndex = labelStr.indexOf('=')
if (eqIndex === -1) {
const error: CommandError = {
code: 'INVALID_LABEL',
message: `Invalid label format: ${labelStr}`,
details: 'Labels must be in key=value format',
}
throw error
}
const key = labelStr.slice(0, eqIndex)
const value = labelStr.slice(eqIndex + 1)
labels[key] = value
}
}
// Add ui=true if --ui flag is set and ui label not already set
if (options.ui && !('ui' in labels)) {
labels['ui'] = 'true'
}
// Create the agent // Create the agent
const agent = await client.createAgent({ const agent = await client.createAgent({
provider: (options.provider as 'claude' | 'codex' | 'opencode') ?? 'claude', provider: (options.provider as 'claude' | 'codex' | 'opencode') ?? 'claude',
@@ -138,6 +165,7 @@ export async function runRunCommand(
images, images,
git, git,
worktreeName: options.worktree, worktreeName: options.worktree,
labels: Object.keys(labels).length > 0 ? labels : undefined,
}) })
await client.close() await client.close()
@@ -1,78 +0,0 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
async function main() {
// The bearer token is the base64-encoded credentials
const bearerToken = Buffer.from("mo:bo").toString("base64");
const transport = new StdioClientTransport({
command: "codex",
args: ["mcp-server"],
env: {
...process.env,
// Set the bearer token env var
PASEO_AGENT_CONTROL_TOKEN: bearerToken
},
});
const client = new Client(
{ name: "test-client", version: "1.0.0" },
{ capabilities: { elicitation: {} } }
);
// Listen for events
client.setNotificationHandler(
z.object({
method: z.literal("codex/event"),
params: z.object({ msg: z.any() }),
}).passthrough(),
(data) => {
const event = (data.params as { msg: unknown }).msg as { type?: string };
if (event.type === "mcp_startup_update" || event.type === "mcp_startup_complete") {
process.stdout.write("MCP Event: " + JSON.stringify(event, null, 2) + "\n");
}
}
);
await client.connect(transport);
// Use correct route (/mcp/agents) and bearer token env var
process.stdout.write("\n=== Testing HTTP MCP server with correct route and bearer token ===\n\n");
try {
const result = await client.callTool({
name: "codex",
arguments: {
prompt: "List all the MCP tools you have available. Just list them, don't use any.",
sandbox: "danger-full-access",
"approval-policy": "never",
config: {
mcp_servers: {
"agent-control": {
url: "http://localhost:6767/mcp/agents",
bearer_token_env_var: "PASEO_AGENT_CONTROL_TOKEN"
}
}
}
}
}, undefined, { timeout: 60000 });
process.stdout.write("\n=== RESULT ===\n");
const content = (result as { content: { text?: string }[] }).content;
for (const item of content) {
if (item.text) {
process.stdout.write(item.text + "\n");
}
}
} catch (error) {
process.stderr.write("Error: " + String(error) + "\n");
}
await client.close();
}
main().catch((error) => {
process.stderr.write(String(error) + "\n");
process.exitCode = 1;
});
@@ -1,81 +0,0 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
async function main() {
const transport = new StdioClientTransport({
command: "codex",
args: ["mcp-server"],
env: {
...process.env,
// Try passing credentials via env var that Codex might read
AGENT_CONTROL_AUTH: "mo:bo"
},
});
const client = new Client(
{ name: "test-client", version: "1.0.0" },
{ capabilities: { elicitation: {} } }
);
// Listen for events
client.setNotificationHandler(
z.object({
method: z.literal("codex/event"),
params: z.object({ msg: z.any() }),
}).passthrough(),
(data) => {
const event = (data.params as { msg: unknown }).msg as { type?: string };
if (event.type === "mcp_startup_update" || event.type === "mcp_startup_complete") {
process.stdout.write("MCP Event: " + JSON.stringify(event, null, 2) + "\n");
}
}
);
await client.connect(transport);
// Try passing MCP server config via the config parameter with headers
process.stdout.write("\n=== Testing HTTP MCP server with basic auth in config ===\n\n");
try {
// Create base64 encoded credentials
const credentials = Buffer.from("mo:bo").toString("base64");
const result = await client.callTool({
name: "codex",
arguments: {
prompt: "List all the MCP tools you have available. Just list them, don't use any.",
sandbox: "danger-full-access",
"approval-policy": "never",
config: {
mcp_servers: {
"agent-control": {
url: "http://localhost:6767/mcp/agent-control",
// Try various ways to pass auth
headers: {
"Authorization": `Basic ${credentials}`
}
}
}
}
}
}, undefined, { timeout: 60000 });
process.stdout.write("\n=== RESULT ===\n");
const content = (result as { content: { text?: string }[] }).content;
for (const item of content) {
if (item.text) {
process.stdout.write(item.text + "\n");
}
}
} catch (error) {
process.stderr.write("Error: " + String(error) + "\n");
}
await client.close();
}
main().catch((error) => {
process.stderr.write(String(error) + "\n");
process.exitCode = 1;
});
@@ -1,78 +0,0 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
async function main() {
// Create base64 encoded credentials
const credentials = Buffer.from("mo:bo").toString("base64");
const transport = new StdioClientTransport({
command: "codex",
args: ["mcp-server"],
env: {
...process.env,
// Set the bearer token env var that we will reference
AGENT_CONTROL_TOKEN: `Basic ${credentials}`
},
});
const client = new Client(
{ name: "test-client", version: "1.0.0" },
{ capabilities: { elicitation: {} } }
);
// Listen for events
client.setNotificationHandler(
z.object({
method: z.literal("codex/event"),
params: z.object({ msg: z.any() }),
}).passthrough(),
(data) => {
const event = (data.params as { msg: unknown }).msg as { type?: string };
if (event.type === "mcp_startup_update" || event.type === "mcp_startup_complete") {
process.stdout.write("MCP Event: " + JSON.stringify(event, null, 2) + "\n");
}
}
);
await client.connect(transport);
// Try passing MCP server config with bearer_token_env_var
process.stdout.write("\n=== Testing HTTP MCP server with bearer_token_env_var ===\n\n");
try {
const result = await client.callTool({
name: "codex",
arguments: {
prompt: "List all the MCP tools you have available. Just list them, don't use any.",
sandbox: "danger-full-access",
"approval-policy": "never",
config: {
mcp_servers: {
"agent-control": {
url: "http://localhost:6767/mcp/agent-control",
bearer_token_env_var: "AGENT_CONTROL_TOKEN"
}
}
}
}
}, undefined, { timeout: 60000 });
process.stdout.write("\n=== RESULT ===\n");
const content = (result as { content: { text?: string }[] }).content;
for (const item of content) {
if (item.text) {
process.stdout.write(item.text + "\n");
}
}
} catch (error) {
process.stderr.write("Error: " + String(error) + "\n");
}
await client.close();
}
main().catch((error) => {
process.stderr.write(String(error) + "\n");
process.exitCode = 1;
});
@@ -1,78 +0,0 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
async function main() {
// The basic auth header
const basicAuth = `Basic ${Buffer.from("mo:bo").toString("base64")}`;
const transport = new StdioClientTransport({
command: "codex",
args: ["mcp-server"],
env: {
...process.env,
},
});
const client = new Client(
{ name: "test-client", version: "1.0.0" },
{ capabilities: { elicitation: {} } }
);
// Listen for events
client.setNotificationHandler(
z.object({
method: z.literal("codex/event"),
params: z.object({ msg: z.any() }),
}).passthrough(),
(data) => {
const event = (data.params as { msg: unknown }).msg as { type?: string };
if (event.type === "mcp_startup_update" || event.type === "mcp_startup_complete") {
process.stdout.write("MCP Event: " + JSON.stringify(event, null, 2) + "\n");
}
}
);
await client.connect(transport);
// Test using http_headers with Authorization
process.stdout.write("\n=== Testing HTTP MCP server with http_headers and Authorization ===\n\n");
try {
const result = await client.callTool({
name: "codex",
arguments: {
prompt: "List all the MCP tools you have available. Just list them, don't use any.",
sandbox: "danger-full-access",
"approval-policy": "never",
config: {
mcp_servers: {
"agent-control": {
url: "http://localhost:6767/mcp/agents",
http_headers: {
Authorization: basicAuth
}
}
}
}
}
}, undefined, { timeout: 60000 });
process.stdout.write("\n=== RESULT ===\n");
const content = (result as { content: { text?: string }[] }).content;
for (const item of content) {
if (item.text) {
process.stdout.write(item.text + "\n");
}
}
} catch (error) {
process.stderr.write("Error: " + String(error) + "\n");
}
await client.close();
}
main().catch((error) => {
process.stderr.write(String(error) + "\n");
process.exitCode = 1;
});
@@ -1,71 +0,0 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { z } from "zod";
async function main() {
const transport = new StdioClientTransport({
command: "codex",
args: ["mcp-server"],
env: { ...process.env },
});
const client = new Client(
{ name: "test-client", version: "1.0.0" },
{ capabilities: { elicitation: {} } }
);
// Listen for events
client.setNotificationHandler(
z.object({
method: z.literal("codex/event"),
params: z.object({ msg: z.any() }),
}).passthrough(),
(data) => {
const event = (data.params as { msg: unknown }).msg as { type?: string };
if (event.type === "mcp_startup_update" || event.type === "mcp_startup_complete") {
process.stdout.write("MCP Event: " + JSON.stringify(event, null, 2) + "\n");
}
}
);
await client.connect(transport);
// Try passing MCP server config via the config parameter with HTTP URL
process.stdout.write("\n=== Testing HTTP MCP server config (agent-control style) ===\n\n");
try {
const result = await client.callTool({
name: "codex",
arguments: {
prompt: "List all the MCP tools you have available. Just list them, don't use any.",
sandbox: "danger-full-access",
"approval-policy": "never",
config: {
mcp_servers: {
"agent-control": {
url: "http://localhost:6767/mcp/agent-control",
type: "http"
}
}
}
}
}, undefined, { timeout: 60000 });
process.stdout.write("\n=== RESULT ===\n");
const content = (result as { content: { text?: string }[] }).content;
for (const item of content) {
if (item.text) {
process.stdout.write(item.text + "\n");
}
}
} catch (error) {
process.stderr.write("Error: " + String(error) + "\n");
}
await client.close();
}
main().catch((error) => {
process.stderr.write(String(error) + "\n");
process.exitCode = 1;
});
@@ -1,27 +0,0 @@
// Quick script to verify buildCodexMcpConfig includes MCP servers
import { buildPaseoDaemonConfigFromEnv } from "../src/server/config.js";
const config = buildPaseoDaemonConfigFromEnv();
process.stdout.write("=== agentControlMcp config ===\n");
process.stdout.write(JSON.stringify(config.agentControlMcp, null, 2) + "\n");
// Simulate what buildCodexMcpConfig does
const mcpServers: Record<string, unknown> = {};
if (config.agentControlMcp) {
const agentControlUrl = config.agentControlMcp.url;
mcpServers["agent-control"] = {
url: agentControlUrl,
...(config.agentControlMcp.headers ? { http_headers: config.agentControlMcp.headers } : {}),
};
}
mcpServers["playwright"] = {
command: "npx",
args: ["@playwright/mcp", "--headless", "--isolated"],
};
process.stdout.write("\n=== Built MCP servers config ===\n");
process.stdout.write(JSON.stringify(mcpServers, null, 2) + "\n");
@@ -163,6 +163,7 @@ export type CreateAgentRequestOptions = {
git?: GitSetupOptions; git?: GitSetupOptions;
worktreeName?: string; worktreeName?: string;
requestId?: string; requestId?: string;
labels?: Record<string, string>;
} & AgentConfigOverrides; } & AgentConfigOverrides;
type VoiceConversationLoadedPayload = VoiceConversationLoadedMessage["payload"]; type VoiceConversationLoadedPayload = VoiceConversationLoadedMessage["payload"];
@@ -774,6 +775,9 @@ export class DaemonClientV2 {
: {}), : {}),
...(options.git ? { git: options.git } : {}), ...(options.git ? { git: options.git } : {}),
...(options.worktreeName ? { worktreeName: options.worktreeName } : {}), ...(options.worktreeName ? { worktreeName: options.worktreeName } : {}),
...(options.labels && Object.keys(options.labels).length > 0
? { labels: options.labels }
: {}),
}); });
const statusPromise = this.waitFor( const statusPromise = this.waitFor(
@@ -2587,6 +2591,7 @@ function resolveAgentConfig(options: CreateAgentRequestOptions): AgentSessionCon
git: _git, git: _git,
worktreeName: _worktreeName, worktreeName: _worktreeName,
requestId: _requestId, requestId: _requestId,
labels: _labels,
...overrides ...overrides
} = options; } = options;
@@ -24,7 +24,6 @@ import type {
AgentTimelineItem, AgentTimelineItem,
AgentUsage, AgentUsage,
AgentRuntimeInfo, AgentRuntimeInfo,
AgentControlMcpConfig,
ListPersistedAgentsOptions, ListPersistedAgentsOptions,
PersistedAgentDescriptor, PersistedAgentDescriptor,
} from "./agent-sdk-types.js"; } from "./agent-sdk-types.js";
@@ -58,7 +57,6 @@ export type AgentManagerOptions = {
maxTimelineItems?: number; maxTimelineItems?: number;
idFactory?: () => string; idFactory?: () => string;
registry?: AgentStorage; registry?: AgentStorage;
agentControlMcp?: AgentControlMcpConfig;
onAgentAttention?: AgentAttentionCallback; onAgentAttention?: AgentAttentionCallback;
logger: Logger; logger: Logger;
}; };
@@ -101,11 +99,14 @@ type ManagedAgentBase = {
lastUsage?: AgentUsage; lastUsage?: AgentUsage;
lastError?: string; lastError?: string;
attention: AttentionState; attention: AttentionState;
parentAgentId?: string;
/** /**
* Internal agents are hidden from listings and don't trigger notifications. * Internal agents are hidden from listings and don't trigger notifications.
*/ */
internal?: boolean; internal?: boolean;
/**
* User-defined labels for categorizing agents (e.g., { ui: "true" }).
*/
labels?: Record<string, string>;
}; };
type ManagedAgentWithSession = ManagedAgentBase & { type ManagedAgentWithSession = ManagedAgentBase & {
@@ -189,7 +190,6 @@ export class AgentManager {
private readonly idFactory: () => string; private readonly idFactory: () => string;
private readonly registry?: AgentStorage; private readonly registry?: AgentStorage;
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>(); private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
private readonly agentControlMcp?: AgentControlMcpConfig;
private onAgentAttention?: AgentAttentionCallback; private onAgentAttention?: AgentAttentionCallback;
private logger: Logger; private logger: Logger;
@@ -198,7 +198,6 @@ export class AgentManager {
options?.maxTimelineItems ?? DEFAULT_MAX_TIMELINE_ITEMS; options?.maxTimelineItems ?? DEFAULT_MAX_TIMELINE_ITEMS;
this.idFactory = options?.idFactory ?? (() => randomUUID()); this.idFactory = options?.idFactory ?? (() => randomUUID());
this.registry = options?.registry; this.registry = options?.registry;
this.agentControlMcp = options?.agentControlMcp;
this.onAgentAttention = options?.onAgentAttention; this.onAgentAttention = options?.onAgentAttention;
this.logger = options.logger.child({ module: "agent", component: "agent-manager" }); this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
if (options?.clients) { if (options?.clients) {
@@ -311,15 +310,17 @@ export class AgentManager {
async createAgent( async createAgent(
config: AgentSessionConfig, config: AgentSessionConfig,
agentId?: string agentId?: string,
options?: { labels?: Record<string, string> }
): Promise<ManagedAgent> { ): Promise<ManagedAgent> {
const normalizedConfig = await this.normalizeConfig(config); const normalizedConfig = await this.normalizeConfig(config, { labels: options?.labels });
const client = this.requireClient(normalizedConfig.provider); const client = this.requireClient(normalizedConfig.provider);
const session = await client.createSession(normalizedConfig); const session = await client.createSession(normalizedConfig);
return this.registerSession( return this.registerSession(
session, session,
normalizedConfig, normalizedConfig,
agentId ?? this.idFactory() agentId ?? this.idFactory(),
{ labels: options?.labels }
); );
} }
@@ -327,7 +328,12 @@ export class AgentManager {
handle: AgentPersistenceHandle, handle: AgentPersistenceHandle,
overrides?: Partial<AgentSessionConfig>, overrides?: Partial<AgentSessionConfig>,
agentId?: string, agentId?: string,
timestamps?: { createdAt?: Date; updatedAt?: Date; lastUserMessageAt?: Date | null } options?: {
createdAt?: Date;
updatedAt?: Date;
lastUserMessageAt?: Date | null;
labels?: Record<string, string>;
}
): Promise<ManagedAgent> { ): Promise<ManagedAgent> {
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>; const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
const mergedConfig = { const mergedConfig = {
@@ -346,7 +352,7 @@ export class AgentManager {
session, session,
normalizedConfig, normalizedConfig,
agentId ?? this.idFactory(), agentId ?? this.idFactory(),
timestamps options
); );
} }
@@ -795,15 +801,17 @@ export class AgentManager {
session: AgentSession, session: AgentSession,
config: AgentSessionConfig, config: AgentSessionConfig,
agentId: string, agentId: string,
timestamps?: { createdAt?: Date; updatedAt?: Date; lastUserMessageAt?: Date | null } options?: {
createdAt?: Date;
updatedAt?: Date;
lastUserMessageAt?: Date | null;
labels?: Record<string, string>;
}
): Promise<ManagedAgent> { ): Promise<ManagedAgent> {
if (this.agents.has(agentId)) { if (this.agents.has(agentId)) {
throw new Error(`Agent with id ${agentId} already exists`); throw new Error(`Agent with id ${agentId} already exists`);
} }
// Inform the session of its managed agent ID for MCP parent-child relationships
session.setManagedAgentId?.(agentId);
const now = new Date(); const now = new Date();
const managed = { const managed = {
id: agentId, id: agentId,
@@ -814,8 +822,8 @@ export class AgentManager {
config, config,
runtimeInfo: undefined, runtimeInfo: undefined,
lifecycle: "initializing", lifecycle: "initializing",
createdAt: timestamps?.createdAt ?? now, createdAt: options?.createdAt ?? now,
updatedAt: timestamps?.updatedAt ?? now, updatedAt: options?.updatedAt ?? now,
availableModes: [], availableModes: [],
currentModeId: null, currentModeId: null,
pendingPermissions: new Map(), pendingPermissions: new Map(),
@@ -823,10 +831,10 @@ export class AgentManager {
timeline: [], timeline: [],
persistence: session.describePersistence(), persistence: session.describePersistence(),
historyPrimed: false, historyPrimed: false,
lastUserMessageAt: timestamps?.lastUserMessageAt ?? null, lastUserMessageAt: options?.lastUserMessageAt ?? null,
attention: { requiresAttention: false }, attention: { requiresAttention: false },
parentAgentId: config.parentAgentId,
internal: config.internal ?? false, internal: config.internal ?? false,
labels: options?.labels,
} as ActiveManagedAgent; } as ActiveManagedAgent;
this.agents.set(agentId, managed); this.agents.set(agentId, managed);
@@ -1093,7 +1101,8 @@ export class AgentManager {
private async normalizeConfig( private async normalizeConfig(
config: AgentSessionConfig config: AgentSessionConfig,
options?: { labels?: Record<string, string> }
): Promise<AgentSessionConfig> { ): Promise<AgentSessionConfig> {
const normalized: AgentSessionConfig = { ...config }; const normalized: AgentSessionConfig = { ...config };
@@ -1107,14 +1116,9 @@ export class AgentManager {
normalized.model = trimmed.length > 0 ? trimmed : undefined; normalized.model = trimmed.length > 0 ? trimmed : undefined;
} }
if (!normalized.agentControlMcp && this.agentControlMcp) { // Inject paseoPromptInstructions for UI agents (with ui=true label)
normalized.agentControlMcp = this.agentControlMcp; const isUiAgent = options?.labels?.ui === "true";
} if (isUiAgent) {
if (
normalized.paseoPromptInstructions === undefined &&
normalized.agentControlMcp
) {
normalized.paseoPromptInstructions = getSelfIdentificationInstructions({ normalized.paseoPromptInstructions = getSelfIdentificationInstructions({
cwd: normalized.cwd, cwd: normalized.cwd,
}); });
@@ -102,9 +102,6 @@ describe("agent MCP end-to-end", () => {
mcpDebug: false, mcpDebug: false,
agentClients: {}, agentClients: {},
agentStoragePath: path.join(paseoHome, "agents"), agentStoragePath: path.join(paseoHome, "agents"),
agentControlMcp: {
url: `http://127.0.0.1:${port}/mcp/agents`,
},
}; };
const previousCodexSessionDir = process.env.CODEX_SESSION_DIR; const previousCodexSessionDir = process.env.CODEX_SESSION_DIR;
@@ -235,9 +232,6 @@ describe("agent MCP end-to-end", () => {
mcpDebug: false, mcpDebug: false,
agentClients: {}, agentClients: {},
agentStoragePath: path.join(paseoHome, "agents"), agentStoragePath: path.join(paseoHome, "agents"),
agentControlMcp: {
url: `http://127.0.0.1:${port}/mcp/agents`,
},
}; };
const previousCodexSessionDir = process.env.CODEX_SESSION_DIR; const previousCodexSessionDir = process.env.CODEX_SESSION_DIR;
@@ -44,6 +44,7 @@ export function toStoredAgentRecord(
? agent.lastUserMessageAt.toISOString() ? agent.lastUserMessageAt.toISOString()
: null, : null,
title: options?.title ?? null, title: options?.title ?? null,
labels: agent.labels && Object.keys(agent.labels).length > 0 ? agent.labels : undefined,
lastStatus: agent.lifecycle, lastStatus: agent.lifecycle,
lastModeId: agent.currentModeId ?? config?.modeId ?? null, lastModeId: agent.currentModeId ?? config?.modeId ?? null,
config: config ?? null, config: config ?? null,
@@ -56,7 +57,6 @@ export function toStoredAgentRecord(
attentionTimestamp: agent.attention.requiresAttention attentionTimestamp: agent.attention.requiresAttention
? agent.attention.attentionTimestamp.toISOString() ? agent.attention.attentionTimestamp.toISOString()
: null, : null,
parentAgentId: agent.parentAgentId ?? null,
internal: options?.internal, internal: options?.internal,
} satisfies StoredAgentRecord; } satisfies StoredAgentRecord;
} }
@@ -85,7 +85,7 @@ export function toAgentPayload(
pendingPermissions: sanitizePendingPermissions(agent.pendingPermissions), pendingPermissions: sanitizePendingPermissions(agent.pendingPermissions),
persistence: sanitizePersistenceHandle(agent.persistence), persistence: sanitizePersistenceHandle(agent.persistence),
title: options?.title ?? null, title: options?.title ?? null,
parentAgentId: agent.parentAgentId ?? null, labels: agent.labels && Object.keys(agent.labels).length > 0 ? agent.labels : undefined,
}; };
const usage = sanitizeUsage(agent.lastUsage); const usage = sanitizeUsage(agent.lastUsage);
@@ -154,7 +154,6 @@ describe("getStructuredAgentResponse (e2e)", () => {
cwd = mkdtempSync(path.join(tmpdir(), "agent-response-loop-")); cwd = mkdtempSync(path.join(tmpdir(), "agent-response-loop-"));
manager = new AgentManager({ manager = new AgentManager({
clients: createAllClients(logger), clients: createAllClients(logger),
agentControlMcp: { url: agentMcpServer.url },
logger, logger,
}); });
}); });
@@ -171,11 +171,6 @@ export type AgentCommandResult = {
usage?: AgentUsage; usage?: AgentUsage;
}; };
export type AgentControlMcpConfig = {
url: string;
headers?: Record<string, string>;
};
export type ListPersistedAgentsOptions = { export type ListPersistedAgentsOptions = {
limit?: number; limit?: number;
}; };
@@ -201,7 +196,6 @@ export type AgentSessionConfig = {
networkAccess?: boolean; networkAccess?: boolean;
webSearch?: boolean; webSearch?: boolean;
reasoningEffort?: string; reasoningEffort?: string;
agentControlMcp?: AgentControlMcpConfig;
/** /**
* Paseo-owned instructions injected into the first user prompt via * Paseo-owned instructions injected into the first user prompt via
* <paseo-instructions>...</paseo-instructions>. * <paseo-instructions>...</paseo-instructions>.
@@ -215,7 +209,6 @@ export type AgentSessionConfig = {
claude?: Partial<ClaudeAgentOptions>; claude?: Partial<ClaudeAgentOptions>;
}; };
mcpServers?: AgentMetadata; mcpServers?: AgentMetadata;
parentAgentId?: string;
/** /**
* Internal agents are hidden from listings and don't trigger notifications. * Internal agents are hidden from listings and don't trigger notifications.
* They are used for ephemeral system tasks like commit/PR generation. * They are used for ephemeral system tasks like commit/PR generation.
@@ -239,12 +232,6 @@ export interface AgentSession {
describePersistence(): AgentPersistenceHandle | null; describePersistence(): AgentPersistenceHandle | null;
interrupt(): Promise<void>; interrupt(): Promise<void>;
close(): Promise<void>; close(): Promise<void>;
/**
* Set the managed agent ID for this session. This is called by AgentManager
* after registration to allow the session to include its ID in MCP requests
* (for parent-child agent relationships).
*/
setManagedAgentId?(agentId: string): void;
/** /**
* List available slash commands for this session. * List available slash commands for this session.
* Commands are provider-specific - Claude supports skills and built-in commands. * Commands are provider-specific - Claude supports skills and built-in commands.
@@ -37,6 +37,7 @@ const STORED_AGENT_SCHEMA = z.object({
lastActivityAt: z.string().optional(), lastActivityAt: z.string().optional(),
lastUserMessageAt: z.string().nullable().optional(), lastUserMessageAt: z.string().nullable().optional(),
title: z.string().nullable().optional(), title: z.string().nullable().optional(),
labels: z.record(z.string()).optional(),
lastStatus: AgentStatusSchema.default("closed"), lastStatus: AgentStatusSchema.default("closed"),
lastModeId: z.string().nullable().optional(), lastModeId: z.string().nullable().optional(),
config: SERIALIZABLE_CONFIG_SCHEMA, config: SERIALIZABLE_CONFIG_SCHEMA,
@@ -53,7 +54,6 @@ const STORED_AGENT_SCHEMA = z.object({
requiresAttention: z.boolean().optional(), requiresAttention: z.boolean().optional(),
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(), attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
attentionTimestamp: z.string().nullable().optional(), attentionTimestamp: z.string().nullable().optional(),
parentAgentId: z.string().nullable().optional(),
internal: z.boolean().optional(), internal: z.boolean().optional(),
archivedAt: z.string().nullable().optional(), archivedAt: z.string().nullable().optional(),
}); });
+1 -14
View File
@@ -40,7 +40,7 @@ export interface AgentMcpServerOptions {
paseoHome?: string; paseoHome?: string;
/** /**
* ID of the agent that is connecting to this MCP server. * ID of the agent that is connecting to this MCP server.
* When set, create_agent will auto-inject this as parentAgentId. * Used for cwd/mode inheritance when agents spawn child agents.
*/ */
callerAgentId?: string; callerAgentId?: string;
logger: Logger; logger: Logger;
@@ -350,12 +350,6 @@ export async function createAgentMcpServer(
.describe( .describe(
"Run agent in background. If false (default), waits for completion or permission request. If true, returns immediately." "Run agent in background. If false (default), waits for completion or permission request. If true, returns immediately."
), ),
parentAgentId: z
.string()
.optional()
.describe(
"Optional parent agent ID. When set, this agent is a child of the specified parent agent."
),
}; };
const createAgentInputSchema = callerAgentId const createAgentInputSchema = callerAgentId
@@ -400,12 +394,10 @@ export async function createAgentMcpServer(
worktreeName?: string; worktreeName?: string;
background?: boolean; background?: boolean;
title: string; title: string;
parentAgentId?: string;
}; };
let resolvedCwd: string; let resolvedCwd: string;
let resolvedMode: string | undefined; let resolvedMode: string | undefined;
let resolvedParentAgentId: string | undefined;
if (callerAgentId) { if (callerAgentId) {
const parentAgent = agentManager.getAgent(callerAgentId); const parentAgent = agentManager.getAgent(callerAgentId);
@@ -413,7 +405,6 @@ export async function createAgentMcpServer(
throw new Error(`Parent agent ${callerAgentId} not found`); throw new Error(`Parent agent ${callerAgentId} not found`);
} }
resolvedCwd = parentAgent.cwd; resolvedCwd = parentAgent.cwd;
resolvedParentAgentId = callerAgentId;
const provider: AgentProvider = agentType ?? "claude"; const provider: AgentProvider = agentType ?? "claude";
const parentMode = parentAgent.currentModeId; const parentMode = parentAgent.currentModeId;
@@ -430,14 +421,12 @@ export async function createAgentMcpServer(
initialMode: string; initialMode: string;
worktreeName?: string; worktreeName?: string;
baseBranch?: string; baseBranch?: string;
parentAgentId?: string;
}; };
const { const {
cwd, cwd,
initialMode, initialMode,
worktreeName, worktreeName,
baseBranch, baseBranch,
parentAgentId,
} = topLevelArgs; } = topLevelArgs;
resolvedCwd = expandPath(cwd); resolvedCwd = expandPath(cwd);
@@ -457,7 +446,6 @@ export async function createAgentMcpServer(
} }
resolvedMode = initialMode; resolvedMode = initialMode;
resolvedParentAgentId = parentAgentId;
} }
const provider: AgentProvider = agentType ?? "claude"; const provider: AgentProvider = agentType ?? "claude";
@@ -467,7 +455,6 @@ export async function createAgentMcpServer(
cwd: resolvedCwd, cwd: resolvedCwd,
modeId: resolvedMode, modeId: resolvedMode,
title: normalizedTitle ?? undefined, title: normalizedTitle ?? undefined,
parentAgentId: resolvedParentAgentId,
}); });
if (initialPrompt) { if (initialPrompt) {
@@ -26,9 +26,6 @@ const hasClaudeCredentials =
provider: "claude", provider: "claude",
cwd: process.cwd(), cwd: process.cwd(),
modeId: "plan", modeId: "plan",
agentControlMcp: {
url: "http://localhost:6767/mcp", // Placeholder - not actually used in plan mode
},
}; };
beforeAll(async () => { beforeAll(async () => {
@@ -240,7 +240,6 @@ async function startAgentMcpServer(): Promise<AgentMcpServerHandle> {
provider: "claude", provider: "claude",
cwd, cwd,
modeId: options?.modeId, modeId: options?.modeId,
agentControlMcp: { url: agentMcpServer.url },
extra: { extra: {
claude: { claude: {
sandbox: { enabled: true, autoAllowBashIfSandboxed: false }, sandbox: { enabled: true, autoAllowBashIfSandboxed: false },
@@ -116,16 +116,6 @@ type ClaudeAgentSessionOptions = {
logger: Logger; logger: Logger;
}; };
function appendCallerAgentId(url: string, agentId: string): string {
try {
const parsed = new URL(url);
parsed.searchParams.set("callerAgentId", agentId);
return parsed.toString();
} catch {
const separator = url.includes("?") ? "&" : "?";
return `${url}${separator}callerAgentId=${encodeURIComponent(agentId)}`;
}
}
export function extractUserMessageText(content: unknown): string | null { export function extractUserMessageText(content: unknown): string | null {
if (typeof content === "string") { if (typeof content === "string") {
@@ -228,25 +218,6 @@ function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial<Age
if (typeof metadata.reasoningEffort === "string") { if (typeof metadata.reasoningEffort === "string") {
result.reasoningEffort = metadata.reasoningEffort; result.reasoningEffort = metadata.reasoningEffort;
} }
if (isMetadata(metadata.agentControlMcp)) {
const url = metadata.agentControlMcp.url;
const headers = metadata.agentControlMcp.headers;
if (typeof url === "string") {
const agentControlMcp: AgentSessionConfig["agentControlMcp"] = { url };
if (isMetadata(headers)) {
const normalizedHeaders: { [key: string]: string } = {};
for (const [key, value] of Object.entries(headers)) {
if (typeof value === "string") {
normalizedHeaders[key] = value;
}
}
if (Object.keys(normalizedHeaders).length > 0) {
agentControlMcp.headers = normalizedHeaders;
}
}
result.agentControlMcp = agentControlMcp;
}
}
if (isMetadata(metadata.extra)) { if (isMetadata(metadata.extra)) {
const extra: AgentSessionConfig["extra"] = {}; const extra: AgentSessionConfig["extra"] = {};
if (isMetadata(metadata.extra.codex)) { if (isMetadata(metadata.extra.codex)) {
@@ -262,9 +233,6 @@ function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial<Age
if (isMetadata(metadata.mcpServers)) { if (isMetadata(metadata.mcpServers)) {
result.mcpServers = metadata.mcpServers; result.mcpServers = metadata.mcpServers;
} }
if (typeof metadata.parentAgentId === "string") {
result.parentAgentId = metadata.parentAgentId;
}
return result; return result;
} }
@@ -432,7 +400,6 @@ class ClaudeAgentSession implements AgentSession {
private activeTurnPromise: Promise<void> | null = null; private activeTurnPromise: Promise<void> | null = null;
private cachedRuntimeInfo: AgentRuntimeInfo | null = null; private cachedRuntimeInfo: AgentRuntimeInfo | null = null;
private lastOptionsModel: string | null = null; private lastOptionsModel: string | null = null;
private managedAgentId: string | null = null;
constructor( constructor(
config: ClaudeAgentConfig, config: ClaudeAgentConfig,
@@ -722,10 +689,6 @@ class ClaudeAgentSession implements AgentSession {
this.input = null; this.input = null;
} }
setManagedAgentId(agentId: string): void {
this.managedAgentId = agentId;
}
async listCommands(): Promise<AgentSlashCommand[]> { async listCommands(): Promise<AgentSlashCommand[]> {
const q = await this.ensureQuery(); const q = await this.ensureQuery();
const commands = await q.supportedCommands(); const commands = await q.supportedCommands();
@@ -819,28 +782,11 @@ class ClaudeAgentSession implements AgentSession {
...this.config.extra?.claude, ...this.config.extra?.claude,
}; };
// Always include the agent-control MCP server so agents can launch other agents
if (!this.config.agentControlMcp) {
throw new Error("agentControlMcp is required for ClaudeAgentSession");
}
const agentControlConfig = this.config.agentControlMcp;
const agentControlUrl = this.managedAgentId
? appendCallerAgentId(agentControlConfig.url, this.managedAgentId)
: agentControlConfig.url;
const defaultMcpServers: Record<string, ClaudeMcpServerConfig> = {
"agent-control": {
type: "http",
url: agentControlUrl,
...(agentControlConfig.headers ? { headers: agentControlConfig.headers } : {}),
},
};
if (this.config.mcpServers) { if (this.config.mcpServers) {
const normalizedUserServers = this.normalizeMcpServers(this.config.mcpServers); const normalizedUserServers = this.normalizeMcpServers(this.config.mcpServers);
// Merge user-provided MCP servers with defaults, user servers take precedence if (normalizedUserServers) {
base.mcpServers = { ...defaultMcpServers, ...normalizedUserServers }; base.mcpServers = normalizedUserServers;
} else { }
base.mcpServers = defaultMcpServers;
} }
if (this.config.model) { if (this.config.model) {
@@ -1859,11 +1859,6 @@ const PermissionParamsSchema = z
type PermissionParams = z.infer<typeof PermissionParamsSchema>; type PermissionParams = z.infer<typeof PermissionParamsSchema>;
const AgentControlMcpConfigSchema = z.object({
url: z.string(),
headers: z.record(z.string()).optional(),
});
type AgentSessionExtra = NonNullable<AgentSessionConfig["extra"]>; type AgentSessionExtra = NonNullable<AgentSessionConfig["extra"]>;
const AgentSessionExtraSchema = z.object({ const AgentSessionExtraSchema = z.object({
@@ -1883,10 +1878,8 @@ const AgentSessionConfigSchema = z
networkAccess: z.boolean().optional(), networkAccess: z.boolean().optional(),
webSearch: z.boolean().optional(), webSearch: z.boolean().optional(),
reasoningEffort: z.string().optional(), reasoningEffort: z.string().optional(),
agentControlMcp: AgentControlMcpConfigSchema.optional(),
extra: AgentSessionExtraSchema.optional(), extra: AgentSessionExtraSchema.optional(),
mcpServers: z.record(z.unknown()).optional(), mcpServers: z.record(z.unknown()).optional(),
parentAgentId: z.string().optional(),
}) })
.passthrough(); .passthrough();
@@ -2718,7 +2711,6 @@ function buildCodexMcpConfig(
config: AgentSessionConfig, config: AgentSessionConfig,
prompt: string, prompt: string,
modeId: string, modeId: string,
managedAgentId?: string,
experimentalResume?: string | null experimentalResume?: string | null
): { ): {
prompt: string; prompt: string;
@@ -2762,21 +2754,6 @@ function buildCodexMcpConfig(
// Build MCP servers configuration // Build MCP servers configuration
const mcpServers: Record<string, CodexMcpServerConfig> = {}; const mcpServers: Record<string, CodexMcpServerConfig> = {};
// Add agent-control MCP server (HTTP-based) if configured
if (config.agentControlMcp) {
let agentControlUrl = config.agentControlMcp.url;
// Append caller agent ID to URL if this is a managed agent
if (managedAgentId) {
const separator = agentControlUrl.includes("?") ? "&" : "?";
agentControlUrl = `${agentControlUrl}${separator}callerAgentId=${encodeURIComponent(managedAgentId)}`;
}
mcpServers["agent-control"] = {
url: agentControlUrl,
tool_timeout_sec: 600, // 10 min timeout for child agents
...(config.agentControlMcp.headers ? { http_headers: config.agentControlMcp.headers } : {}),
};
}
// Merge MCP servers from extra.codex.mcp_servers (legacy location) // Merge MCP servers from extra.codex.mcp_servers (legacy location)
const extraCodex = config.extra?.codex as Record<string, unknown> | undefined; const extraCodex = config.extra?.codex as Record<string, unknown> | undefined;
if (extraCodex?.mcp_servers && typeof extraCodex.mcp_servers === "object") { if (extraCodex?.mcp_servers && typeof extraCodex.mcp_servers === "object") {
@@ -3037,7 +3014,6 @@ class CodexMcpAgentSession implements AgentSession {
private turnState: TurnState | null = null; private turnState: TurnState | null = null;
private pendingPatchChanges = new Map<string, PatchFileChange[]>(); private pendingPatchChanges = new Map<string, PatchFileChange[]>();
private patchChangesByCallId = new Map<string, PatchFileChange[]>(); private patchChangesByCallId = new Map<string, PatchFileChange[]>();
private managedAgentId: string | null = null;
private resumeHandle: AgentPersistenceHandle | null = null; private resumeHandle: AgentPersistenceHandle | null = null;
private pendingResumeFile: string | null = null; private pendingResumeFile: string | null = null;
@@ -3541,10 +3517,6 @@ class CodexMcpAgentSession implements AgentSession {
this.conversationId = null; this.conversationId = null;
} }
setManagedAgentId(agentId: string): void {
this.managedAgentId = agentId;
}
async listCommands(): Promise<AgentSlashCommand[]> { async listCommands(): Promise<AgentSlashCommand[]> {
const [skills, prompts] = await Promise.all([ const [skills, prompts] = await Promise.all([
listCodexSkills(this.config.cwd), listCodexSkills(this.config.cwd),
@@ -3591,7 +3563,6 @@ class CodexMcpAgentSession implements AgentSession {
this.config, this.config,
prompt, prompt,
this.currentMode, this.currentMode,
this.managedAgentId ?? undefined,
resumeFile resumeFile
); );
const attempt = async (arguments_: CodexToolArguments) => const attempt = async (arguments_: CodexToolArguments) =>
@@ -3631,7 +3602,7 @@ class CodexMcpAgentSession implements AgentSession {
); );
if (isMissingConversationIdResponse(response)) { if (isMissingConversationIdResponse(response)) {
const replayPrompt = this.buildResumePrompt(prompt); const replayPrompt = this.buildResumePrompt(prompt);
const config = buildCodexMcpConfig(this.config, replayPrompt, this.currentMode, this.managedAgentId ?? undefined); const config = buildCodexMcpConfig(this.config, replayPrompt, this.currentMode);
const attempt = async (arguments_: CodexToolArguments) => const attempt = async (arguments_: CodexToolArguments) =>
this.client.callTool( this.client.callTool(
{ name: "codex", arguments: arguments_ }, { name: "codex", arguments: arguments_ },
@@ -3654,7 +3625,7 @@ class CodexMcpAgentSession implements AgentSession {
} catch (error) { } catch (error) {
if (isMissingConversationIdError(error)) { if (isMissingConversationIdError(error)) {
const replayPrompt = this.buildResumePrompt(prompt); const replayPrompt = this.buildResumePrompt(prompt);
const config = buildCodexMcpConfig(this.config, replayPrompt, this.currentMode, this.managedAgentId ?? undefined); const config = buildCodexMcpConfig(this.config, replayPrompt, this.currentMode);
const attempt = async (arguments_: CodexToolArguments) => const attempt = async (arguments_: CodexToolArguments) =>
this.client.callTool( this.client.callTool(
{ name: "codex", arguments: arguments_ }, { name: "codex", arguments: arguments_ },
+1 -7
View File
@@ -56,7 +56,6 @@ import { printPairingQrIfEnabled } from "./pairing-qr.js";
import { startRelayTransport, type RelayTransportController } from "./relay-transport.js"; import { startRelayTransport, type RelayTransportController } from "./relay-transport.js";
import type { import type {
AgentClient, AgentClient,
AgentControlMcpConfig,
AgentProvider, AgentProvider,
} from "./agent/agent-sdk-types.js"; } from "./agent/agent-sdk-types.js";
@@ -78,7 +77,6 @@ export type PaseoDaemonConfig = {
mcpDebug: boolean; mcpDebug: boolean;
agentClients: Partial<Record<AgentProvider, AgentClient>>; agentClients: Partial<Record<AgentProvider, AgentClient>>;
agentStoragePath: string; agentStoragePath: string;
agentControlMcp: AgentControlMcpConfig;
relayEnabled?: boolean; relayEnabled?: boolean;
relayEndpoint?: string; relayEndpoint?: string;
appBaseUrl?: string; appBaseUrl?: string;
@@ -211,7 +209,6 @@ export async function createPaseoDaemon(
...config.agentClients, ...config.agentClients,
}, },
registry: agentStorage, registry: agentStorage,
agentControlMcp: config.agentControlMcp,
logger, logger,
}); });
@@ -378,10 +375,7 @@ export async function createPaseoDaemon(
agentStorage, agentStorage,
downloadTokenStore, downloadTokenStore,
config.paseoHome, config.paseoHome,
{ agentMcpRoute,
agentMcpUrl: config.agentControlMcp.url,
agentMcpHeaders: config.agentControlMcp.headers,
},
{ allowedOrigins }, { allowedOrigins },
{ stt: sttService, tts: ttsService }, { stt: sttService, tts: ttsService },
terminalManager terminalManager
-3
View File
@@ -80,9 +80,6 @@ export function loadConfig(
agentMcpRoute: DEFAULT_AGENT_MCP_ROUTE, agentMcpRoute: DEFAULT_AGENT_MCP_ROUTE,
agentMcpAllowedHosts: [mcpListen, `localhost:${mcpListen.split(":")[1]}`], agentMcpAllowedHosts: [mcpListen, `localhost:${mcpListen.split(":")[1]}`],
mcpDebug: env.MCP_DEBUG === "1", mcpDebug: env.MCP_DEBUG === "1",
agentControlMcp: {
url: `http://${mcpListen}${DEFAULT_AGENT_MCP_ROUTE}`,
},
agentStoragePath: path.join(paseoHome, "agents"), agentStoragePath: path.join(paseoHome, "agents"),
staticDir: "public", staticDir: "public",
agentClients: {}, agentClients: {},
@@ -57,9 +57,6 @@ async function startDaemon(options: {
mcpDebug: false, mcpDebug: false,
agentClients: {}, agentClients: {},
agentStoragePath: path.join(options.paseoHome, "agents"), agentStoragePath: path.join(options.paseoHome, "agents"),
agentControlMcp: {
url: `http://127.0.0.1:${port}/mcp/agents`,
},
openai: process.env.OPENAI_API_KEY ? { apiKey: process.env.OPENAI_API_KEY } : undefined, openai: process.env.OPENAI_API_KEY ? { apiKey: process.env.OPENAI_API_KEY } : undefined,
}; };
@@ -1,22 +1,16 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest"; import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, writeFileSync, existsSync, rmSync, mkdirSync, readFileSync, readdirSync } from "fs"; import { mkdtempSync, rmSync } from "fs";
import { tmpdir } from "os"; import { tmpdir } from "os";
import path from "path"; import path from "path";
import { import {
createDaemonTestContext, createDaemonTestContext,
type DaemonTestContext, type DaemonTestContext,
} from "../test-utils/index.js"; } from "../test-utils/index.js";
import type { AgentTimelineItem } from "../agent/agent-sdk-types.js";
import type { AgentSnapshotPayload, SessionOutboundMessage } from "../messages.js";
function tmpCwd(): string { function tmpCwd(): string {
return mkdtempSync(path.join(tmpdir(), "daemon-e2e-")); return mkdtempSync(path.join(tmpdir(), "daemon-e2e-"));
} }
// Use gpt-5.1-codex-mini with low reasoning effort for faster test execution
const CODEX_TEST_MODEL = "gpt-5.1-codex-mini";
const CODEX_TEST_REASONING_EFFORT = "low";
describe("daemon E2E", () => { describe("daemon E2E", () => {
let ctx: DaemonTestContext; let ctx: DaemonTestContext;
@@ -29,140 +23,10 @@ describe("daemon E2E", () => {
}, 60000); }, 60000);
describe("multi-agent orchestration", () => { describe("multi-agent orchestration", () => {
test( // TODO: Re-implement orchestration tests with new Paseo MCP
"parent agent creates child agent via agent-control MCP", // The old agent-control MCP has been removed
async () => { test("placeholder for future orchestration tests", async () => {
const cwd = tmpCwd(); expect(true).toBe(true);
const childCwd = tmpCwd(); });
// Create parent Codex agent
const parent = await ctx.client.createAgent({
provider: "codex", model: CODEX_TEST_MODEL, reasoningEffort: CODEX_TEST_REASONING_EFFORT,
cwd,
title: "Parent Agent",
});
expect(parent.id).toBeTruthy();
expect(parent.status).toBe("idle");
// Clear message queue before sending prompt
ctx.client.clearMessageQueue();
// Prompt the parent to create a child agent using agent-control MCP
const prompt = [
`Use the create_agent tool from the agent-control MCP server to create a new codex agent.`,
`Set the cwd to: ${childCwd}`,
`Set the title to: Child Agent`,
`Set agentType to: codex`,
`Do NOT set an initialPrompt - just create the agent.`,
`After creating the agent, reply with "CREATED" followed by the child's agentId.`,
].join(" ");
await ctx.client.sendMessage(parent.id, prompt);
// Wait for parent to complete
const afterCreate = await ctx.client.waitForAgentIdle(
parent.id,
120000
);
expect(afterCreate.status).toBe("idle");
// Verify timeline contains a tool call to create_agent
const queue = ctx.client.getMessageQueue();
const timelineItems: AgentTimelineItem[] = [];
for (const m of queue) {
if (
m.type === "agent_stream" &&
m.payload.agentId === parent.id &&
m.payload.event.type === "timeline"
) {
timelineItems.push(m.payload.event.item);
}
}
// Should have a tool call to create_agent from agent-control
const hasCreateAgentCall = timelineItems.some(
(item) =>
item.type === "tool_call" &&
item.name === "agent-control.create_agent"
);
expect(hasCreateAgentCall).toBe(true);
// Now verify we can see both agents via session_state
// Send a list_persisted_agents_request to trigger session_state refresh
// Or we can check the queue for agent_state messages
const agentStateMessages = queue.filter(
(m) => m.type === "agent_state"
);
// Extract unique agent IDs from state messages
const agentIds = new Set<string>();
for (const m of agentStateMessages) {
if (m.type === "agent_state") {
agentIds.add(m.payload.id);
}
}
// Should have at least 2 agents (parent + child)
expect(agentIds.size).toBeGreaterThanOrEqual(2);
expect(agentIds.has(parent.id)).toBe(true);
// Get the child agent ID from the tool call output
const createAgentCall = timelineItems.find(
(item) =>
item.type === "tool_call" &&
item.name === "agent-control.create_agent"
);
let childAgentId: string | null = null;
if (
createAgentCall &&
createAgentCall.type === "tool_call" &&
createAgentCall.output
) {
const output = createAgentCall.output as unknown;
const tryExtract = (value: unknown): string | null => {
if (!value) return null;
if (typeof value === "string") {
try {
return tryExtract(JSON.parse(value));
} catch {
return null;
}
}
if (typeof value !== "object") return null;
const asObj = value as Record<string, unknown>;
const direct = asObj.agentId;
if (typeof direct === "string") return direct;
const structured = asObj.structuredContent;
if (structured && typeof structured === "object") {
const nested = (structured as Record<string, unknown>).agentId;
if (typeof nested === "string") return nested;
}
if (typeof structured === "string") {
try {
return tryExtract(JSON.parse(structured));
} catch {
return null;
}
}
return null;
};
childAgentId = tryExtract(output);
}
// Verify we found the child agent ID
expect(childAgentId).toBeTruthy();
expect(agentIds.has(childAgentId!)).toBe(true);
// Cleanup
rmSync(cwd, { recursive: true, force: true });
rmSync(childCwd, { recursive: true, force: true });
},
300000 // 5 minute timeout for multi-agent E2E
);
}); });
}); });
@@ -77,10 +77,16 @@ export function buildSessionConfig(
export function extractTimestamps( export function extractTimestamps(
record: StoredAgentRecord record: StoredAgentRecord
): { createdAt: Date; updatedAt: Date; lastUserMessageAt: Date | null } { ): {
createdAt: Date;
updatedAt: Date;
lastUserMessageAt: Date | null;
labels?: Record<string, string>;
} {
return { return {
createdAt: new Date(record.createdAt), createdAt: new Date(record.createdAt),
updatedAt: new Date(record.lastActivityAt ?? record.updatedAt), updatedAt: new Date(record.lastActivityAt ?? record.updatedAt),
lastUserMessageAt: record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null, lastUserMessageAt: record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null,
labels: record.labels,
}; };
} }
+16 -21
View File
@@ -104,11 +104,6 @@ import { getProjectIcon } from "../utils/project-icon.js";
import { expandTilde } from "../utils/path.js"; import { expandTilde } from "../utils/path.js";
import type pino from "pino"; import type pino from "pino";
type AgentMcpClientConfig = {
agentMcpUrl: string;
agentMcpHeaders?: Record<string, string>;
};
const execAsync = promisify(exec); const execAsync = promisify(exec);
const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = { const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
...process.env, ...process.env,
@@ -294,7 +289,7 @@ export class Session {
private agentTools: ToolSet | null = null; private agentTools: ToolSet | null = null;
private agentManager: AgentManager; private agentManager: AgentManager;
private readonly agentStorage: AgentStorage; private readonly agentStorage: AgentStorage;
private readonly agentMcpConfig: AgentMcpClientConfig; private readonly agentMcpRoute: string;
private readonly downloadTokenStore: DownloadTokenStore; private readonly downloadTokenStore: DownloadTokenStore;
private readonly pushTokenStore: PushTokenStore; private readonly pushTokenStore: PushTokenStore;
private readonly providerRegistry: ReturnType<typeof buildProviderRegistry>; private readonly providerRegistry: ReturnType<typeof buildProviderRegistry>;
@@ -317,7 +312,7 @@ export class Session {
paseoHome: string, paseoHome: string,
agentManager: AgentManager, agentManager: AgentManager,
agentStorage: AgentStorage, agentStorage: AgentStorage,
agentMcpConfig: AgentMcpClientConfig, agentMcpRoute: string,
stt: OpenAISTT | null, stt: OpenAISTT | null,
tts: OpenAITTS | null, tts: OpenAITTS | null,
terminalManager: TerminalManager | null, terminalManager: TerminalManager | null,
@@ -331,7 +326,7 @@ export class Session {
this.paseoHome = paseoHome; this.paseoHome = paseoHome;
this.agentManager = agentManager; this.agentManager = agentManager;
this.agentStorage = agentStorage; this.agentStorage = agentStorage;
this.agentMcpConfig = agentMcpConfig; this.agentMcpRoute = agentMcpRoute;
this.terminalManager = terminalManager; this.terminalManager = terminalManager;
this.voiceConversationStore = voiceConversationStore; this.voiceConversationStore = voiceConversationStore;
this.abortController = new AbortController(); this.abortController = new AbortController();
@@ -505,15 +500,10 @@ export class Session {
*/ */
private async initializeAgentMcp(): Promise<void> { private async initializeAgentMcp(): Promise<void> {
try { try {
// Connect to the local MCP server using localhost
const agentMcpUrl = `http://127.0.0.1:6767${this.agentMcpRoute}`;
const transport = new StreamableHTTPClientTransport( const transport = new StreamableHTTPClientTransport(
new URL(this.agentMcpConfig.agentMcpUrl), new URL(agentMcpUrl)
this.agentMcpConfig.agentMcpHeaders
? {
requestInit: {
headers: this.agentMcpConfig.agentMcpHeaders,
},
}
: undefined
); );
this.agentMcpClient = await experimental_createMCPClient({ this.agentMcpClient = await experimental_createMCPClient({
@@ -632,7 +622,6 @@ export class Session {
requiresAttention: record.requiresAttention ?? false, requiresAttention: record.requiresAttention ?? false,
attentionReason: record.attentionReason ?? null, attentionReason: record.attentionReason ?? null,
attentionTimestamp: record.attentionTimestamp ?? null, attentionTimestamp: record.attentionTimestamp ?? null,
parentAgentId: record.parentAgentId ?? null,
archivedAt: record.archivedAt ?? null, archivedAt: record.archivedAt ?? null,
}; };
} }
@@ -1326,7 +1315,7 @@ export class Session {
private async handleCreateAgentRequest( private async handleCreateAgentRequest(
msg: Extract<SessionInboundMessage, { type: "create_agent_request" }> msg: Extract<SessionInboundMessage, { type: "create_agent_request" }>
): Promise<void> { ): Promise<void> {
const { config, worktreeName, requestId, initialPrompt, git, images } = msg; const { config, worktreeName, requestId, initialPrompt, git, images, labels } = msg;
this.sessionLogger.info( this.sessionLogger.info(
{ cwd: config.cwd, provider: config.provider, worktreeName }, { cwd: config.cwd, provider: config.provider, worktreeName },
`Creating agent in ${config.cwd} (${config.provider})${ `Creating agent in ${config.cwd} (${config.provider})${
@@ -1356,9 +1345,14 @@ export class Session {
const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig( const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig(
config, config,
git, git,
worktreeName worktreeName,
labels
);
const snapshot = await this.agentManager.createAgent(
sessionConfig,
undefined,
{ labels }
); );
const snapshot = await this.agentManager.createAgent(sessionConfig);
await this.forwardAgentState(snapshot); await this.forwardAgentState(snapshot);
const trimmedPrompt = initialPrompt?.trim(); const trimmedPrompt = initialPrompt?.trim();
@@ -1584,7 +1578,8 @@ export class Session {
private async buildAgentSessionConfig( private async buildAgentSessionConfig(
config: AgentSessionConfig, config: AgentSessionConfig,
gitOptions?: GitSetupOptions, gitOptions?: GitSetupOptions,
legacyWorktreeName?: string legacyWorktreeName?: string,
_labels?: Record<string, string>
): Promise<{ sessionConfig: AgentSessionConfig; worktreeConfig?: WorktreeConfig }> { ): Promise<{ sessionConfig: AgentSessionConfig; worktreeConfig?: WorktreeConfig }> {
let cwd = expandTilde(config.cwd); let cwd = expandTilde(config.cwd);
const normalized = this.normalizeGitOptions(gitOptions, legacyWorktreeName); const normalized = this.normalizeGitOptions(gitOptions, legacyWorktreeName);
@@ -64,9 +64,6 @@ export async function createTestPaseoDaemon(
mcpDebug: false, mcpDebug: false,
agentClients: {}, agentClients: {},
agentStoragePath: path.join(paseoHome, "agents"), agentStoragePath: path.join(paseoHome, "agents"),
agentControlMcp: {
url: `http://127.0.0.1:${port}/mcp/agents`,
},
relayEnabled: options.relayEnabled ?? false, relayEnabled: options.relayEnabled ?? false,
relayEndpoint: options.relayEndpoint ?? "relay.paseo.sh:443", relayEndpoint: options.relayEndpoint ?? "relay.paseo.sh:443",
appBaseUrl: "https://app.paseo.sh", appBaseUrl: "https://app.paseo.sh",
@@ -10,11 +10,6 @@ import type pino from "pino";
import type { WSOutboundMessage } from "./messages.js"; import type { WSOutboundMessage } from "./messages.js";
import { WebSocketSessionBridge } from "./websocket-session-bridge.js"; import { WebSocketSessionBridge } from "./websocket-session-bridge.js";
type AgentMcpClientConfig = {
agentMcpUrl: string;
agentMcpHeaders?: Record<string, string>;
};
type WebSocketServerConfig = { type WebSocketServerConfig = {
allowedOrigins: Set<string>; allowedOrigins: Set<string>;
}; };
@@ -34,7 +29,7 @@ export class VoiceAssistantWebSocketServer {
agentStorage: AgentStorage, agentStorage: AgentStorage,
downloadTokenStore: DownloadTokenStore, downloadTokenStore: DownloadTokenStore,
paseoHome: string, paseoHome: string,
agentMcpConfig: AgentMcpClientConfig, agentMcpRoute: string,
wsConfig: WebSocketServerConfig, wsConfig: WebSocketServerConfig,
speech?: { stt: OpenAISTT | null; tts: OpenAITTS | null }, speech?: { stt: OpenAISTT | null; tts: OpenAITTS | null },
terminalManager?: TerminalManager | null terminalManager?: TerminalManager | null
@@ -46,7 +41,7 @@ export class VoiceAssistantWebSocketServer {
agentStorage, agentStorage,
downloadTokenStore, downloadTokenStore,
paseoHome, paseoHome,
agentMcpConfig, agentMcpRoute,
speech, speech,
terminalManager terminalManager
); );
@@ -19,11 +19,6 @@ import type { OpenAITTS } from "./agent/tts-openai.js";
import type { TerminalManager } from "../terminal/terminal-manager.js"; import type { TerminalManager } from "../terminal/terminal-manager.js";
import type pino from "pino"; import type pino from "pino";
type AgentMcpClientConfig = {
agentMcpUrl: string;
agentMcpHeaders?: Record<string, string>;
};
export class WebSocketSessionBridge { export class WebSocketSessionBridge {
private readonly logger: pino.Logger; private readonly logger: pino.Logger;
private readonly sessions: Map<WebSocket, Session> = new Map(); private readonly sessions: Map<WebSocket, Session> = new Map();
@@ -34,7 +29,7 @@ export class WebSocketSessionBridge {
private readonly paseoHome: string; private readonly paseoHome: string;
private readonly pushTokenStore: PushTokenStore; private readonly pushTokenStore: PushTokenStore;
private readonly pushService: PushService; private readonly pushService: PushService;
private readonly agentMcpConfig: AgentMcpClientConfig; private readonly agentMcpRoute: string;
private readonly stt: OpenAISTT | null; private readonly stt: OpenAISTT | null;
private readonly tts: OpenAITTS | null; private readonly tts: OpenAITTS | null;
private readonly terminalManager: TerminalManager | null; private readonly terminalManager: TerminalManager | null;
@@ -46,7 +41,7 @@ export class WebSocketSessionBridge {
agentStorage: AgentStorage, agentStorage: AgentStorage,
downloadTokenStore: DownloadTokenStore, downloadTokenStore: DownloadTokenStore,
paseoHome: string, paseoHome: string,
agentMcpConfig: AgentMcpClientConfig, agentMcpRoute: string,
speech?: { stt: OpenAISTT | null; tts: OpenAITTS | null }, speech?: { stt: OpenAISTT | null; tts: OpenAITTS | null },
terminalManager?: TerminalManager | null terminalManager?: TerminalManager | null
) { ) {
@@ -55,7 +50,7 @@ export class WebSocketSessionBridge {
this.agentStorage = agentStorage; this.agentStorage = agentStorage;
this.downloadTokenStore = downloadTokenStore; this.downloadTokenStore = downloadTokenStore;
this.paseoHome = paseoHome; this.paseoHome = paseoHome;
this.agentMcpConfig = agentMcpConfig; this.agentMcpRoute = agentMcpRoute;
this.stt = speech?.stt ?? null; this.stt = speech?.stt ?? null;
this.tts = speech?.tts ?? null; this.tts = speech?.tts ?? null;
this.terminalManager = terminalManager ?? null; this.terminalManager = terminalManager ?? null;
@@ -91,7 +86,7 @@ export class WebSocketSessionBridge {
this.paseoHome, this.paseoHome,
this.agentManager, this.agentManager,
this.agentStorage, this.agentStorage,
this.agentMcpConfig, this.agentMcpRoute,
this.stt, this.stt,
this.tts, this.tts,
this.terminalManager, this.terminalManager,
+2 -7
View File
@@ -63,12 +63,6 @@ const AgentSessionConfigSchema = z.object({
networkAccess: z.boolean().optional(), networkAccess: z.boolean().optional(),
webSearch: z.boolean().optional(), webSearch: z.boolean().optional(),
reasoningEffort: z.string().optional(), reasoningEffort: z.string().optional(),
agentControlMcp: z
.object({
url: z.string(),
headers: z.record(z.string()).optional(),
})
.optional(),
extra: z extra: z
.object({ .object({
codex: z.record(z.unknown()).optional(), codex: z.record(z.unknown()).optional(),
@@ -247,10 +241,10 @@ export const AgentSnapshotPayloadSchema = z.object({
lastUsage: AgentUsageSchema.optional(), lastUsage: AgentUsageSchema.optional(),
lastError: z.string().optional(), lastError: z.string().optional(),
title: z.string().nullable(), title: z.string().nullable(),
labels: z.record(z.string()).optional(),
requiresAttention: z.boolean().optional(), requiresAttention: z.boolean().optional(),
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(), attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
attentionTimestamp: z.string().nullable().optional(), attentionTimestamp: z.string().nullable().optional(),
parentAgentId: z.string().nullable().optional(),
archivedAt: z.string().nullable().optional(), archivedAt: z.string().nullable().optional(),
}); });
@@ -385,6 +379,7 @@ export const CreateAgentRequestMessageSchema = z.object({
mimeType: z.string(), // e.g., "image/jpeg", "image/png" mimeType: z.string(), // e.g., "image/jpeg", "image/png"
})).optional(), })).optional(),
git: GitSetupOptionsSchema.optional(), git: GitSetupOptionsSchema.optional(),
labels: z.record(z.string()).optional(),
requestId: z.string(), requestId: z.string(),
}); });