Move agent-control MCP config into AgentManager
This commit is contained in:
@@ -18,6 +18,7 @@ import type {
|
|||||||
AgentTimelineItem,
|
AgentTimelineItem,
|
||||||
AgentUsage,
|
AgentUsage,
|
||||||
AgentRuntimeInfo,
|
AgentRuntimeInfo,
|
||||||
|
AgentControlMcpConfig,
|
||||||
ListPersistedAgentsOptions,
|
ListPersistedAgentsOptions,
|
||||||
PersistedAgentDescriptor,
|
PersistedAgentDescriptor,
|
||||||
} from "./agent-sdk-types.js";
|
} from "./agent-sdk-types.js";
|
||||||
@@ -54,6 +55,7 @@ export type AgentManagerOptions = {
|
|||||||
maxTimelineItems?: number;
|
maxTimelineItems?: number;
|
||||||
idFactory?: () => string;
|
idFactory?: () => string;
|
||||||
registry?: AgentRegistry;
|
registry?: AgentRegistry;
|
||||||
|
agentControlMcp?: AgentControlMcpConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WaitForAgentOptions = {
|
export type WaitForAgentOptions = {
|
||||||
@@ -178,12 +180,14 @@ export class AgentManager {
|
|||||||
private readonly idFactory: () => string;
|
private readonly idFactory: () => string;
|
||||||
private readonly registry?: AgentRegistry;
|
private readonly registry?: AgentRegistry;
|
||||||
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
|
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
|
||||||
|
private readonly agentControlMcp?: AgentControlMcpConfig;
|
||||||
|
|
||||||
constructor(options?: AgentManagerOptions) {
|
constructor(options?: AgentManagerOptions) {
|
||||||
this.maxTimelineItems =
|
this.maxTimelineItems =
|
||||||
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;
|
||||||
if (options?.clients) {
|
if (options?.clients) {
|
||||||
for (const [provider, client] of Object.entries(options.clients)) {
|
for (const [provider, client] of Object.entries(options.clients)) {
|
||||||
if (client) {
|
if (client) {
|
||||||
@@ -982,6 +986,10 @@ export class AgentManager {
|
|||||||
normalized.model = trimmed.length > 0 ? trimmed : undefined;
|
normalized.model = trimmed.length > 0 ? trimmed : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!normalized.agentControlMcp && this.agentControlMcp) {
|
||||||
|
normalized.agentControlMcp = this.agentControlMcp;
|
||||||
|
}
|
||||||
|
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ export type AgentRuntimeInfo = {
|
|||||||
extra?: Record<string, unknown>;
|
extra?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AgentControlMcpConfig = {
|
||||||
|
url: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
export type ListPersistedAgentsOptions = {
|
export type ListPersistedAgentsOptions = {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
};
|
};
|
||||||
@@ -156,6 +161,7 @@ export type AgentSessionConfig = {
|
|||||||
networkAccess?: boolean;
|
networkAccess?: boolean;
|
||||||
webSearch?: boolean;
|
webSearch?: boolean;
|
||||||
reasoningEffort?: string;
|
reasoningEffort?: string;
|
||||||
|
agentControlMcp?: AgentControlMcpConfig;
|
||||||
extra?: {
|
extra?: {
|
||||||
codex?: Record<string, unknown>;
|
codex?: Record<string, unknown>;
|
||||||
claude?: Partial<ClaudeAgentOptions>;
|
claude?: Partial<ClaudeAgentOptions>;
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ export interface AgentMcpServerOptions {
|
|||||||
* When set, create_agent will auto-inject this as parentAgentId.
|
* When set, create_agent will auto-inject this as parentAgentId.
|
||||||
*/
|
*/
|
||||||
callerAgentId?: string;
|
callerAgentId?: string;
|
||||||
/**
|
|
||||||
* Agent-control MCP URL to inject for Codex agents spawned via MCP.
|
|
||||||
*/
|
|
||||||
agentControlMcpUrl?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AgentProviderEnum = z.enum(
|
const AgentProviderEnum = z.enum(
|
||||||
@@ -175,8 +171,7 @@ async function serializeSnapshotWithMetadata(
|
|||||||
export async function createAgentMcpServer(
|
export async function createAgentMcpServer(
|
||||||
options: AgentMcpServerOptions
|
options: AgentMcpServerOptions
|
||||||
): Promise<McpServer> {
|
): Promise<McpServer> {
|
||||||
const { agentManager, agentRegistry, callerAgentId, agentControlMcpUrl } =
|
const { agentManager, agentRegistry, callerAgentId } = options;
|
||||||
options;
|
|
||||||
const waitTracker = new WaitForAgentTracker();
|
const waitTracker = new WaitForAgentTracker();
|
||||||
|
|
||||||
const server = new McpServer({
|
const server = new McpServer({
|
||||||
@@ -279,17 +274,12 @@ export async function createAgentMcpServer(
|
|||||||
const normalizedTitle = title?.trim() ?? null;
|
const normalizedTitle = title?.trim() ?? null;
|
||||||
// Use explicit parentAgentId if provided, otherwise default to caller agent ID
|
// Use explicit parentAgentId if provided, otherwise default to caller agent ID
|
||||||
const resolvedParentAgentId = parentAgentId ?? callerAgentId;
|
const resolvedParentAgentId = parentAgentId ?? callerAgentId;
|
||||||
const codexExtra =
|
|
||||||
provider === "codex" && agentControlMcpUrl
|
|
||||||
? { codex: { agentControlMcpUrl } }
|
|
||||||
: undefined;
|
|
||||||
const snapshot = await agentManager.createAgent({
|
const snapshot = await agentManager.createAgent({
|
||||||
provider,
|
provider,
|
||||||
cwd: resolvedCwd,
|
cwd: resolvedCwd,
|
||||||
modeId: initialMode,
|
modeId: initialMode,
|
||||||
title: normalizedTitle ?? undefined,
|
title: normalizedTitle ?? undefined,
|
||||||
parentAgentId: resolvedParentAgentId,
|
parentAgentId: resolvedParentAgentId,
|
||||||
extra: codexExtra,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (initialPrompt) {
|
if (initialPrompt) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import type {
|
|||||||
AgentPromptInput,
|
AgentPromptInput,
|
||||||
AgentRunOptions,
|
AgentRunOptions,
|
||||||
AgentRunResult,
|
AgentRunResult,
|
||||||
|
AgentControlMcpConfig,
|
||||||
AgentSession,
|
AgentSession,
|
||||||
AgentSessionConfig,
|
AgentSessionConfig,
|
||||||
AgentStreamEvent,
|
AgentStreamEvent,
|
||||||
@@ -83,20 +84,13 @@ type ClaudeAgentConfig = AgentSessionConfig & { provider: "claude" };
|
|||||||
|
|
||||||
export type ClaudeContentChunk = { type: string; [key: string]: any };
|
export type ClaudeContentChunk = { type: string; [key: string]: any };
|
||||||
|
|
||||||
type AgentControlMcpConfig = {
|
|
||||||
url: string;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ClaudeAgentClientOptions = {
|
type ClaudeAgentClientOptions = {
|
||||||
defaults?: { agents?: Record<string, AgentDefinition> };
|
defaults?: { agents?: Record<string, AgentDefinition> };
|
||||||
agentControlMcp?: AgentControlMcpConfig;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClaudeAgentSessionOptions = {
|
type ClaudeAgentSessionOptions = {
|
||||||
defaults?: { agents?: Record<string, AgentDefinition> };
|
defaults?: { agents?: Record<string, AgentDefinition> };
|
||||||
handle?: AgentPersistenceHandle;
|
handle?: AgentPersistenceHandle;
|
||||||
agentControlMcp?: AgentControlMcpConfig;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_AGENT_CONTROL_MCP: AgentControlMcpConfig = {
|
const DEFAULT_AGENT_CONTROL_MCP: AgentControlMcpConfig = {
|
||||||
@@ -185,18 +179,15 @@ export class ClaudeAgentClient implements AgentClient {
|
|||||||
readonly capabilities = CLAUDE_CAPABILITIES;
|
readonly capabilities = CLAUDE_CAPABILITIES;
|
||||||
|
|
||||||
private readonly defaults?: { agents?: Record<string, AgentDefinition> };
|
private readonly defaults?: { agents?: Record<string, AgentDefinition> };
|
||||||
private readonly agentControlMcp?: AgentControlMcpConfig;
|
|
||||||
|
|
||||||
constructor(options?: ClaudeAgentClientOptions) {
|
constructor(options?: ClaudeAgentClientOptions) {
|
||||||
this.defaults = options?.defaults;
|
this.defaults = options?.defaults;
|
||||||
this.agentControlMcp = options?.agentControlMcp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
||||||
const claudeConfig = this.assertConfig(config);
|
const claudeConfig = this.assertConfig(config);
|
||||||
return new ClaudeAgentSession(claudeConfig, {
|
return new ClaudeAgentSession(claudeConfig, {
|
||||||
defaults: this.defaults,
|
defaults: this.defaults,
|
||||||
agentControlMcp: this.agentControlMcp,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +204,6 @@ export class ClaudeAgentClient implements AgentClient {
|
|||||||
const claudeConfig = this.assertConfig(mergedConfig);
|
const claudeConfig = this.assertConfig(mergedConfig);
|
||||||
return new ClaudeAgentSession(claudeConfig, {
|
return new ClaudeAgentSession(claudeConfig, {
|
||||||
defaults: this.defaults,
|
defaults: this.defaults,
|
||||||
agentControlMcp: this.agentControlMcp,
|
|
||||||
handle,
|
handle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -254,7 +244,6 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
|
|
||||||
private readonly config: ClaudeAgentConfig;
|
private readonly config: ClaudeAgentConfig;
|
||||||
private readonly defaults?: { agents?: Record<string, AgentDefinition> };
|
private readonly defaults?: { agents?: Record<string, AgentDefinition> };
|
||||||
private readonly agentControlMcp?: AgentControlMcpConfig;
|
|
||||||
private query: Query | null = null;
|
private query: Query | null = null;
|
||||||
private input: Pushable<SDKUserMessage> | null = null;
|
private input: Pushable<SDKUserMessage> | null = null;
|
||||||
private claudeSessionId: string | null;
|
private claudeSessionId: string | null;
|
||||||
@@ -283,7 +272,6 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
) {
|
) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.defaults = options?.defaults;
|
this.defaults = options?.defaults;
|
||||||
this.agentControlMcp = options?.agentControlMcp;
|
|
||||||
const handle = options?.handle;
|
const handle = options?.handle;
|
||||||
this.claudeSessionId = handle?.sessionId ?? handle?.nativeHandle ?? null;
|
this.claudeSessionId = handle?.sessionId ?? handle?.nativeHandle ?? null;
|
||||||
this.pendingLocalId = this.claudeSessionId ?? `claude-${randomUUID()}`;
|
this.pendingLocalId = this.claudeSessionId ?? `claude-${randomUUID()}`;
|
||||||
@@ -575,7 +563,8 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Always include the agent-control MCP server so agents can launch other agents
|
// Always include the agent-control MCP server so agents can launch other agents
|
||||||
const agentControlConfig = this.agentControlMcp ?? DEFAULT_AGENT_CONTROL_MCP;
|
const agentControlConfig =
|
||||||
|
this.config.agentControlMcp ?? DEFAULT_AGENT_CONTROL_MCP;
|
||||||
const agentControlUrl = this.managedAgentId
|
const agentControlUrl = this.managedAgentId
|
||||||
? appendCallerAgentId(agentControlConfig.url, this.managedAgentId)
|
? appendCallerAgentId(agentControlConfig.url, this.managedAgentId)
|
||||||
: agentControlConfig.url;
|
: agentControlConfig.url;
|
||||||
|
|||||||
@@ -843,7 +843,7 @@ describe("CodexAgentClient (SDK integration)", () => {
|
|||||||
provider: "codex",
|
provider: "codex",
|
||||||
cwd,
|
cwd,
|
||||||
modeId: "full-access",
|
modeId: "full-access",
|
||||||
extra: { codex: { agentControlMcpUrl: mcpServer.url } },
|
agentControlMcp: { url: mcpServer.url },
|
||||||
};
|
};
|
||||||
let session: Awaited<ReturnType<typeof client.createSession>> | null = null;
|
let session: Awaited<ReturnType<typeof client.createSession>> | null = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js
|
|||||||
type CodexAgentConfig = AgentSessionConfig & { provider: "codex" };
|
type CodexAgentConfig = AgentSessionConfig & { provider: "codex" };
|
||||||
|
|
||||||
type CodexExtraConfig = {
|
type CodexExtraConfig = {
|
||||||
agentControlMcpUrl?: string;
|
|
||||||
developerInstructions?: string;
|
developerInstructions?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -269,11 +268,7 @@ function detectSystemCodexPath(): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null {
|
function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null {
|
||||||
const extras = config.extra?.codex as CodexExtraConfig | undefined;
|
const baseUrl = config.agentControlMcp?.url;
|
||||||
if (!extras || typeof extras !== "object") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const baseUrl = extras.agentControlMcpUrl;
|
|
||||||
if (!baseUrl || typeof baseUrl !== "string") {
|
if (!baseUrl || typeof baseUrl !== "string") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,17 +93,16 @@ async function main() {
|
|||||||
const agentRegistry = new AgentRegistry();
|
const agentRegistry = new AgentRegistry();
|
||||||
const agentManager = new AgentManager({
|
const agentManager = new AgentManager({
|
||||||
clients: {
|
clients: {
|
||||||
claude: new ClaudeAgentClient({
|
claude: new ClaudeAgentClient(),
|
||||||
agentControlMcp: {
|
|
||||||
url: agentMcpUrl,
|
|
||||||
...(agentMcpAuthHeader
|
|
||||||
? { headers: { Authorization: agentMcpAuthHeader } }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
codex: new CodexAgentClient(),
|
codex: new CodexAgentClient(),
|
||||||
},
|
},
|
||||||
registry: agentRegistry,
|
registry: agentRegistry,
|
||||||
|
agentControlMcp: {
|
||||||
|
url: agentMcpUrl,
|
||||||
|
...(agentMcpAuthHeader
|
||||||
|
? { headers: { Authorization: agentMcpAuthHeader } }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
attachAgentRegistryPersistence(agentManager, agentRegistry);
|
attachAgentRegistryPersistence(agentManager, agentRegistry);
|
||||||
@@ -123,7 +122,6 @@ async function main() {
|
|||||||
agentManager,
|
agentManager,
|
||||||
agentRegistry,
|
agentRegistry,
|
||||||
callerAgentId,
|
callerAgentId,
|
||||||
agentControlMcpUrl: agentMcpUrl,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const transport = new StreamableHTTPServerTransport({
|
const transport = new StreamableHTTPServerTransport({
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ 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(),
|
||||||
|
|||||||
@@ -1256,8 +1256,10 @@ export class Session {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sessionConfig = this.withCodexAgentControl(
|
const sessionConfig = await this.buildAgentSessionConfig(
|
||||||
await this.buildAgentSessionConfig(config, git, worktreeName)
|
config,
|
||||||
|
git,
|
||||||
|
worktreeName
|
||||||
);
|
);
|
||||||
const snapshot = await this.agentManager.createAgent(sessionConfig);
|
const snapshot = await this.agentManager.createAgent(sessionConfig);
|
||||||
this.setCachedTitle(snapshot.id, null);
|
this.setCachedTitle(snapshot.id, null);
|
||||||
@@ -1352,13 +1354,9 @@ export class Session {
|
|||||||
`[Session ${this.clientId}] Resuming agent ${handle.sessionId} (${handle.provider})`
|
`[Session ${this.clientId}] Resuming agent ${handle.sessionId} (${handle.provider})`
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const normalizedOverrides = this.withCodexAgentControl(
|
|
||||||
overrides ?? {},
|
|
||||||
handle.provider
|
|
||||||
);
|
|
||||||
const snapshot = await this.agentManager.resumeAgent(
|
const snapshot = await this.agentManager.resumeAgent(
|
||||||
handle,
|
handle,
|
||||||
normalizedOverrides
|
overrides
|
||||||
);
|
);
|
||||||
this.setCachedTitle(snapshot.id, null);
|
this.setCachedTitle(snapshot.id, null);
|
||||||
await this.agentManager.primeAgentHistory(snapshot.id);
|
await this.agentManager.primeAgentHistory(snapshot.id);
|
||||||
@@ -1419,11 +1417,11 @@ export class Session {
|
|||||||
`Agent ${agentId} cannot be refreshed because it lacks persistence`
|
`Agent ${agentId} cannot be refreshed because it lacks persistence`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const overrides = this.withCodexAgentControl(
|
snapshot = await this.agentManager.resumeAgent(
|
||||||
|
handle,
|
||||||
buildConfigOverrides(record),
|
buildConfigOverrides(record),
|
||||||
record.provider
|
agentId
|
||||||
);
|
);
|
||||||
snapshot = await this.agentManager.resumeAgent(handle, overrides, agentId);
|
|
||||||
this.setCachedTitle(agentId, null);
|
this.setCachedTitle(agentId, null);
|
||||||
}
|
}
|
||||||
await this.agentManager.primeAgentHistory(agentId);
|
await this.agentManager.primeAgentHistory(agentId);
|
||||||
@@ -1534,37 +1532,6 @@ export class Session {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private withCodexAgentControl<T extends Partial<AgentSessionConfig>>(
|
|
||||||
config: T,
|
|
||||||
providerOverride?: AgentProvider
|
|
||||||
): T {
|
|
||||||
const provider =
|
|
||||||
providerOverride ?? (config as AgentSessionConfig).provider;
|
|
||||||
if (provider !== "codex") {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
const agentControlMcpUrl = this.agentMcpConfig?.agentMcpUrl;
|
|
||||||
if (!agentControlMcpUrl) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
const extra = (config.extra ?? {}) as AgentSessionConfig["extra"];
|
|
||||||
const codexExtra =
|
|
||||||
extra?.codex && typeof extra.codex === "object"
|
|
||||||
? { ...extra.codex }
|
|
||||||
: {};
|
|
||||||
if (typeof (codexExtra as Record<string, unknown>).agentControlMcpUrl !== "string") {
|
|
||||||
(codexExtra as Record<string, unknown>).agentControlMcpUrl =
|
|
||||||
agentControlMcpUrl;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
extra: {
|
|
||||||
...extra,
|
|
||||||
codex: codexExtra,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleGitRepoInfoRequest(
|
private async handleGitRepoInfoRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "git_repo_info_request" }>
|
msg: Extract<SessionInboundMessage, { type: "git_repo_info_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user