refactor(agent): add wait_for_agent tool and simplify prompt sending API
This commit is contained in:
@@ -96,6 +96,73 @@ describe("AgentManager", () => {
|
||||
}
|
||||
}, 120000);
|
||||
|
||||
it("should wait for permission requests", async () => {
|
||||
const manager = new AgentManager();
|
||||
let capturedPermission: Awaited<
|
||||
ReturnType<(typeof manager)["waitForPermissionRequest"]>
|
||||
> | null = null;
|
||||
|
||||
const agentId = await manager.createAgent({
|
||||
cwd: tmpDir,
|
||||
type: "claude",
|
||||
initialMode: "plan",
|
||||
});
|
||||
createdAgents.push({ manager, agentId });
|
||||
|
||||
const waitPromise = manager
|
||||
.waitForPermissionRequest(agentId)
|
||||
.then((permission) => {
|
||||
capturedPermission = permission;
|
||||
return permission;
|
||||
});
|
||||
|
||||
await manager.sendPrompt(
|
||||
agentId,
|
||||
"Create a file called wait-for-permission.txt with the content 'hello world'"
|
||||
);
|
||||
|
||||
const permission = await waitPromise;
|
||||
expect(permission).not.toBeNull();
|
||||
expect(permission!.agentId).toBe(agentId);
|
||||
expect(permission!.requestId).toBeDefined();
|
||||
expect(permission!.options.length).toBeGreaterThan(0);
|
||||
|
||||
const acceptOption = permission!.options.find(
|
||||
(option) => option.kind === "allow_once" || option.kind === "allow_always"
|
||||
);
|
||||
expect(acceptOption).toBeDefined();
|
||||
|
||||
manager.respondToPermission(
|
||||
permission!.agentId,
|
||||
permission!.requestId,
|
||||
acceptOption!.optionId
|
||||
);
|
||||
|
||||
// Ensure the captured permission matches the resolved value
|
||||
expect(capturedPermission).toEqual(permission);
|
||||
}, 120000);
|
||||
|
||||
it("should support aborting waitForPermissionRequest", async () => {
|
||||
const manager = new AgentManager();
|
||||
|
||||
const agentId = await manager.createAgent({
|
||||
cwd: tmpDir,
|
||||
type: "claude",
|
||||
initialMode: "plan",
|
||||
});
|
||||
createdAgents.push({ manager, agentId });
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const waitPromise = manager.waitForPermissionRequest(agentId, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
|
||||
await expect(waitPromise).rejects.toMatchObject({ name: "AbortError" });
|
||||
}, 30000);
|
||||
|
||||
it("should not fail when sending '.' after plan permission request", async () => {
|
||||
const manager = new AgentManager();
|
||||
let permissionRequest: RequestPermissionRequest | null = null;
|
||||
|
||||
@@ -379,14 +379,13 @@ export class AgentManager {
|
||||
* Send a prompt to an agent
|
||||
* @param agentId - Agent ID
|
||||
* @param prompt - The prompt text or ContentBlock array
|
||||
* @param options - Optional settings: maxWait (ms), sessionMode to set before sending, messageId for deduplication
|
||||
* @returns Object with didComplete boolean indicating if agent finished within maxWait time
|
||||
* @param options - Optional settings: sessionMode to set before sending, messageId for deduplication
|
||||
*/
|
||||
async sendPrompt(
|
||||
agentId: string,
|
||||
prompt: string | ContentBlock[],
|
||||
options?: { maxWait?: number; sessionMode?: string; messageId?: string }
|
||||
): Promise<{ didComplete: boolean; stopReason?: string }> {
|
||||
options?: { sessionMode?: string; messageId?: string }
|
||||
): Promise<void> {
|
||||
const agent = this.agents.get(agentId);
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${agentId} not found`);
|
||||
@@ -577,38 +576,9 @@ export class AgentManager {
|
||||
);
|
||||
});
|
||||
|
||||
const maxWait = options?.maxWait;
|
||||
|
||||
// If no maxWait specified, return immediately
|
||||
if (maxWait === undefined) {
|
||||
console.log(
|
||||
`[Agent ${agentId}] Prompt sent (non-blocking, use get_agent_status to check completion)`
|
||||
);
|
||||
return { didComplete: false };
|
||||
}
|
||||
|
||||
// If maxWait specified, race between completion and timeout
|
||||
try {
|
||||
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("timeout")), maxWait)
|
||||
);
|
||||
|
||||
const response = await Promise.race([promptPromise, timeoutPromise]);
|
||||
|
||||
console.log(
|
||||
`[Agent ${agentId}] Prompt completed within ${maxWait}ms with stopReason: ${response.stopReason}`
|
||||
);
|
||||
return { didComplete: true, stopReason: response.stopReason };
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "timeout") {
|
||||
console.log(
|
||||
`[Agent ${agentId}] Prompt did not complete within ${maxWait}ms (still processing)`
|
||||
);
|
||||
return { didComplete: false };
|
||||
}
|
||||
// Real error - rethrow
|
||||
throw error;
|
||||
}
|
||||
console.log(
|
||||
`[Agent ${agentId}] Prompt sent (non-blocking, use get_agent_status to check completion)`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1701,6 +1671,158 @@ export class AgentManager {
|
||||
return allPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next permission request from an agent, or until agent finishes
|
||||
* Resolves immediately if a permission is already pending
|
||||
* Returns permission data if agent requests permission, or null if agent finishes without requesting
|
||||
*/
|
||||
async waitForPermissionRequest(
|
||||
agentId: string,
|
||||
options?: { signal?: AbortSignal }
|
||||
): Promise<{
|
||||
agentId: string;
|
||||
requestId: string;
|
||||
sessionId: string;
|
||||
toolCall: any;
|
||||
options: Array<{
|
||||
kind: string;
|
||||
name: string;
|
||||
optionId: string;
|
||||
}>;
|
||||
} | null> {
|
||||
const agent = this.agents.get(agentId);
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${agentId} not found`);
|
||||
}
|
||||
|
||||
const abortSignal = options?.signal ?? null;
|
||||
|
||||
const createAbortError = () => {
|
||||
const error = new Error(
|
||||
`Wait for permission aborted for agent ${agentId}`
|
||||
);
|
||||
error.name = "AbortError";
|
||||
return error;
|
||||
};
|
||||
|
||||
const formatPending = (
|
||||
requestId: string,
|
||||
permission: PendingPermission
|
||||
) => ({
|
||||
agentId,
|
||||
requestId,
|
||||
sessionId: permission.sessionId,
|
||||
toolCall: permission.params.toolCall,
|
||||
options: permission.params.options,
|
||||
});
|
||||
|
||||
// Return immediately if a permission is already pending
|
||||
const existingPermission = agent.pendingPermissions.entries().next();
|
||||
if (!existingPermission.done) {
|
||||
const [requestId, permission] = existingPermission.value;
|
||||
return formatPending(requestId, permission);
|
||||
}
|
||||
|
||||
if (abortSignal?.aborted) {
|
||||
throw createAbortError();
|
||||
}
|
||||
|
||||
const initialStatus = getAgentStatusFromState(agent.state);
|
||||
|
||||
// If agent is not processing or initializing, return null (no permission requested)
|
||||
if (initialStatus !== "processing" && initialStatus !== "initializing") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
|
||||
const cleanup = () => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = null;
|
||||
}
|
||||
abortSignal?.removeEventListener("abort", onAbort);
|
||||
};
|
||||
|
||||
const resolvePending = (value: ReturnType<typeof formatPending>) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
cleanup();
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
const rejectWithError = (error: Error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(error);
|
||||
};
|
||||
|
||||
const onAbort = () => {
|
||||
rejectWithError(createAbortError());
|
||||
};
|
||||
|
||||
if (abortSignal) {
|
||||
abortSignal.addEventListener("abort", onAbort, { once: true });
|
||||
}
|
||||
|
||||
try {
|
||||
unsubscribe = this.subscribeToUpdates(agentId, (update) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notification = update.notification;
|
||||
|
||||
if (notification.type === "permission") {
|
||||
const { requestId } = notification;
|
||||
const pendingPermission =
|
||||
agent.pendingPermissions.get(requestId) ?? null;
|
||||
|
||||
if (!pendingPermission) {
|
||||
rejectWithError(
|
||||
new Error(
|
||||
`Permission ${requestId} no longer pending for agent ${agentId}`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
resolvePending(formatPending(requestId, pendingPermission));
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification.type === "status") {
|
||||
const status = notification.status;
|
||||
|
||||
// If agent transitions out of processing without requesting permission, return null
|
||||
if (status !== "processing") {
|
||||
settled = true;
|
||||
cleanup();
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
rejectWithError(
|
||||
error instanceof Error ? error : new Error(String(error))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (abortSignal?.aborted) {
|
||||
onAbort();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully shutdown all agents
|
||||
* Waits for processing agents to finish, then terminates all processes
|
||||
|
||||
@@ -20,6 +20,11 @@ export interface AgentTypeDefinition {
|
||||
}
|
||||
|
||||
const CLAUDE_MODES: AgentModeDefinition[] = [
|
||||
{
|
||||
id: "default",
|
||||
name: "Default",
|
||||
description: "Default Claude Code mode with standard permissions",
|
||||
},
|
||||
{
|
||||
id: "plan",
|
||||
name: "Plan",
|
||||
|
||||
@@ -5,6 +5,10 @@ import { resolve } from "path";
|
||||
import { AgentManager } from "./agent-manager.js";
|
||||
import type { AgentNotification } from "./types.js";
|
||||
import { curateAgentActivity } from "./activity-curator.js";
|
||||
import {
|
||||
listAgentTypeDefinitions,
|
||||
type AgentType,
|
||||
} from "./agent-types.js";
|
||||
|
||||
export interface AgentMcpServerOptions {
|
||||
agentManager: AgentManager;
|
||||
@@ -50,6 +54,17 @@ export async function createAgentMcpServer(
|
||||
): Promise<McpServer> {
|
||||
const { agentManager } = options;
|
||||
|
||||
const agentTypeDefinitions = listAgentTypeDefinitions();
|
||||
if (agentTypeDefinitions.length === 0) {
|
||||
throw new Error("[Agent MCP] No agent types configured");
|
||||
}
|
||||
|
||||
const agentTypeIds = agentTypeDefinitions.map(
|
||||
(definition) => definition.id
|
||||
) as [AgentType, ...AgentType[]];
|
||||
|
||||
const AgentTypeEnum = z.enum(agentTypeIds);
|
||||
|
||||
const server = new McpServer({
|
||||
name: "agent-mcp",
|
||||
version: "1.0.0",
|
||||
@@ -61,25 +76,25 @@ export async function createAgentMcpServer(
|
||||
{
|
||||
title: "Create Coding Agent",
|
||||
description:
|
||||
"Creates a new Claude Code agent via ACP. The agent runs as a separate process and can execute coding tasks autonomously in a specified directory. Returns immediately with agent ID and status. If an initial prompt is provided, the agent will start working on it automatically. Optionally create a git worktree for isolated development.",
|
||||
"Creates a new Claude Code or Codex agent via ACP. The agent runs as a separate process and can execute coding tasks autonomously in a specified directory. Returns immediately with agent ID, type, and status. If an initial prompt is provided, the agent will start working on it automatically. Optionally create a git worktree for isolated development.",
|
||||
inputSchema: {
|
||||
cwd: z
|
||||
.string()
|
||||
.describe(
|
||||
"REQUIRED: Working directory for the agent. Can be absolute path, tilde-prefixed path, or relative path (e.g., '~/dev/project', '/path/to/repo', './subdir')."
|
||||
),
|
||||
agentType: AgentTypeEnum.optional().describe(
|
||||
"Optional: Agent implementation to spawn. 'claude' provides full Claude Code capabilities with session persistence. 'codex' uses Zed Codex ACP with fast startup and explicit permission modes. Defaults to 'claude'."
|
||||
),
|
||||
initialPrompt: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional initial task or prompt for the agent to start working on immediately after creation. If provided, agent will begin processing this task right away."
|
||||
),
|
||||
initialMode: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Initial session mode for the agent (e.g., 'ask', 'code', 'architect'). If not specified, the agent will use its default mode. The available modes depend on the specific agent implementation."
|
||||
),
|
||||
initialMode: z.string().optional().describe(
|
||||
"Optional: Initial session mode for the agent. Mode is validated at runtime based on agent type. Claude Code supports 'default', 'plan', and 'bypassPermissions'. Codex supports 'read-only', 'auto', and 'full-access'. Defaults to the agent type's default mode."
|
||||
),
|
||||
worktreeName: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -89,6 +104,7 @@ export async function createAgentMcpServer(
|
||||
},
|
||||
outputSchema: {
|
||||
agentId: z.string().describe("Unique identifier for the created agent"),
|
||||
type: AgentTypeEnum.describe("Agent implementation that was created"),
|
||||
status: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -99,11 +115,11 @@ export async function createAgentMcpServer(
|
||||
availableModes: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
})).nullable().describe("Available session modes for this agent"),
|
||||
},
|
||||
},
|
||||
async ({ cwd, initialPrompt, initialMode, worktreeName }) => {
|
||||
async ({ cwd, agentType, initialPrompt, initialMode, worktreeName }) => {
|
||||
// Expand and resolve the working directory
|
||||
let resolvedCwd = expandPath(cwd);
|
||||
|
||||
@@ -119,9 +135,11 @@ export async function createAgentMcpServer(
|
||||
resolvedCwd = worktreeConfig.worktreePath;
|
||||
}
|
||||
|
||||
const resolvedType: AgentType = agentType ?? "claude";
|
||||
|
||||
const agentId = await agentManager.createAgent({
|
||||
cwd: resolvedCwd,
|
||||
type: "claude",
|
||||
type: resolvedType,
|
||||
initialPrompt,
|
||||
initialMode,
|
||||
});
|
||||
@@ -132,6 +150,7 @@ export async function createAgentMcpServer(
|
||||
|
||||
const result = {
|
||||
agentId,
|
||||
type: resolvedType,
|
||||
status,
|
||||
cwd: resolvedCwd,
|
||||
currentModeId: currentModeId ?? null,
|
||||
@@ -145,13 +164,86 @@ export async function createAgentMcpServer(
|
||||
}
|
||||
);
|
||||
|
||||
// Tool: wait_for_agent
|
||||
server.registerTool(
|
||||
"wait_for_agent",
|
||||
{
|
||||
title: "Wait For Agent",
|
||||
description:
|
||||
"Wait until the agent requests permission or finishes its task. Returns the agent's activity and permission request (if any). Use this to monitor agent progress without polling.",
|
||||
inputSchema: {
|
||||
agentId: z
|
||||
.string()
|
||||
.describe("Agent ID to wait on (typically the result of create_coding_agent)"),
|
||||
},
|
||||
outputSchema: {
|
||||
agentId: z.string(),
|
||||
status: z
|
||||
.string()
|
||||
.describe("Agent status after waiting completed"),
|
||||
permission: z.object({
|
||||
agentId: z.string(),
|
||||
requestId: z.string(),
|
||||
sessionId: z.string(),
|
||||
toolCall: z.any(),
|
||||
options: z.array(
|
||||
z.object({
|
||||
kind: z.string(),
|
||||
name: z.string(),
|
||||
optionId: z.string(),
|
||||
})
|
||||
),
|
||||
}).nullable().describe("Permission request if agent requested one, null if agent finished without requesting"),
|
||||
activity: z.object({
|
||||
format: z
|
||||
.literal("curated")
|
||||
.describe("Activity format: curated text for easy reading"),
|
||||
updateCount: z
|
||||
.number()
|
||||
.describe("Total number of updates recorded for the agent"),
|
||||
currentModeId: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe("Current session mode"),
|
||||
content: z
|
||||
.string()
|
||||
.describe("Curated activity transcript including the latest updates"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async ({ agentId }, { signal }) => {
|
||||
const permission = await agentManager.waitForPermissionRequest(agentId, {
|
||||
signal,
|
||||
});
|
||||
const updates = agentManager.getAgentUpdates(agentId);
|
||||
const curatedText = curateAgentActivity(updates);
|
||||
const currentModeId = agentManager.getCurrentMode(agentId);
|
||||
const status = agentManager.getAgentStatus(agentId);
|
||||
|
||||
return {
|
||||
content: [],
|
||||
structuredContent: {
|
||||
agentId,
|
||||
status,
|
||||
permission,
|
||||
activity: {
|
||||
format: "curated" as const,
|
||||
updateCount: updates.length,
|
||||
currentModeId,
|
||||
content: curatedText,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Tool: send_agent_prompt
|
||||
server.registerTool(
|
||||
"send_agent_prompt",
|
||||
{
|
||||
title: "Send Agent Prompt",
|
||||
description:
|
||||
"Sends a task or prompt to an existing agent. By default, returns immediately without waiting (non-blocking). The agent will process the prompt in the background. Use get_agent_status or get_agent_activity to check progress. Optionally specify maxWait to wait for completion, or sessionMode to switch modes before sending.",
|
||||
"Sends a task or prompt to an existing agent. Returns immediately without waiting (non-blocking). The agent will process the prompt in the background. Use wait_for_agent, get_agent_status, or get_agent_activity to monitor progress. Optionally switch session modes before sending.",
|
||||
inputSchema: {
|
||||
agentId: z.string().describe("Agent ID returned from create_coding_agent"),
|
||||
prompt: z
|
||||
@@ -159,35 +251,25 @@ export async function createAgentMcpServer(
|
||||
.describe(
|
||||
"The task, instruction, or feedback to send to the agent. Be specific about what you want the agent to accomplish."
|
||||
),
|
||||
sessionMode: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional: Session mode to set before sending the prompt (e.g., 'plan', 'code', 'default'). If specified, the agent's mode will be changed before processing the prompt."
|
||||
),
|
||||
maxWait: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional: Maximum milliseconds to wait for agent to complete. If not provided, returns immediately (non-blocking). If agent doesn't finish within this time, returns with didComplete=false and agent continues processing in background."
|
||||
),
|
||||
sessionMode: z.string().optional().describe(
|
||||
"Optional: Session mode to set before sending the prompt. Mode is validated at runtime based on agent type. Claude Code supports 'default', 'plan', and 'bypassPermissions'. Codex offers 'read-only', 'auto', and 'full-access' for progressively broader permissions."
|
||||
),
|
||||
},
|
||||
outputSchema: {
|
||||
success: z.boolean().describe("Whether the prompt was sent successfully"),
|
||||
didComplete: z.boolean().describe("Whether the agent completed within maxWait time (always false if maxWait not specified)"),
|
||||
stopReason: z.string().nullable().describe("Reason agent stopped if it completed: 'end_turn', 'max_tokens', 'max_turn_requests', 'refusal', 'cancelled'"),
|
||||
status: z
|
||||
.string()
|
||||
.describe("Agent status immediately after enqueuing the prompt (usually 'processing')"),
|
||||
},
|
||||
},
|
||||
async ({ agentId, prompt, sessionMode, maxWait }) => {
|
||||
const response = await agentManager.sendPrompt(agentId, prompt, {
|
||||
maxWait,
|
||||
async ({ agentId, prompt, sessionMode }) => {
|
||||
await agentManager.sendPrompt(agentId, prompt, {
|
||||
sessionMode,
|
||||
});
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
didComplete: response.didComplete,
|
||||
stopReason: response.stopReason ?? null,
|
||||
status: agentManager.getAgentStatus(agentId),
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -215,14 +297,14 @@ export async function createAgentMcpServer(
|
||||
status: z.string(),
|
||||
createdAt: z.string(),
|
||||
lastActivityAt: z.string(),
|
||||
type: z.enum(["claude", "codex"]),
|
||||
type: AgentTypeEnum,
|
||||
sessionId: z.string().nullable(),
|
||||
error: z.string().nullable(),
|
||||
currentModeId: z.string().nullable(),
|
||||
availableModes: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
})).nullable(),
|
||||
})
|
||||
.describe("Detailed agent information"),
|
||||
@@ -264,14 +346,14 @@ export async function createAgentMcpServer(
|
||||
status: z.string(),
|
||||
createdAt: z.string(),
|
||||
lastActivityAt: z.string(),
|
||||
type: z.enum(["claude", "codex"]),
|
||||
type: AgentTypeEnum,
|
||||
sessionId: z.string().nullable(),
|
||||
error: z.string().nullable(),
|
||||
currentModeId: z.string().nullable(),
|
||||
availableModes: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
})).nullable(),
|
||||
})
|
||||
),
|
||||
@@ -435,14 +517,12 @@ export async function createAgentMcpServer(
|
||||
{
|
||||
title: "Set Agent Session Mode",
|
||||
description:
|
||||
"Change the agent's session mode (e.g., from 'ask' to 'code', or 'architect' to 'code'). Each mode affects the agent's behavior - 'ask' mode requests permission before changes, 'code' mode writes code directly, 'architect' mode plans without implementation. The available modes depend on the specific agent. Use get_agent_status or list_agents to see available modes for each agent.",
|
||||
"Change the agent's session mode. Claude Code supports 'plan' for step-by-step approvals and 'bypassPermissions' to auto-approve actions. Codex offers 'read-only', 'auto', and 'full-access' to control how freely it can modify the workspace or access the system. Use get_agent_status or list_agents to see which modes are currently available for each agent.",
|
||||
inputSchema: {
|
||||
agentId: z.string().describe("Agent ID to configure"),
|
||||
modeId: z
|
||||
.string()
|
||||
.describe(
|
||||
"The session mode to set (e.g., 'ask', 'code', 'architect'). Must be one of the agent's available modes."
|
||||
),
|
||||
modeId: z.string().describe(
|
||||
"The session mode to set. Mode is validated at runtime based on the agent's available modes. Check agent's availableModes from get_agent_status or list_agents to see valid options for the specific agent."
|
||||
),
|
||||
},
|
||||
outputSchema: {
|
||||
success: z.boolean().describe("Whether the mode change succeeded"),
|
||||
|
||||
Reference in New Issue
Block a user