mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
refactor: replace agent-control MCP with labels system and Paseo MCP
This commit is contained in:
@@ -246,7 +246,6 @@ function normalizeAgentSnapshot(
|
||||
requiresAttention: snapshot.requiresAttention ?? false,
|
||||
attentionReason: snapshot.attentionReason ?? null,
|
||||
attentionTimestamp,
|
||||
parentAgentId: snapshot.parentAgentId,
|
||||
archivedAt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,11 +62,6 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
||||
}
|
||||
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
||||
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 = {
|
||||
id: agent.id,
|
||||
serverId,
|
||||
@@ -79,7 +74,6 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
||||
requiresAttention: agent.requiresAttention,
|
||||
attentionReason: agent.attentionReason,
|
||||
attentionTimestamp: agent.attentionTimestamp,
|
||||
parentAgentId: agent.parentAgentId,
|
||||
archivedAt: agent.archivedAt,
|
||||
};
|
||||
allAgents.push(nextAgent);
|
||||
|
||||
@@ -98,7 +98,6 @@ export interface Agent {
|
||||
requiresAttention?: boolean;
|
||||
attentionReason?: "finished" | "error" | "permission" | null;
|
||||
attentionTimestamp?: Date | null;
|
||||
parentAgentId?: string | null;
|
||||
archivedAt?: Date | null;
|
||||
}
|
||||
|
||||
@@ -839,7 +838,6 @@ export const useSessionStore = create<SessionStore>()(
|
||||
requiresAttention: agent.requiresAttention ?? false,
|
||||
attentionReason: agent.attentionReason ?? null,
|
||||
attentionTimestamp: agent.attentionTimestamp ?? null,
|
||||
parentAgentId: agent.parentAgentId,
|
||||
});
|
||||
}
|
||||
return entries;
|
||||
|
||||
@@ -12,6 +12,5 @@ export interface AgentDirectoryEntry {
|
||||
requiresAttention?: boolean;
|
||||
attentionReason?: "finished" | "error" | "permission" | null;
|
||||
attentionTimestamp?: Date | null;
|
||||
parentAgentId?: string | null;
|
||||
archivedAt?: Date | null;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ function makeAgent(overrides: Partial<AggregatedAgent> = {}): AggregatedAgent {
|
||||
requiresAttention: overrides.requiresAttention ?? false,
|
||||
attentionReason: overrides.attentionReason ?? null,
|
||||
attentionTimestamp: overrides.attentionTimestamp ?? null,
|
||||
parentAgentId: overrides.parentAgentId ?? null,
|
||||
} as AggregatedAgent;
|
||||
}
|
||||
|
||||
|
||||
@@ -1162,12 +1162,8 @@ const TOOL_NAME_MAP: Record<string, string> = {
|
||||
read_file: "Read",
|
||||
apply_patch: "Edit",
|
||||
paseo_worktree_setup: "Setup",
|
||||
"agent-control.set_title": "Set title",
|
||||
"agent-control.set_branch": "Set branch",
|
||||
set_title: "Set title",
|
||||
set_branch: "Set branch",
|
||||
"mcp__agent-control__set_title": "Set title",
|
||||
"mcp__agent-control__set_branch": "Set branch",
|
||||
thinking: "Thinking",
|
||||
};
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ Notes:
|
||||
|
||||
### `packages/cli/src/commands/agent/inspect.ts`
|
||||
Inline types:
|
||||
- `AgentSnapshotLike` (snapshot fields + `lastUsage`, `capabilities`, `availableModes`, `pendingPermissions`, `parentAgentId`)
|
||||
- `AgentSnapshotLike` (snapshot fields + `lastUsage`, `capabilities`, `availableModes`, `pendingPermissions`)
|
||||
|
||||
Recommended server types:
|
||||
- `AgentSnapshotPayload` (overall snapshot shape). **Not exported**.
|
||||
|
||||
@@ -37,9 +37,11 @@ export function createCli(): Command {
|
||||
// Primary agent commands (top-level)
|
||||
program
|
||||
.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('-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('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||
.action((options, command) => {
|
||||
@@ -62,6 +64,8 @@ export function createCli(): Command {
|
||||
.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('--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)')
|
||||
.action(withOutput(runRunCommand))
|
||||
|
||||
|
||||
@@ -14,12 +14,19 @@ import { withOutput } from '../../output/index.js'
|
||||
export function createAgentCommand(): Command {
|
||||
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)
|
||||
agent
|
||||
.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('-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('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||
.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('--mode <mode>', 'Provider-specific mode (e.g., plan, default, bypass)')
|
||||
.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)')
|
||||
.action(withOutput(runRunCommand))
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ interface AgentInspect {
|
||||
id: string
|
||||
tool: string
|
||||
}>
|
||||
parentAgentId: string | null
|
||||
}
|
||||
|
||||
/** Key-value row for table display */
|
||||
@@ -122,7 +121,6 @@ function toInspectData(snapshot: AgentSnapshotPayload): AgentInspect {
|
||||
id: p.id,
|
||||
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: 'ParentAgentId',
|
||||
value: agent.parentAgentId ?? 'null',
|
||||
})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@ export interface AgentLsOptions extends CommandOptions {
|
||||
status?: string
|
||||
/** Filter by specific cwd (overrides default cwd filtering) */
|
||||
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()
|
||||
|
||||
// Sort agents: running first, then idle, then others; within each group, most recent first
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface AgentRunOptions extends CommandOptions {
|
||||
base?: string
|
||||
image?: string[]
|
||||
cwd?: string
|
||||
label?: string[]
|
||||
ui?: boolean
|
||||
}
|
||||
|
||||
function toRunResult(agent: AgentSnapshotPayload): AgentRunResult {
|
||||
@@ -127,6 +129,31 @@ export async function runRunCommand(
|
||||
}
|
||||
: 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
|
||||
const agent = await client.createAgent({
|
||||
provider: (options.provider as 'claude' | 'codex' | 'opencode') ?? 'claude',
|
||||
@@ -138,6 +165,7 @@ export async function runRunCommand(
|
||||
images,
|
||||
git,
|
||||
worktreeName: options.worktree,
|
||||
labels: Object.keys(labels).length > 0 ? labels : undefined,
|
||||
})
|
||||
|
||||
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;
|
||||
worktreeName?: string;
|
||||
requestId?: string;
|
||||
labels?: Record<string, string>;
|
||||
} & AgentConfigOverrides;
|
||||
|
||||
type VoiceConversationLoadedPayload = VoiceConversationLoadedMessage["payload"];
|
||||
@@ -774,6 +775,9 @@ export class DaemonClientV2 {
|
||||
: {}),
|
||||
...(options.git ? { git: options.git } : {}),
|
||||
...(options.worktreeName ? { worktreeName: options.worktreeName } : {}),
|
||||
...(options.labels && Object.keys(options.labels).length > 0
|
||||
? { labels: options.labels }
|
||||
: {}),
|
||||
});
|
||||
|
||||
const statusPromise = this.waitFor(
|
||||
@@ -2587,6 +2591,7 @@ function resolveAgentConfig(options: CreateAgentRequestOptions): AgentSessionCon
|
||||
git: _git,
|
||||
worktreeName: _worktreeName,
|
||||
requestId: _requestId,
|
||||
labels: _labels,
|
||||
...overrides
|
||||
} = options;
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import type {
|
||||
AgentTimelineItem,
|
||||
AgentUsage,
|
||||
AgentRuntimeInfo,
|
||||
AgentControlMcpConfig,
|
||||
ListPersistedAgentsOptions,
|
||||
PersistedAgentDescriptor,
|
||||
} from "./agent-sdk-types.js";
|
||||
@@ -58,7 +57,6 @@ export type AgentManagerOptions = {
|
||||
maxTimelineItems?: number;
|
||||
idFactory?: () => string;
|
||||
registry?: AgentStorage;
|
||||
agentControlMcp?: AgentControlMcpConfig;
|
||||
onAgentAttention?: AgentAttentionCallback;
|
||||
logger: Logger;
|
||||
};
|
||||
@@ -101,11 +99,14 @@ type ManagedAgentBase = {
|
||||
lastUsage?: AgentUsage;
|
||||
lastError?: string;
|
||||
attention: AttentionState;
|
||||
parentAgentId?: string;
|
||||
/**
|
||||
* Internal agents are hidden from listings and don't trigger notifications.
|
||||
*/
|
||||
internal?: boolean;
|
||||
/**
|
||||
* User-defined labels for categorizing agents (e.g., { ui: "true" }).
|
||||
*/
|
||||
labels?: Record<string, string>;
|
||||
};
|
||||
|
||||
type ManagedAgentWithSession = ManagedAgentBase & {
|
||||
@@ -189,7 +190,6 @@ export class AgentManager {
|
||||
private readonly idFactory: () => string;
|
||||
private readonly registry?: AgentStorage;
|
||||
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
|
||||
private readonly agentControlMcp?: AgentControlMcpConfig;
|
||||
private onAgentAttention?: AgentAttentionCallback;
|
||||
private logger: Logger;
|
||||
|
||||
@@ -198,7 +198,6 @@ export class AgentManager {
|
||||
options?.maxTimelineItems ?? DEFAULT_MAX_TIMELINE_ITEMS;
|
||||
this.idFactory = options?.idFactory ?? (() => randomUUID());
|
||||
this.registry = options?.registry;
|
||||
this.agentControlMcp = options?.agentControlMcp;
|
||||
this.onAgentAttention = options?.onAgentAttention;
|
||||
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
||||
if (options?.clients) {
|
||||
@@ -311,15 +310,17 @@ export class AgentManager {
|
||||
|
||||
async createAgent(
|
||||
config: AgentSessionConfig,
|
||||
agentId?: string
|
||||
agentId?: string,
|
||||
options?: { labels?: Record<string, string> }
|
||||
): Promise<ManagedAgent> {
|
||||
const normalizedConfig = await this.normalizeConfig(config);
|
||||
const normalizedConfig = await this.normalizeConfig(config, { labels: options?.labels });
|
||||
const client = this.requireClient(normalizedConfig.provider);
|
||||
const session = await client.createSession(normalizedConfig);
|
||||
return this.registerSession(
|
||||
session,
|
||||
normalizedConfig,
|
||||
agentId ?? this.idFactory()
|
||||
agentId ?? this.idFactory(),
|
||||
{ labels: options?.labels }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -327,7 +328,12 @@ export class AgentManager {
|
||||
handle: AgentPersistenceHandle,
|
||||
overrides?: Partial<AgentSessionConfig>,
|
||||
agentId?: string,
|
||||
timestamps?: { createdAt?: Date; updatedAt?: Date; lastUserMessageAt?: Date | null }
|
||||
options?: {
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
lastUserMessageAt?: Date | null;
|
||||
labels?: Record<string, string>;
|
||||
}
|
||||
): Promise<ManagedAgent> {
|
||||
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
|
||||
const mergedConfig = {
|
||||
@@ -346,7 +352,7 @@ export class AgentManager {
|
||||
session,
|
||||
normalizedConfig,
|
||||
agentId ?? this.idFactory(),
|
||||
timestamps
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
@@ -795,15 +801,17 @@ export class AgentManager {
|
||||
session: AgentSession,
|
||||
config: AgentSessionConfig,
|
||||
agentId: string,
|
||||
timestamps?: { createdAt?: Date; updatedAt?: Date; lastUserMessageAt?: Date | null }
|
||||
options?: {
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
lastUserMessageAt?: Date | null;
|
||||
labels?: Record<string, string>;
|
||||
}
|
||||
): Promise<ManagedAgent> {
|
||||
if (this.agents.has(agentId)) {
|
||||
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 managed = {
|
||||
id: agentId,
|
||||
@@ -814,8 +822,8 @@ export class AgentManager {
|
||||
config,
|
||||
runtimeInfo: undefined,
|
||||
lifecycle: "initializing",
|
||||
createdAt: timestamps?.createdAt ?? now,
|
||||
updatedAt: timestamps?.updatedAt ?? now,
|
||||
createdAt: options?.createdAt ?? now,
|
||||
updatedAt: options?.updatedAt ?? now,
|
||||
availableModes: [],
|
||||
currentModeId: null,
|
||||
pendingPermissions: new Map(),
|
||||
@@ -823,10 +831,10 @@ export class AgentManager {
|
||||
timeline: [],
|
||||
persistence: session.describePersistence(),
|
||||
historyPrimed: false,
|
||||
lastUserMessageAt: timestamps?.lastUserMessageAt ?? null,
|
||||
lastUserMessageAt: options?.lastUserMessageAt ?? null,
|
||||
attention: { requiresAttention: false },
|
||||
parentAgentId: config.parentAgentId,
|
||||
internal: config.internal ?? false,
|
||||
labels: options?.labels,
|
||||
} as ActiveManagedAgent;
|
||||
|
||||
this.agents.set(agentId, managed);
|
||||
@@ -1093,7 +1101,8 @@ export class AgentManager {
|
||||
|
||||
|
||||
private async normalizeConfig(
|
||||
config: AgentSessionConfig
|
||||
config: AgentSessionConfig,
|
||||
options?: { labels?: Record<string, string> }
|
||||
): Promise<AgentSessionConfig> {
|
||||
const normalized: AgentSessionConfig = { ...config };
|
||||
|
||||
@@ -1107,14 +1116,9 @@ export class AgentManager {
|
||||
normalized.model = trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
if (!normalized.agentControlMcp && this.agentControlMcp) {
|
||||
normalized.agentControlMcp = this.agentControlMcp;
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.paseoPromptInstructions === undefined &&
|
||||
normalized.agentControlMcp
|
||||
) {
|
||||
// Inject paseoPromptInstructions for UI agents (with ui=true label)
|
||||
const isUiAgent = options?.labels?.ui === "true";
|
||||
if (isUiAgent) {
|
||||
normalized.paseoPromptInstructions = getSelfIdentificationInstructions({
|
||||
cwd: normalized.cwd,
|
||||
});
|
||||
|
||||
@@ -102,9 +102,6 @@ describe("agent MCP end-to-end", () => {
|
||||
mcpDebug: false,
|
||||
agentClients: {},
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
agentControlMcp: {
|
||||
url: `http://127.0.0.1:${port}/mcp/agents`,
|
||||
},
|
||||
};
|
||||
|
||||
const previousCodexSessionDir = process.env.CODEX_SESSION_DIR;
|
||||
@@ -235,9 +232,6 @@ describe("agent MCP end-to-end", () => {
|
||||
mcpDebug: false,
|
||||
agentClients: {},
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
agentControlMcp: {
|
||||
url: `http://127.0.0.1:${port}/mcp/agents`,
|
||||
},
|
||||
};
|
||||
|
||||
const previousCodexSessionDir = process.env.CODEX_SESSION_DIR;
|
||||
|
||||
@@ -44,6 +44,7 @@ export function toStoredAgentRecord(
|
||||
? agent.lastUserMessageAt.toISOString()
|
||||
: null,
|
||||
title: options?.title ?? null,
|
||||
labels: agent.labels && Object.keys(agent.labels).length > 0 ? agent.labels : undefined,
|
||||
lastStatus: agent.lifecycle,
|
||||
lastModeId: agent.currentModeId ?? config?.modeId ?? null,
|
||||
config: config ?? null,
|
||||
@@ -56,7 +57,6 @@ export function toStoredAgentRecord(
|
||||
attentionTimestamp: agent.attention.requiresAttention
|
||||
? agent.attention.attentionTimestamp.toISOString()
|
||||
: null,
|
||||
parentAgentId: agent.parentAgentId ?? null,
|
||||
internal: options?.internal,
|
||||
} satisfies StoredAgentRecord;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export function toAgentPayload(
|
||||
pendingPermissions: sanitizePendingPermissions(agent.pendingPermissions),
|
||||
persistence: sanitizePersistenceHandle(agent.persistence),
|
||||
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);
|
||||
|
||||
@@ -154,7 +154,6 @@ describe("getStructuredAgentResponse (e2e)", () => {
|
||||
cwd = mkdtempSync(path.join(tmpdir(), "agent-response-loop-"));
|
||||
manager = new AgentManager({
|
||||
clients: createAllClients(logger),
|
||||
agentControlMcp: { url: agentMcpServer.url },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,11 +171,6 @@ export type AgentCommandResult = {
|
||||
usage?: AgentUsage;
|
||||
};
|
||||
|
||||
export type AgentControlMcpConfig = {
|
||||
url: string;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type ListPersistedAgentsOptions = {
|
||||
limit?: number;
|
||||
};
|
||||
@@ -201,7 +196,6 @@ export type AgentSessionConfig = {
|
||||
networkAccess?: boolean;
|
||||
webSearch?: boolean;
|
||||
reasoningEffort?: string;
|
||||
agentControlMcp?: AgentControlMcpConfig;
|
||||
/**
|
||||
* Paseo-owned instructions injected into the first user prompt via
|
||||
* <paseo-instructions>...</paseo-instructions>.
|
||||
@@ -215,7 +209,6 @@ export type AgentSessionConfig = {
|
||||
claude?: Partial<ClaudeAgentOptions>;
|
||||
};
|
||||
mcpServers?: AgentMetadata;
|
||||
parentAgentId?: string;
|
||||
/**
|
||||
* Internal agents are hidden from listings and don't trigger notifications.
|
||||
* They are used for ephemeral system tasks like commit/PR generation.
|
||||
@@ -239,12 +232,6 @@ export interface AgentSession {
|
||||
describePersistence(): AgentPersistenceHandle | null;
|
||||
interrupt(): 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.
|
||||
* 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(),
|
||||
lastUserMessageAt: z.string().nullable().optional(),
|
||||
title: z.string().nullable().optional(),
|
||||
labels: z.record(z.string()).optional(),
|
||||
lastStatus: AgentStatusSchema.default("closed"),
|
||||
lastModeId: z.string().nullable().optional(),
|
||||
config: SERIALIZABLE_CONFIG_SCHEMA,
|
||||
@@ -53,7 +54,6 @@ const STORED_AGENT_SCHEMA = z.object({
|
||||
requiresAttention: z.boolean().optional(),
|
||||
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
|
||||
attentionTimestamp: z.string().nullable().optional(),
|
||||
parentAgentId: z.string().nullable().optional(),
|
||||
internal: z.boolean().optional(),
|
||||
archivedAt: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface AgentMcpServerOptions {
|
||||
paseoHome?: string;
|
||||
/**
|
||||
* 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;
|
||||
logger: Logger;
|
||||
@@ -350,12 +350,6 @@ export async function createAgentMcpServer(
|
||||
.describe(
|
||||
"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
|
||||
@@ -400,12 +394,10 @@ export async function createAgentMcpServer(
|
||||
worktreeName?: string;
|
||||
background?: boolean;
|
||||
title: string;
|
||||
parentAgentId?: string;
|
||||
};
|
||||
|
||||
let resolvedCwd: string;
|
||||
let resolvedMode: string | undefined;
|
||||
let resolvedParentAgentId: string | undefined;
|
||||
|
||||
if (callerAgentId) {
|
||||
const parentAgent = agentManager.getAgent(callerAgentId);
|
||||
@@ -413,7 +405,6 @@ export async function createAgentMcpServer(
|
||||
throw new Error(`Parent agent ${callerAgentId} not found`);
|
||||
}
|
||||
resolvedCwd = parentAgent.cwd;
|
||||
resolvedParentAgentId = callerAgentId;
|
||||
|
||||
const provider: AgentProvider = agentType ?? "claude";
|
||||
const parentMode = parentAgent.currentModeId;
|
||||
@@ -430,14 +421,12 @@ export async function createAgentMcpServer(
|
||||
initialMode: string;
|
||||
worktreeName?: string;
|
||||
baseBranch?: string;
|
||||
parentAgentId?: string;
|
||||
};
|
||||
const {
|
||||
cwd,
|
||||
initialMode,
|
||||
worktreeName,
|
||||
baseBranch,
|
||||
parentAgentId,
|
||||
} = topLevelArgs;
|
||||
|
||||
resolvedCwd = expandPath(cwd);
|
||||
@@ -457,7 +446,6 @@ export async function createAgentMcpServer(
|
||||
}
|
||||
|
||||
resolvedMode = initialMode;
|
||||
resolvedParentAgentId = parentAgentId;
|
||||
}
|
||||
|
||||
const provider: AgentProvider = agentType ?? "claude";
|
||||
@@ -467,7 +455,6 @@ export async function createAgentMcpServer(
|
||||
cwd: resolvedCwd,
|
||||
modeId: resolvedMode,
|
||||
title: normalizedTitle ?? undefined,
|
||||
parentAgentId: resolvedParentAgentId,
|
||||
});
|
||||
|
||||
if (initialPrompt) {
|
||||
|
||||
@@ -26,9 +26,6 @@ const hasClaudeCredentials =
|
||||
provider: "claude",
|
||||
cwd: process.cwd(),
|
||||
modeId: "plan",
|
||||
agentControlMcp: {
|
||||
url: "http://localhost:6767/mcp", // Placeholder - not actually used in plan mode
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
@@ -240,7 +240,6 @@ async function startAgentMcpServer(): Promise<AgentMcpServerHandle> {
|
||||
provider: "claude",
|
||||
cwd,
|
||||
modeId: options?.modeId,
|
||||
agentControlMcp: { url: agentMcpServer.url },
|
||||
extra: {
|
||||
claude: {
|
||||
sandbox: { enabled: true, autoAllowBashIfSandboxed: false },
|
||||
|
||||
@@ -116,16 +116,6 @@ type ClaudeAgentSessionOptions = {
|
||||
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 {
|
||||
if (typeof content === "string") {
|
||||
@@ -228,25 +218,6 @@ function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial<Age
|
||||
if (typeof metadata.reasoningEffort === "string") {
|
||||
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)) {
|
||||
const extra: AgentSessionConfig["extra"] = {};
|
||||
if (isMetadata(metadata.extra.codex)) {
|
||||
@@ -262,9 +233,6 @@ function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial<Age
|
||||
if (isMetadata(metadata.mcpServers)) {
|
||||
result.mcpServers = metadata.mcpServers;
|
||||
}
|
||||
if (typeof metadata.parentAgentId === "string") {
|
||||
result.parentAgentId = metadata.parentAgentId;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -432,7 +400,6 @@ class ClaudeAgentSession implements AgentSession {
|
||||
private activeTurnPromise: Promise<void> | null = null;
|
||||
private cachedRuntimeInfo: AgentRuntimeInfo | null = null;
|
||||
private lastOptionsModel: string | null = null;
|
||||
private managedAgentId: string | null = null;
|
||||
|
||||
constructor(
|
||||
config: ClaudeAgentConfig,
|
||||
@@ -722,10 +689,6 @@ class ClaudeAgentSession implements AgentSession {
|
||||
this.input = null;
|
||||
}
|
||||
|
||||
setManagedAgentId(agentId: string): void {
|
||||
this.managedAgentId = agentId;
|
||||
}
|
||||
|
||||
async listCommands(): Promise<AgentSlashCommand[]> {
|
||||
const q = await this.ensureQuery();
|
||||
const commands = await q.supportedCommands();
|
||||
@@ -819,28 +782,11 @@ class ClaudeAgentSession implements AgentSession {
|
||||
...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) {
|
||||
const normalizedUserServers = this.normalizeMcpServers(this.config.mcpServers);
|
||||
// Merge user-provided MCP servers with defaults, user servers take precedence
|
||||
base.mcpServers = { ...defaultMcpServers, ...normalizedUserServers };
|
||||
} else {
|
||||
base.mcpServers = defaultMcpServers;
|
||||
if (normalizedUserServers) {
|
||||
base.mcpServers = normalizedUserServers;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.config.model) {
|
||||
|
||||
@@ -1859,11 +1859,6 @@ const PermissionParamsSchema = z
|
||||
|
||||
type PermissionParams = z.infer<typeof PermissionParamsSchema>;
|
||||
|
||||
const AgentControlMcpConfigSchema = z.object({
|
||||
url: z.string(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
type AgentSessionExtra = NonNullable<AgentSessionConfig["extra"]>;
|
||||
|
||||
const AgentSessionExtraSchema = z.object({
|
||||
@@ -1883,10 +1878,8 @@ const AgentSessionConfigSchema = z
|
||||
networkAccess: z.boolean().optional(),
|
||||
webSearch: z.boolean().optional(),
|
||||
reasoningEffort: z.string().optional(),
|
||||
agentControlMcp: AgentControlMcpConfigSchema.optional(),
|
||||
extra: AgentSessionExtraSchema.optional(),
|
||||
mcpServers: z.record(z.unknown()).optional(),
|
||||
parentAgentId: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -2718,7 +2711,6 @@ function buildCodexMcpConfig(
|
||||
config: AgentSessionConfig,
|
||||
prompt: string,
|
||||
modeId: string,
|
||||
managedAgentId?: string,
|
||||
experimentalResume?: string | null
|
||||
): {
|
||||
prompt: string;
|
||||
@@ -2762,21 +2754,6 @@ function buildCodexMcpConfig(
|
||||
// Build MCP servers configuration
|
||||
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)
|
||||
const extraCodex = config.extra?.codex as Record<string, unknown> | undefined;
|
||||
if (extraCodex?.mcp_servers && typeof extraCodex.mcp_servers === "object") {
|
||||
@@ -3037,7 +3014,6 @@ class CodexMcpAgentSession implements AgentSession {
|
||||
private turnState: TurnState | null = null;
|
||||
private pendingPatchChanges = new Map<string, PatchFileChange[]>();
|
||||
private patchChangesByCallId = new Map<string, PatchFileChange[]>();
|
||||
private managedAgentId: string | null = null;
|
||||
private resumeHandle: AgentPersistenceHandle | null = null;
|
||||
private pendingResumeFile: string | null = null;
|
||||
|
||||
@@ -3541,10 +3517,6 @@ class CodexMcpAgentSession implements AgentSession {
|
||||
this.conversationId = null;
|
||||
}
|
||||
|
||||
setManagedAgentId(agentId: string): void {
|
||||
this.managedAgentId = agentId;
|
||||
}
|
||||
|
||||
async listCommands(): Promise<AgentSlashCommand[]> {
|
||||
const [skills, prompts] = await Promise.all([
|
||||
listCodexSkills(this.config.cwd),
|
||||
@@ -3591,7 +3563,6 @@ class CodexMcpAgentSession implements AgentSession {
|
||||
this.config,
|
||||
prompt,
|
||||
this.currentMode,
|
||||
this.managedAgentId ?? undefined,
|
||||
resumeFile
|
||||
);
|
||||
const attempt = async (arguments_: CodexToolArguments) =>
|
||||
@@ -3631,7 +3602,7 @@ class CodexMcpAgentSession implements AgentSession {
|
||||
);
|
||||
if (isMissingConversationIdResponse(response)) {
|
||||
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) =>
|
||||
this.client.callTool(
|
||||
{ name: "codex", arguments: arguments_ },
|
||||
@@ -3654,7 +3625,7 @@ class CodexMcpAgentSession implements AgentSession {
|
||||
} catch (error) {
|
||||
if (isMissingConversationIdError(error)) {
|
||||
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) =>
|
||||
this.client.callTool(
|
||||
{ name: "codex", arguments: arguments_ },
|
||||
|
||||
@@ -56,7 +56,6 @@ import { printPairingQrIfEnabled } from "./pairing-qr.js";
|
||||
import { startRelayTransport, type RelayTransportController } from "./relay-transport.js";
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentControlMcpConfig,
|
||||
AgentProvider,
|
||||
} from "./agent/agent-sdk-types.js";
|
||||
|
||||
@@ -78,7 +77,6 @@ export type PaseoDaemonConfig = {
|
||||
mcpDebug: boolean;
|
||||
agentClients: Partial<Record<AgentProvider, AgentClient>>;
|
||||
agentStoragePath: string;
|
||||
agentControlMcp: AgentControlMcpConfig;
|
||||
relayEnabled?: boolean;
|
||||
relayEndpoint?: string;
|
||||
appBaseUrl?: string;
|
||||
@@ -211,7 +209,6 @@ export async function createPaseoDaemon(
|
||||
...config.agentClients,
|
||||
},
|
||||
registry: agentStorage,
|
||||
agentControlMcp: config.agentControlMcp,
|
||||
logger,
|
||||
});
|
||||
|
||||
@@ -378,10 +375,7 @@ export async function createPaseoDaemon(
|
||||
agentStorage,
|
||||
downloadTokenStore,
|
||||
config.paseoHome,
|
||||
{
|
||||
agentMcpUrl: config.agentControlMcp.url,
|
||||
agentMcpHeaders: config.agentControlMcp.headers,
|
||||
},
|
||||
agentMcpRoute,
|
||||
{ allowedOrigins },
|
||||
{ stt: sttService, tts: ttsService },
|
||||
terminalManager
|
||||
|
||||
@@ -80,9 +80,6 @@ export function loadConfig(
|
||||
agentMcpRoute: DEFAULT_AGENT_MCP_ROUTE,
|
||||
agentMcpAllowedHosts: [mcpListen, `localhost:${mcpListen.split(":")[1]}`],
|
||||
mcpDebug: env.MCP_DEBUG === "1",
|
||||
agentControlMcp: {
|
||||
url: `http://${mcpListen}${DEFAULT_AGENT_MCP_ROUTE}`,
|
||||
},
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
staticDir: "public",
|
||||
agentClients: {},
|
||||
|
||||
@@ -57,9 +57,6 @@ async function startDaemon(options: {
|
||||
mcpDebug: false,
|
||||
agentClients: {},
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
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 path from "path";
|
||||
import {
|
||||
createDaemonTestContext,
|
||||
type DaemonTestContext,
|
||||
} from "../test-utils/index.js";
|
||||
import type { AgentTimelineItem } from "../agent/agent-sdk-types.js";
|
||||
import type { AgentSnapshotPayload, SessionOutboundMessage } from "../messages.js";
|
||||
|
||||
function tmpCwd(): string {
|
||||
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", () => {
|
||||
let ctx: DaemonTestContext;
|
||||
|
||||
@@ -29,140 +23,10 @@ describe("daemon E2E", () => {
|
||||
}, 60000);
|
||||
|
||||
describe("multi-agent orchestration", () => {
|
||||
test(
|
||||
"parent agent creates child agent via agent-control MCP",
|
||||
async () => {
|
||||
const cwd = tmpCwd();
|
||||
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
|
||||
);
|
||||
// TODO: Re-implement orchestration tests with new Paseo MCP
|
||||
// The old agent-control MCP has been removed
|
||||
test("placeholder for future orchestration tests", async () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -77,10 +77,16 @@ export function buildSessionConfig(
|
||||
|
||||
export function extractTimestamps(
|
||||
record: StoredAgentRecord
|
||||
): { createdAt: Date; updatedAt: Date; lastUserMessageAt: Date | null } {
|
||||
): {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
lastUserMessageAt: Date | null;
|
||||
labels?: Record<string, string>;
|
||||
} {
|
||||
return {
|
||||
createdAt: new Date(record.createdAt),
|
||||
updatedAt: new Date(record.lastActivityAt ?? record.updatedAt),
|
||||
lastUserMessageAt: record.lastUserMessageAt ? new Date(record.lastUserMessageAt) : null,
|
||||
labels: record.labels,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,11 +104,6 @@ import { getProjectIcon } from "../utils/project-icon.js";
|
||||
import { expandTilde } from "../utils/path.js";
|
||||
import type pino from "pino";
|
||||
|
||||
type AgentMcpClientConfig = {
|
||||
agentMcpUrl: string;
|
||||
agentMcpHeaders?: Record<string, string>;
|
||||
};
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
@@ -294,7 +289,7 @@ export class Session {
|
||||
private agentTools: ToolSet | null = null;
|
||||
private agentManager: AgentManager;
|
||||
private readonly agentStorage: AgentStorage;
|
||||
private readonly agentMcpConfig: AgentMcpClientConfig;
|
||||
private readonly agentMcpRoute: string;
|
||||
private readonly downloadTokenStore: DownloadTokenStore;
|
||||
private readonly pushTokenStore: PushTokenStore;
|
||||
private readonly providerRegistry: ReturnType<typeof buildProviderRegistry>;
|
||||
@@ -317,7 +312,7 @@ export class Session {
|
||||
paseoHome: string,
|
||||
agentManager: AgentManager,
|
||||
agentStorage: AgentStorage,
|
||||
agentMcpConfig: AgentMcpClientConfig,
|
||||
agentMcpRoute: string,
|
||||
stt: OpenAISTT | null,
|
||||
tts: OpenAITTS | null,
|
||||
terminalManager: TerminalManager | null,
|
||||
@@ -331,7 +326,7 @@ export class Session {
|
||||
this.paseoHome = paseoHome;
|
||||
this.agentManager = agentManager;
|
||||
this.agentStorage = agentStorage;
|
||||
this.agentMcpConfig = agentMcpConfig;
|
||||
this.agentMcpRoute = agentMcpRoute;
|
||||
this.terminalManager = terminalManager;
|
||||
this.voiceConversationStore = voiceConversationStore;
|
||||
this.abortController = new AbortController();
|
||||
@@ -505,15 +500,10 @@ export class Session {
|
||||
*/
|
||||
private async initializeAgentMcp(): Promise<void> {
|
||||
try {
|
||||
// Connect to the local MCP server using localhost
|
||||
const agentMcpUrl = `http://127.0.0.1:6767${this.agentMcpRoute}`;
|
||||
const transport = new StreamableHTTPClientTransport(
|
||||
new URL(this.agentMcpConfig.agentMcpUrl),
|
||||
this.agentMcpConfig.agentMcpHeaders
|
||||
? {
|
||||
requestInit: {
|
||||
headers: this.agentMcpConfig.agentMcpHeaders,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
new URL(agentMcpUrl)
|
||||
);
|
||||
|
||||
this.agentMcpClient = await experimental_createMCPClient({
|
||||
@@ -632,7 +622,6 @@ export class Session {
|
||||
requiresAttention: record.requiresAttention ?? false,
|
||||
attentionReason: record.attentionReason ?? null,
|
||||
attentionTimestamp: record.attentionTimestamp ?? null,
|
||||
parentAgentId: record.parentAgentId ?? null,
|
||||
archivedAt: record.archivedAt ?? null,
|
||||
};
|
||||
}
|
||||
@@ -1326,7 +1315,7 @@ export class Session {
|
||||
private async handleCreateAgentRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "create_agent_request" }>
|
||||
): Promise<void> {
|
||||
const { config, worktreeName, requestId, initialPrompt, git, images } = msg;
|
||||
const { config, worktreeName, requestId, initialPrompt, git, images, labels } = msg;
|
||||
this.sessionLogger.info(
|
||||
{ cwd: config.cwd, provider: config.provider, worktreeName },
|
||||
`Creating agent in ${config.cwd} (${config.provider})${
|
||||
@@ -1356,9 +1345,14 @@ export class Session {
|
||||
const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig(
|
||||
config,
|
||||
git,
|
||||
worktreeName
|
||||
worktreeName,
|
||||
labels
|
||||
);
|
||||
const snapshot = await this.agentManager.createAgent(
|
||||
sessionConfig,
|
||||
undefined,
|
||||
{ labels }
|
||||
);
|
||||
const snapshot = await this.agentManager.createAgent(sessionConfig);
|
||||
await this.forwardAgentState(snapshot);
|
||||
|
||||
const trimmedPrompt = initialPrompt?.trim();
|
||||
@@ -1584,7 +1578,8 @@ export class Session {
|
||||
private async buildAgentSessionConfig(
|
||||
config: AgentSessionConfig,
|
||||
gitOptions?: GitSetupOptions,
|
||||
legacyWorktreeName?: string
|
||||
legacyWorktreeName?: string,
|
||||
_labels?: Record<string, string>
|
||||
): Promise<{ sessionConfig: AgentSessionConfig; worktreeConfig?: WorktreeConfig }> {
|
||||
let cwd = expandTilde(config.cwd);
|
||||
const normalized = this.normalizeGitOptions(gitOptions, legacyWorktreeName);
|
||||
|
||||
@@ -64,9 +64,6 @@ export async function createTestPaseoDaemon(
|
||||
mcpDebug: false,
|
||||
agentClients: {},
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
agentControlMcp: {
|
||||
url: `http://127.0.0.1:${port}/mcp/agents`,
|
||||
},
|
||||
relayEnabled: options.relayEnabled ?? false,
|
||||
relayEndpoint: options.relayEndpoint ?? "relay.paseo.sh:443",
|
||||
appBaseUrl: "https://app.paseo.sh",
|
||||
|
||||
@@ -10,11 +10,6 @@ import type pino from "pino";
|
||||
import type { WSOutboundMessage } from "./messages.js";
|
||||
import { WebSocketSessionBridge } from "./websocket-session-bridge.js";
|
||||
|
||||
type AgentMcpClientConfig = {
|
||||
agentMcpUrl: string;
|
||||
agentMcpHeaders?: Record<string, string>;
|
||||
};
|
||||
|
||||
type WebSocketServerConfig = {
|
||||
allowedOrigins: Set<string>;
|
||||
};
|
||||
@@ -34,7 +29,7 @@ export class VoiceAssistantWebSocketServer {
|
||||
agentStorage: AgentStorage,
|
||||
downloadTokenStore: DownloadTokenStore,
|
||||
paseoHome: string,
|
||||
agentMcpConfig: AgentMcpClientConfig,
|
||||
agentMcpRoute: string,
|
||||
wsConfig: WebSocketServerConfig,
|
||||
speech?: { stt: OpenAISTT | null; tts: OpenAITTS | null },
|
||||
terminalManager?: TerminalManager | null
|
||||
@@ -46,7 +41,7 @@ export class VoiceAssistantWebSocketServer {
|
||||
agentStorage,
|
||||
downloadTokenStore,
|
||||
paseoHome,
|
||||
agentMcpConfig,
|
||||
agentMcpRoute,
|
||||
speech,
|
||||
terminalManager
|
||||
);
|
||||
|
||||
@@ -19,11 +19,6 @@ import type { OpenAITTS } from "./agent/tts-openai.js";
|
||||
import type { TerminalManager } from "../terminal/terminal-manager.js";
|
||||
import type pino from "pino";
|
||||
|
||||
type AgentMcpClientConfig = {
|
||||
agentMcpUrl: string;
|
||||
agentMcpHeaders?: Record<string, string>;
|
||||
};
|
||||
|
||||
export class WebSocketSessionBridge {
|
||||
private readonly logger: pino.Logger;
|
||||
private readonly sessions: Map<WebSocket, Session> = new Map();
|
||||
@@ -34,7 +29,7 @@ export class WebSocketSessionBridge {
|
||||
private readonly paseoHome: string;
|
||||
private readonly pushTokenStore: PushTokenStore;
|
||||
private readonly pushService: PushService;
|
||||
private readonly agentMcpConfig: AgentMcpClientConfig;
|
||||
private readonly agentMcpRoute: string;
|
||||
private readonly stt: OpenAISTT | null;
|
||||
private readonly tts: OpenAITTS | null;
|
||||
private readonly terminalManager: TerminalManager | null;
|
||||
@@ -46,7 +41,7 @@ export class WebSocketSessionBridge {
|
||||
agentStorage: AgentStorage,
|
||||
downloadTokenStore: DownloadTokenStore,
|
||||
paseoHome: string,
|
||||
agentMcpConfig: AgentMcpClientConfig,
|
||||
agentMcpRoute: string,
|
||||
speech?: { stt: OpenAISTT | null; tts: OpenAITTS | null },
|
||||
terminalManager?: TerminalManager | null
|
||||
) {
|
||||
@@ -55,7 +50,7 @@ export class WebSocketSessionBridge {
|
||||
this.agentStorage = agentStorage;
|
||||
this.downloadTokenStore = downloadTokenStore;
|
||||
this.paseoHome = paseoHome;
|
||||
this.agentMcpConfig = agentMcpConfig;
|
||||
this.agentMcpRoute = agentMcpRoute;
|
||||
this.stt = speech?.stt ?? null;
|
||||
this.tts = speech?.tts ?? null;
|
||||
this.terminalManager = terminalManager ?? null;
|
||||
@@ -91,7 +86,7 @@ export class WebSocketSessionBridge {
|
||||
this.paseoHome,
|
||||
this.agentManager,
|
||||
this.agentStorage,
|
||||
this.agentMcpConfig,
|
||||
this.agentMcpRoute,
|
||||
this.stt,
|
||||
this.tts,
|
||||
this.terminalManager,
|
||||
|
||||
@@ -63,12 +63,6 @@ const AgentSessionConfigSchema = z.object({
|
||||
networkAccess: z.boolean().optional(),
|
||||
webSearch: z.boolean().optional(),
|
||||
reasoningEffort: z.string().optional(),
|
||||
agentControlMcp: z
|
||||
.object({
|
||||
url: z.string(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
extra: z
|
||||
.object({
|
||||
codex: z.record(z.unknown()).optional(),
|
||||
@@ -247,10 +241,10 @@ export const AgentSnapshotPayloadSchema = z.object({
|
||||
lastUsage: AgentUsageSchema.optional(),
|
||||
lastError: z.string().optional(),
|
||||
title: z.string().nullable(),
|
||||
labels: z.record(z.string()).optional(),
|
||||
requiresAttention: z.boolean().optional(),
|
||||
attentionReason: z.enum(["finished", "error", "permission"]).nullable().optional(),
|
||||
attentionTimestamp: z.string().nullable().optional(),
|
||||
parentAgentId: 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"
|
||||
})).optional(),
|
||||
git: GitSetupOptionsSchema.optional(),
|
||||
labels: z.record(z.string()).optional(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user