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,
|
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);
|
||||||
|
|||||||
@@ -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",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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**.
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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_ },
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user