mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
@@ -154,7 +154,7 @@ async function createTopLevelAgent(args?: Partial<StructuredContent>): Promise<s
|
||||
title: "Parity agent",
|
||||
provider: "claude/claude-test-model",
|
||||
initialPrompt: "say done and stop",
|
||||
mode: "bypassPermissions",
|
||||
settings: { modeId: "bypassPermissions" },
|
||||
background: true,
|
||||
...args,
|
||||
});
|
||||
@@ -240,7 +240,7 @@ beforeAll(async () => {
|
||||
title: "MCP parity parent",
|
||||
provider: "claude/claude-test-model",
|
||||
initialPrompt: "say done and stop",
|
||||
mode: "bypassPermissions",
|
||||
settings: { modeId: "bypassPermissions" },
|
||||
background: true,
|
||||
});
|
||||
parentAgentId = str(parentPayload.agentId);
|
||||
@@ -339,7 +339,7 @@ describe("Suite A: Core Fixes", () => {
|
||||
test("create_agent accepts provider features over MCP", async () => {
|
||||
let agentId: string | null = null;
|
||||
try {
|
||||
agentId = await createTopLevelAgent({ features: { test_feature: true } });
|
||||
agentId = await createTopLevelAgent({ settings: { features: { test_feature: true } } });
|
||||
const internalSnapshot = daemonHandle.daemon.agentManager.getAgent(agentId);
|
||||
expect(internalSnapshot?.config.featureValues).toEqual({ test_feature: true });
|
||||
|
||||
@@ -356,7 +356,7 @@ describe("Suite A: Core Fixes", () => {
|
||||
try {
|
||||
agentId = await createChildAgent({
|
||||
provider: "claude/claude-test-model",
|
||||
features: { test_feature: true },
|
||||
settings: { features: { test_feature: true } },
|
||||
});
|
||||
const internalSnapshot = daemonHandle.daemon.agentManager.getAgent(agentId);
|
||||
expect(internalSnapshot?.config.featureValues).toEqual({ test_feature: true });
|
||||
@@ -369,14 +369,13 @@ describe("Suite A: Core Fixes", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("set_agent_feature updates provider features over MCP", async () => {
|
||||
test("update_agent updates provider features over MCP", async () => {
|
||||
let agentId: string | null = null;
|
||||
try {
|
||||
agentId = await createTopLevelAgent({ features: { test_feature: false } });
|
||||
const updated = await callToolStructured(topLevelClient, "set_agent_feature", {
|
||||
agentId = await createTopLevelAgent({ settings: { features: { test_feature: false } } });
|
||||
const updated = await callToolStructured(topLevelClient, "update_agent", {
|
||||
agentId,
|
||||
featureId: "test_feature",
|
||||
value: true,
|
||||
settings: { features: { test_feature: true } },
|
||||
});
|
||||
expect(updated.success).toBe(true);
|
||||
const internalSnapshot = daemonHandle.daemon.agentManager.getAgent(agentId);
|
||||
@@ -390,15 +389,18 @@ describe("Suite A: Core Fixes", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("list_provider_features returns draft provider features over MCP", async () => {
|
||||
const payload = await callToolStructured(topLevelClient, "list_provider_features", {
|
||||
test("inspect_provider returns draft provider features over MCP", async () => {
|
||||
const payload = await callToolStructured(topLevelClient, "inspect_provider", {
|
||||
provider: "claude",
|
||||
cwd: parentAgentCwd,
|
||||
model: "claude-test-model",
|
||||
featureValues: { test_feature: true },
|
||||
settings: {
|
||||
model: "claude-test-model",
|
||||
features: { test_feature: true },
|
||||
},
|
||||
});
|
||||
|
||||
expect(payload.provider).toBe("claude");
|
||||
expect(payload.selectedModel).toBe("claude-test-model");
|
||||
expect(recordArr(payload.features)).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -117,7 +117,9 @@ function buildAgentManagerSpies() {
|
||||
createAgent: vi.fn(),
|
||||
waitForAgentEvent: vi.fn(),
|
||||
recordUserMessage: vi.fn(),
|
||||
setAgentMode: vi.fn(),
|
||||
setAgentMode: vi.fn().mockResolvedValue(undefined),
|
||||
setAgentModel: vi.fn().mockResolvedValue(undefined),
|
||||
setAgentThinkingOption: vi.fn().mockResolvedValue(undefined),
|
||||
setAgentFeature: vi.fn().mockResolvedValue(undefined),
|
||||
setLabels: vi.fn().mockResolvedValue(undefined),
|
||||
setTitle: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -446,7 +448,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const missingTitle = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
provider: "codex/gpt-5.4",
|
||||
initialPrompt: "test",
|
||||
});
|
||||
@@ -455,7 +457,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const tooLong = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
provider: "codex/gpt-5.4",
|
||||
title: "x".repeat(61),
|
||||
initialPrompt: "test",
|
||||
@@ -465,7 +467,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const ok = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
provider: "codex/gpt-5.4",
|
||||
title: "Short title",
|
||||
initialPrompt: "test",
|
||||
@@ -479,7 +481,7 @@ describe("create_agent MCP tool", () => {
|
||||
const tool = registeredTool(server, "create_agent");
|
||||
const parsed = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
provider: "codex/gpt-5.4",
|
||||
title: "Short title",
|
||||
});
|
||||
@@ -510,7 +512,7 @@ describe("create_agent MCP tool", () => {
|
||||
provider: "codex/gpt-5.4",
|
||||
initialPrompt: "Do work",
|
||||
background: true,
|
||||
features: { fast_mode: true },
|
||||
settings: { features: { fast_mode: true } },
|
||||
};
|
||||
|
||||
const parsed = await tool.inputSchema.safeParseAsync(input);
|
||||
@@ -536,7 +538,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const missingProvider = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
title: "Short title",
|
||||
initialPrompt: "test",
|
||||
});
|
||||
@@ -549,7 +551,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const providerWithoutModel = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
title: "Short title",
|
||||
provider: "codex",
|
||||
initialPrompt: "test",
|
||||
@@ -558,7 +560,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const providerWithEmptyModel = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
title: "Short title",
|
||||
provider: "codex/",
|
||||
initialPrompt: "test",
|
||||
@@ -567,7 +569,7 @@ describe("create_agent MCP tool", () => {
|
||||
|
||||
const providerWithEmptyProvider = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
title: "Short title",
|
||||
provider: "/gpt-5.4",
|
||||
initialPrompt: "test",
|
||||
@@ -577,7 +579,7 @@ describe("create_agent MCP tool", () => {
|
||||
await expect(
|
||||
tool.handler({
|
||||
cwd: existingCwd,
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
title: "Short title",
|
||||
provider: "codex/gpt-5.4",
|
||||
model: "gpt-5.4",
|
||||
@@ -722,10 +724,9 @@ describe("create_agent MCP tool", () => {
|
||||
await tool.handler({
|
||||
cwd: existingCwd,
|
||||
title: "Config test",
|
||||
mode: "auto",
|
||||
initialPrompt: "Do work",
|
||||
provider: "codex/gpt-5.4",
|
||||
thinking: "think-hard",
|
||||
settings: { modeId: "auto", thinkingOptionId: "think-hard" },
|
||||
labels: { source: "mcp" },
|
||||
});
|
||||
|
||||
@@ -1269,7 +1270,7 @@ describe("create_agent MCP tool", () => {
|
||||
const parsed = await tool.inputSchema.safeParseAsync({
|
||||
cwd: existingCwd,
|
||||
title: "Custom provider agent",
|
||||
mode: "default",
|
||||
settings: { modeId: "default" },
|
||||
provider: "zai/custom-model",
|
||||
initialPrompt: "Do work",
|
||||
});
|
||||
@@ -1360,7 +1361,7 @@ describe("create_agent MCP tool", () => {
|
||||
provider: "codex/gpt-5.4",
|
||||
initialPrompt: "Do work",
|
||||
background: true,
|
||||
features: { fast_mode: true },
|
||||
settings: { features: { fast_mode: true } },
|
||||
};
|
||||
|
||||
const parsed = await tool.inputSchema.safeParseAsync(input);
|
||||
@@ -1403,7 +1404,7 @@ describe("create_agent MCP tool", () => {
|
||||
await tool.handler({
|
||||
cwd: existingCwd,
|
||||
title: "Injected config test",
|
||||
mode: "auto",
|
||||
settings: { modeId: "auto" },
|
||||
provider: "codex/gpt-5.4",
|
||||
initialPrompt: "Do work",
|
||||
});
|
||||
@@ -1428,7 +1429,7 @@ describe("create_agent MCP tool", () => {
|
||||
cwd: existingCwd,
|
||||
title: "Bad mode",
|
||||
provider: "opencode/gpt-5.4",
|
||||
mode: "bypassPermissions",
|
||||
settings: { modeId: "bypassPermissions" },
|
||||
initialPrompt: "Do work",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
@@ -1567,7 +1568,7 @@ describe("create_agent MCP tool", () => {
|
||||
await tool.handler({
|
||||
title: "Child",
|
||||
provider: "opencode/gpt-5.4",
|
||||
mode: "build",
|
||||
settings: { modeId: "build" },
|
||||
initialPrompt: "Do work",
|
||||
});
|
||||
|
||||
@@ -1579,17 +1580,31 @@ describe("create_agent MCP tool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("set_agent_feature MCP tool", () => {
|
||||
describe("update_agent MCP tool", () => {
|
||||
const logger = createTestLogger();
|
||||
|
||||
it("sets a provider feature on an existing agent", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
it("does not register the replaced feature-specific MCP tool", async () => {
|
||||
const { agentManager, agentStorage } = createTestDeps();
|
||||
const server = await createAgentMcpServer({ agentManager, agentStorage, logger });
|
||||
const tool = registeredTool(server, "set_agent_feature");
|
||||
|
||||
expect(lookupTool(server, "set_agent_feature")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updates runtime settings before metadata", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
spies.agentStorage.get.mockResolvedValue(createStoredRecord({ id: "agent-1" }));
|
||||
const server = await createAgentMcpServer({ agentManager, agentStorage, logger });
|
||||
const tool = registeredTool(server, "update_agent");
|
||||
const input = {
|
||||
agentId: "agent-1",
|
||||
featureId: "fast_mode",
|
||||
value: true,
|
||||
name: "Updated agent",
|
||||
labels: { role: "worker" },
|
||||
settings: {
|
||||
modeId: "full-access",
|
||||
model: "gpt-5.4",
|
||||
thinkingOptionId: "high",
|
||||
features: { fast_mode: true },
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = await tool.inputSchema.safeParseAsync(input);
|
||||
@@ -1597,9 +1612,39 @@ describe("set_agent_feature MCP tool", () => {
|
||||
|
||||
const response = await tool.handler(input);
|
||||
|
||||
expect(spies.agentManager.setAgentMode).toHaveBeenCalledWith("agent-1", "full-access");
|
||||
expect(spies.agentManager.setAgentModel).toHaveBeenCalledWith("agent-1", "gpt-5.4");
|
||||
expect(spies.agentManager.setAgentThinkingOption).toHaveBeenCalledWith("agent-1", "high");
|
||||
expect(spies.agentManager.setAgentFeature).toHaveBeenCalledWith("agent-1", "fast_mode", true);
|
||||
expect(spies.agentStorage.upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "agent-1",
|
||||
title: "Updated agent",
|
||||
}),
|
||||
);
|
||||
expect(spies.agentManager.setLabels).toHaveBeenCalledWith("agent-1", { role: "worker" });
|
||||
expect(response.structuredContent).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it("does not update metadata when runtime settings fail", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
spies.agentManager.setAgentFeature.mockRejectedValue(new Error("unsupported feature"));
|
||||
const server = await createAgentMcpServer({ agentManager, agentStorage, logger });
|
||||
const tool = registeredTool(server, "update_agent");
|
||||
|
||||
await expect(
|
||||
tool.handler({
|
||||
agentId: "agent-1",
|
||||
name: "Should not persist",
|
||||
labels: { role: "worker" },
|
||||
settings: { features: { fast_mode: true } },
|
||||
}),
|
||||
).rejects.toThrow("unsupported feature");
|
||||
|
||||
expect(spies.agentStorage.get).not.toHaveBeenCalled();
|
||||
expect(spies.agentStorage.upsert).not.toHaveBeenCalled();
|
||||
expect(spies.agentManager.setLabels).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("create_schedule MCP tool", () => {
|
||||
@@ -2033,10 +2078,17 @@ describe("provider listing MCP tool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("model listing MCP tool", () => {
|
||||
describe("provider MCP tools", () => {
|
||||
const logger = createTestLogger();
|
||||
|
||||
it("lists provider features for a draft agent configuration", async () => {
|
||||
it("does not register the replaced feature-specific provider discovery MCP tool", async () => {
|
||||
const { agentManager, agentStorage } = createTestDeps();
|
||||
const server = await createAgentMcpServer({ agentManager, agentStorage, logger });
|
||||
|
||||
expect(lookupTool(server, "list_provider_features")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("inspects provider features for a draft agent configuration", async () => {
|
||||
const { agentManager, agentStorage, spies } = createTestDeps();
|
||||
spies.agentManager.listDraftFeatures.mockResolvedValue([
|
||||
{
|
||||
@@ -2046,19 +2098,29 @@ describe("model listing MCP tool", () => {
|
||||
value: false,
|
||||
},
|
||||
]);
|
||||
const providerRegistry = {
|
||||
codex: createProviderDefinition({
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
description: "OpenAI coding agent",
|
||||
modes: [{ id: "full-access", label: "Full Access", description: "Can edit files" }],
|
||||
}),
|
||||
};
|
||||
const server = await createAgentMcpServer({
|
||||
agentManager,
|
||||
agentStorage,
|
||||
providerRegistry,
|
||||
logger,
|
||||
});
|
||||
const tool = registeredTool(server, "list_provider_features");
|
||||
const tool = registeredTool(server, "inspect_provider");
|
||||
const input = {
|
||||
provider: "codex",
|
||||
provider: "codex/gpt-5.4",
|
||||
cwd: "~/repo",
|
||||
modeId: "full-access",
|
||||
model: "gpt-5.4",
|
||||
thinkingOptionId: "high",
|
||||
featureValues: { fast_mode: true },
|
||||
settings: {
|
||||
modeId: "full-access",
|
||||
thinkingOptionId: "high",
|
||||
features: { fast_mode: true },
|
||||
},
|
||||
};
|
||||
|
||||
const parsed = await tool.inputSchema.safeParseAsync(input);
|
||||
@@ -2076,6 +2138,12 @@ describe("model listing MCP tool", () => {
|
||||
});
|
||||
expect(response.structuredContent).toEqual({
|
||||
provider: "codex",
|
||||
label: "Codex",
|
||||
description: "OpenAI coding agent",
|
||||
enabled: true,
|
||||
status: "available",
|
||||
modes: [{ id: "full-access", label: "Full Access", description: "Can edit files" }],
|
||||
selectedModel: "gpt-5.4",
|
||||
features: [
|
||||
{
|
||||
type: "toggle",
|
||||
@@ -2118,6 +2186,38 @@ describe("model listing MCP tool", () => {
|
||||
);
|
||||
expect(fetchModels).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("inspect_provider rejects disabled providers without fetching models", async () => {
|
||||
const { agentManager, agentStorage } = createTestDeps();
|
||||
const fetchModels = vi.fn().mockResolvedValue([
|
||||
{
|
||||
provider: "codex",
|
||||
id: "gpt-5.4",
|
||||
label: "GPT-5.4",
|
||||
},
|
||||
]);
|
||||
const providerRegistry = {
|
||||
codex: createProviderDefinition({
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
enabled: false,
|
||||
fetchModels,
|
||||
}),
|
||||
};
|
||||
|
||||
const server = await createAgentMcpServer({
|
||||
agentManager,
|
||||
agentStorage,
|
||||
providerRegistry,
|
||||
logger,
|
||||
});
|
||||
const tool = registeredTool(server, "inspect_provider");
|
||||
|
||||
await expect(tool.handler({ provider: "codex", cwd: "~/repo" })).rejects.toThrow(
|
||||
"Provider 'codex' is disabled",
|
||||
);
|
||||
expect(fetchModels).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("speak MCP tool", () => {
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
AgentModelSchema,
|
||||
AgentProviderEnum,
|
||||
AgentStatusEnum,
|
||||
ProviderModeSchema,
|
||||
ProviderSummarySchema,
|
||||
parseDurationString,
|
||||
resolveRequiredProviderModel,
|
||||
@@ -587,6 +588,55 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
},
|
||||
{ message: "provider must be provider/model, for example codex/gpt-5.4" },
|
||||
);
|
||||
const ProviderOrProviderModelInputSchema = AgentProviderEnum.trim()
|
||||
.min(1, "provider is required")
|
||||
.refine(
|
||||
(value) => {
|
||||
if (!value.includes("/")) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
resolveRequiredProviderModel(value);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: "provider must be provider or provider/model, for example codex/gpt-5.4" },
|
||||
);
|
||||
const CreateAgentSettingsInputSchema = z
|
||||
.object({
|
||||
modeId: z.string().optional().describe("Session mode to configure before the first run."),
|
||||
thinkingOptionId: z.string().optional().describe("Thinking option ID."),
|
||||
features: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe("Provider-specific feature values, for example { fast_mode: true } for Codex."),
|
||||
})
|
||||
.strict();
|
||||
const UpdateAgentSettingsInputSchema = z
|
||||
.object({
|
||||
modeId: z.string().optional().describe("Session mode ID."),
|
||||
model: z.string().nullable().optional().describe("Model ID. Pass null to clear."),
|
||||
thinkingOptionId: z
|
||||
.string()
|
||||
.nullable()
|
||||
.optional()
|
||||
.describe("Thinking option ID. Pass null to clear."),
|
||||
features: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe("Provider-specific feature values, for example { fast_mode: true } for Codex."),
|
||||
})
|
||||
.strict();
|
||||
const InspectProviderSettingsInputSchema = z
|
||||
.object({
|
||||
modeId: z.string().optional().describe("Draft session mode ID."),
|
||||
model: z.string().optional().describe("Draft model ID."),
|
||||
thinkingOptionId: z.string().optional().describe("Draft thinking option ID."),
|
||||
features: z.record(z.unknown()).optional().describe("Draft provider feature values."),
|
||||
})
|
||||
.strict();
|
||||
const agentToAgentInputSchema = {
|
||||
cwd: z
|
||||
.string()
|
||||
@@ -601,23 +651,15 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
provider: ProviderModelInputSchema.describe(
|
||||
"Required provider/model pair, for example codex/gpt-5.4.",
|
||||
),
|
||||
thinking: z.string().optional().describe("Thinking option ID"),
|
||||
features: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe("Provider-specific feature values, for example { fast_mode: true } for Codex."),
|
||||
labels: z.record(z.string(), z.string()).optional().describe("Labels to set on the agent"),
|
||||
settings: CreateAgentSettingsInputSchema.optional().describe(
|
||||
"Initial runtime settings for the new agent.",
|
||||
),
|
||||
initialPrompt: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "initialPrompt is required")
|
||||
.describe("Required first task to run immediately after creation."),
|
||||
mode: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional session mode for the new agent. Required when the new agent uses a different provider than the caller agent.",
|
||||
),
|
||||
background: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -647,21 +689,15 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
provider: ProviderModelInputSchema.describe(
|
||||
"Required provider/model pair, for example codex/gpt-5.4.",
|
||||
),
|
||||
thinking: z.string().optional().describe("Thinking option ID"),
|
||||
features: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe("Provider-specific feature values, for example { fast_mode: true } for Codex."),
|
||||
labels: z.record(z.string(), z.string()).optional().describe("Labels to set on the agent"),
|
||||
settings: CreateAgentSettingsInputSchema.optional().describe(
|
||||
"Initial runtime settings for the new agent.",
|
||||
),
|
||||
initialPrompt: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "initialPrompt is required")
|
||||
.describe("Required first task to run immediately after creation."),
|
||||
mode: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional session mode to configure before the first run."),
|
||||
worktreeName: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -700,13 +736,17 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
const createAgentInputSchema = callerAgentId ? agentToAgentInputSchema : topLevelInputSchema;
|
||||
const agentToAgentCreateAgentArgsSchema = z.object(agentToAgentInputSchema).strict();
|
||||
const topLevelCreateAgentArgsSchema = z.object(topLevelInputSchema).strict();
|
||||
const listProviderFeaturesInputSchema = {
|
||||
provider: AgentProviderEnum,
|
||||
cwd: z.string().describe("Working directory used to resolve provider feature availability."),
|
||||
modeId: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
thinkingOptionId: z.string().optional(),
|
||||
featureValues: z.record(z.unknown()).optional(),
|
||||
const inspectProviderInputSchema = {
|
||||
provider: ProviderOrProviderModelInputSchema.describe(
|
||||
"Provider ID, optionally with a model ID (for example codex or codex/gpt-5.4).",
|
||||
),
|
||||
cwd: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Working directory used to resolve provider feature availability."),
|
||||
settings: InspectProviderSettingsInputSchema.optional().describe(
|
||||
"Draft provider settings used to compute available features.",
|
||||
),
|
||||
};
|
||||
|
||||
if (options.voiceOnly || options.enableVoiceTools || callerContext?.enableVoiceTools) {
|
||||
@@ -758,7 +798,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
background: boolean;
|
||||
normalizedTitle: string | null;
|
||||
model: string | undefined;
|
||||
thinking: string | undefined;
|
||||
thinkingOptionId: string | undefined;
|
||||
features: Record<string, unknown> | undefined;
|
||||
labels: Record<string, string> | undefined;
|
||||
notifyOnFinish: boolean;
|
||||
@@ -805,6 +845,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
throw new Error(`Parent agent ${parentAgentId} not found`);
|
||||
}
|
||||
const provider = resolvedProviderModel.provider;
|
||||
const settings = callerArgs.settings;
|
||||
const resolvedCwd = resolveChildAgentCwd({
|
||||
parentCwd: parentAgent.cwd,
|
||||
requestedCwd: callerArgs.cwd,
|
||||
@@ -812,7 +853,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
allowCustomCwd: callerContext?.allowCustomCwd ?? true,
|
||||
});
|
||||
const resolvedMode = resolveAndValidateCreateAgentMode({
|
||||
requestedMode: callerArgs.mode,
|
||||
requestedMode: settings?.modeId,
|
||||
targetProvider: provider,
|
||||
parent: {
|
||||
provider: parentAgent.provider,
|
||||
@@ -828,8 +869,8 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
background: callerArgs.background ?? false,
|
||||
normalizedTitle: callerArgs.title.trim(),
|
||||
model: resolvedProviderModel.model,
|
||||
thinking: callerArgs.thinking,
|
||||
features: callerArgs.features,
|
||||
thinkingOptionId: settings?.thinkingOptionId,
|
||||
features: settings?.features,
|
||||
labels: callerArgs.labels,
|
||||
notifyOnFinish: callerArgs.notifyOnFinish ?? false,
|
||||
resolvedCwd,
|
||||
@@ -843,9 +884,10 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
): Promise<ResolvedCreateAgentArgs> => {
|
||||
const topLevelArgs = topLevelCreateAgentArgsSchema.parse(args);
|
||||
const resolvedProviderModel = resolveRequiredProviderModel(topLevelArgs.provider);
|
||||
const { cwd, mode, worktreeName, baseBranch, refName, action, githubPrNumber } = topLevelArgs;
|
||||
const { cwd, settings, worktreeName, baseBranch, refName, action, githubPrNumber } =
|
||||
topLevelArgs;
|
||||
const resolvedMode = resolveAndValidateCreateAgentMode({
|
||||
requestedMode: mode,
|
||||
requestedMode: settings?.modeId,
|
||||
targetProvider: resolvedProviderModel.provider,
|
||||
parent: null,
|
||||
availableModes: getAvailableModeIds(resolvedProviderModel.provider),
|
||||
@@ -902,8 +944,8 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
background: topLevelArgs.background ?? false,
|
||||
normalizedTitle: topLevelArgs.title.trim(),
|
||||
model: resolvedProviderModel.model,
|
||||
thinking: topLevelArgs.thinking,
|
||||
features: topLevelArgs.features,
|
||||
thinkingOptionId: settings?.thinkingOptionId,
|
||||
features: settings?.features,
|
||||
labels: topLevelArgs.labels,
|
||||
notifyOnFinish: topLevelArgs.notifyOnFinish ?? false,
|
||||
resolvedCwd,
|
||||
@@ -946,7 +988,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
background,
|
||||
normalizedTitle,
|
||||
model,
|
||||
thinking,
|
||||
thinkingOptionId,
|
||||
features,
|
||||
labels,
|
||||
notifyOnFinish,
|
||||
@@ -968,7 +1010,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
modeId: resolvedMode,
|
||||
title: normalizedTitle ?? undefined,
|
||||
model,
|
||||
thinkingOptionId: thinking,
|
||||
thinkingOptionId,
|
||||
featureValues: features,
|
||||
},
|
||||
undefined,
|
||||
@@ -1057,29 +1099,6 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"set_agent_feature",
|
||||
{
|
||||
title: "Set agent feature",
|
||||
description: "Set a provider-specific feature on an existing agent, such as Codex fast_mode.",
|
||||
inputSchema: {
|
||||
agentId: z.string(),
|
||||
featureId: z.string().trim().min(1),
|
||||
value: z.unknown(),
|
||||
},
|
||||
outputSchema: {
|
||||
success: z.boolean(),
|
||||
},
|
||||
},
|
||||
async ({ agentId, featureId, value }) => {
|
||||
await agentManager.setAgentFeature(agentId, featureId, value);
|
||||
return {
|
||||
content: [],
|
||||
structuredContent: ensureValidJson({ success: true }),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"wait_for_agent",
|
||||
{
|
||||
@@ -1440,17 +1459,35 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
"update_agent",
|
||||
{
|
||||
title: "Update agent",
|
||||
description: "Update an agent name and/or labels.",
|
||||
description: "Update an agent name, labels, and/or runtime settings.",
|
||||
inputSchema: {
|
||||
agentId: z.string(),
|
||||
name: z.string().optional(),
|
||||
labels: z.record(z.string(), z.string()).optional().describe("Labels to set on the agent"),
|
||||
settings: UpdateAgentSettingsInputSchema.optional().describe(
|
||||
"Runtime settings to apply to the agent.",
|
||||
),
|
||||
},
|
||||
outputSchema: {
|
||||
success: z.boolean(),
|
||||
},
|
||||
},
|
||||
async ({ agentId, name, labels }) => {
|
||||
async ({ agentId, name, labels, settings }) => {
|
||||
if (settings?.modeId !== undefined) {
|
||||
await agentManager.setAgentMode(agentId, settings.modeId);
|
||||
}
|
||||
if (settings?.model !== undefined) {
|
||||
await agentManager.setAgentModel(agentId, settings.model);
|
||||
}
|
||||
if (settings?.thinkingOptionId !== undefined) {
|
||||
await agentManager.setAgentThinkingOption(agentId, settings.thinkingOptionId);
|
||||
}
|
||||
if (settings?.features) {
|
||||
for (const [featureId, value] of Object.entries(settings.features)) {
|
||||
await agentManager.setAgentFeature(agentId, featureId, value);
|
||||
}
|
||||
}
|
||||
|
||||
const trimmedName = name?.trim();
|
||||
if (trimmedName) {
|
||||
const record = await agentStorage.get(agentId);
|
||||
@@ -2022,30 +2059,63 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"list_provider_features",
|
||||
"inspect_provider",
|
||||
{
|
||||
title: "List provider features",
|
||||
title: "Inspect provider",
|
||||
description:
|
||||
"List provider-specific features available for a draft agent configuration, such as Codex fast_mode.",
|
||||
inputSchema: listProviderFeaturesInputSchema,
|
||||
"Inspect compact provider capabilities for orchestration, including modes and draft feature settings. Use list_models for the full model list.",
|
||||
inputSchema: inspectProviderInputSchema,
|
||||
outputSchema: {
|
||||
provider: AgentProviderEnum,
|
||||
label: z.string().nullable().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
enabled: z.boolean(),
|
||||
status: z.string(),
|
||||
modes: z.array(ProviderModeSchema).nullish(),
|
||||
selectedModel: z.string().nullable(),
|
||||
features: z.array(AgentFeatureSchema),
|
||||
},
|
||||
},
|
||||
async ({ provider, cwd, modeId, model, thinkingOptionId, featureValues }) => {
|
||||
const features = await agentManager.listDraftFeatures({
|
||||
async ({ provider, cwd, settings }) => {
|
||||
const resolvedProviderModel = resolveScheduleProviderAndModel({
|
||||
provider,
|
||||
cwd: expandUserPath(cwd),
|
||||
...(modeId ? { modeId } : {}),
|
||||
...(model ? { model } : {}),
|
||||
...(thinkingOptionId ? { thinkingOptionId } : {}),
|
||||
...(featureValues ? { featureValues } : {}),
|
||||
defaultProvider: provider,
|
||||
});
|
||||
const providerId = resolvedProviderModel.provider;
|
||||
if (!providerRegistry) {
|
||||
throw new Error("Provider registry is not configured");
|
||||
}
|
||||
const definition = providerRegistry[providerId];
|
||||
if (!definition) {
|
||||
throw new Error(`Provider ${providerId} is not configured`);
|
||||
}
|
||||
const summary = await resolveProviderSummary(definition, childLogger);
|
||||
if (!definition.enabled) {
|
||||
throw new Error(`Provider '${providerId}' is disabled`);
|
||||
}
|
||||
if (summary.status !== "available") {
|
||||
throw new Error(summary.error ?? `Provider '${providerId}' is unavailable`);
|
||||
}
|
||||
const resolvedCwd = resolveScopedCwd(cwd, { required: true });
|
||||
const selectedModel = settings?.model ?? resolvedProviderModel.model;
|
||||
const features = await agentManager.listDraftFeatures({
|
||||
provider: providerId,
|
||||
cwd: resolvedCwd,
|
||||
...(settings?.modeId ? { modeId: settings.modeId } : {}),
|
||||
...(selectedModel ? { model: selectedModel } : {}),
|
||||
...(settings?.thinkingOptionId ? { thinkingOptionId: settings.thinkingOptionId } : {}),
|
||||
...(settings?.features ? { featureValues: settings.features } : {}),
|
||||
});
|
||||
return {
|
||||
content: [],
|
||||
structuredContent: ensureValidJson({
|
||||
provider,
|
||||
provider: providerId,
|
||||
label: summary.label,
|
||||
description: summary.description,
|
||||
enabled: summary.enabled,
|
||||
status: summary.status,
|
||||
modes: summary.modes,
|
||||
selectedModel: selectedModel ?? null,
|
||||
features,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -15,20 +15,19 @@ The MCP server itself is controlled by `daemon.mcp.enabled`. Existing agents may
|
||||
|
||||
### Agents
|
||||
|
||||
| Tool | Function |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `create_agent` | Create an agent tied to a working directory, optionally with an initial prompt, provider features, or a new git worktree. |
|
||||
| `wait_for_agent` | Block until an agent requests permission or finishes its current run. |
|
||||
| `send_agent_prompt` | Send a task to a running agent. |
|
||||
| `get_agent_status` | Return the latest snapshot for an agent. |
|
||||
| `list_agents` | List recent agents as compact metadata. |
|
||||
| `cancel_agent` | Abort an agent's current run but keep the agent alive. |
|
||||
| `archive_agent` | Soft-delete an agent and remove it from the active list. |
|
||||
| `kill_agent` | Terminate an agent session permanently. |
|
||||
| `update_agent` | Update an agent name or labels. |
|
||||
| `get_agent_activity` | Return recent agent timeline entries as a curated summary. |
|
||||
| `set_agent_mode` | Switch an agent's session mode. |
|
||||
| `set_agent_feature` | Set a provider-specific feature on an existing agent, for example Codex `fast_mode`. |
|
||||
| Tool | Function |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `create_agent` | Create an agent tied to a working directory, optionally with initial settings or a new git worktree. |
|
||||
| `wait_for_agent` | Block until an agent requests permission or finishes its current run. |
|
||||
| `send_agent_prompt` | Send a task to a running agent. |
|
||||
| `get_agent_status` | Return the latest snapshot for an agent. |
|
||||
| `list_agents` | List recent agents as compact metadata. |
|
||||
| `cancel_agent` | Abort an agent's current run but keep the agent alive. |
|
||||
| `archive_agent` | Soft-delete an agent and remove it from the active list. |
|
||||
| `kill_agent` | Terminate an agent session permanently. |
|
||||
| `update_agent` | Update an agent name, labels, or runtime settings such as mode/model/thinking/features. |
|
||||
| `get_agent_activity` | Return recent agent timeline entries as a curated summary. |
|
||||
| `set_agent_mode` | Switch an agent's session mode. |
|
||||
|
||||
### Terminals
|
||||
|
||||
@@ -53,11 +52,11 @@ The MCP server itself is controlled by `daemon.mcp.enabled`. Existing agents may
|
||||
|
||||
### Providers
|
||||
|
||||
| Tool | Function |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| `list_providers` | List configured agent providers, availability, and modes. |
|
||||
| `list_models` | List models for an agent provider. |
|
||||
| `list_provider_features` | List provider-specific features for a draft agent configuration, such as Codex `fast_mode`. |
|
||||
| Tool | Function |
|
||||
| ------------------ | ----------------------------------------------------------------- |
|
||||
| `list_providers` | List configured agent providers, availability, and modes. |
|
||||
| `list_models` | List models for an agent provider. |
|
||||
| `inspect_provider` | Inspect compact provider capabilities and draft feature settings. |
|
||||
|
||||
### Worktrees
|
||||
|
||||
|
||||
@@ -20,25 +20,29 @@ Returns `{ branchName, worktreePath }`. Pass `cwd` to target a specific repo.
|
||||
|
||||
## Agents
|
||||
|
||||
**`create_agent`** — required: `title`, `provider` (`claude/opus`, `codex/gpt-5.4`, …), `initialPrompt`. Common: `cwd` (often a `worktreePath`), `background` (default `false` — blocks until completion or permission), `notifyOnFinish`, `features`. Returns `{ agentId, … }`.
|
||||
**`create_agent`** — required: `title`, `provider` (`claude/opus`, `codex/gpt-5.4`, …), `initialPrompt`. Common: `cwd` (often a `worktreePath`), `background` (default `false` — blocks until completion or permission), `notifyOnFinish`, `settings`. Returns `{ agentId, … }`.
|
||||
|
||||
Provider features are provider-specific. For Codex fast mode, pass `features: { "fast_mode": true }` when creating the agent.
|
||||
Initial runtime settings live under `settings`: `modeId`, `thinkingOptionId`, and provider-specific `features`. For Codex fast mode, pass `settings: { features: { "fast_mode": true } }` when creating the agent.
|
||||
|
||||
Compose: call `create_worktree` first, then `create_agent` with `cwd` set to the returned `worktreePath`.
|
||||
|
||||
**`send_agent_prompt`** — `{ agentId, prompt }`. Blocks by default; pass `background: true` to fire-and-forget.
|
||||
|
||||
**`set_agent_feature`** — `{ agentId, featureId, value }`. Use for provider-specific toggles on an existing agent, for example `{ agentId, featureId: "fast_mode", value: true }` for Codex.
|
||||
**`update_agent`** — `{ agentId, name?, labels?, settings? }`. Use `settings` for runtime changes on an existing agent: `modeId`, `model`, `thinkingOptionId`, and provider-specific `features`. For Codex fast mode, pass `settings: { features: { "fast_mode": true } }`.
|
||||
|
||||
**`list_agents`** — filter by `cwd`, `statuses`, `sinceHours`, `includeArchived`.
|
||||
|
||||
**`archive_agent`** — `{ agentId }`. Interrupts if running, removes from active list.
|
||||
|
||||
## Provider features
|
||||
## Provider discovery
|
||||
|
||||
**`list_provider_features`** — query provider-specific features before setting them. Required: `provider`, `cwd`. Optional: `model`, `modeId`, `thinkingOptionId`, `featureValues`.
|
||||
**`list_providers`** — compact provider availability and modes.
|
||||
|
||||
Only set feature IDs returned by `list_provider_features`. For Codex fast mode, look for `fast_mode` and pass `features: { "fast_mode": true }` to `create_agent`.
|
||||
**`list_models`** — full model list for one provider. Use only when you need model IDs or thinking options; the list can be large.
|
||||
|
||||
**`inspect_provider`** — compact provider capability and feature inspection. Required: `provider`; pass `cwd` when you are not in an agent-scoped session. Optional: `settings` with draft `model`, `modeId`, `thinkingOptionId`, and `features`.
|
||||
|
||||
Only set feature IDs returned by `inspect_provider`. For Codex fast mode, look for `fast_mode` and pass `settings: { features: { "fast_mode": true } }` to `create_agent` or `update_agent`.
|
||||
|
||||
## Heartbeats
|
||||
|
||||
|
||||
Reference in New Issue
Block a user