/**
 * Custom Callboard Agent Tools — tool-server spec for agent sessions.
 *
 * Gives agents programmatic access to platform APIs during their agent sessions:
 * - Orchestrate other agents (start sessions, check status, read output)
 * - Manage their own cron jobs
 * - Query and log activity
 * - Discover other agents on the platform
 *
 * Returns a provider-neutral {@link ToolServerSpec}; callers translate to their
 * engine's registration shape via `provider.buildToolServer(spec)`.
 */
import { z } from "zod";
import { defineTool } from "../agents/ports/tools.js";
import { listAgents, getAgent, createAgent, agentExists, isValidAlias, ensureAgentWorkspaceDir, getAgentWorkspacePath } from "./agent-file-service.js";
import { scaffoldWorkspace, compileSystemPrompt } from "./claude-compiler.js";
import { executeAgent } from "./agent-executor.js";
import { listCronJobs, createCronJob, updateCronJob, deleteCronJob } from "./agent-cron-jobs.js";
import { buildJobManagementTools } from "./job-management-tools.js";
import { scheduleJob, cancelJob } from "./cron-scheduler.js";
import { listTriggers, getTrigger, createTrigger, updateTrigger, deleteTrigger } from "./agent-triggers.js";
import { getActivity, appendActivity } from "./agent-activity.js";
import { providerModelSchema, resolveProviderModelArgs } from "./tool-provider-args.js";
import { themeFileService } from "./theme-file-service.js";
import { generateThemeCSS } from "./quick-completion.js";
import { createLogger } from "../utils/logger.js";
import { resolveAgentKeyAlias, routeKeyAliasForPersist } from "./agent-settings.js";
const log = createLogger("agent-tools");
let _sendMessage = null;
/**
 * Register the sendMessage function. Called by claude.ts on module load
 * to break the circular dependency.
 */
export function setMessageSender(fn) {
    _sendMessage = fn;
}
function getSendMessage() {
    if (!_sendMessage)
        throw new Error("sendMessage not registered — call setMessageSender() first");
    return _sendMessage;
}
// ─── Tool Definitions ───────────────────────────────────────────────
/**
 * Build a tool-server spec scoped to a specific agent.
 * The agentAlias is baked into the closure so scoped tools (cron jobs, activity)
 * only access that agent's data. Orchestration tools can target other agents.
 */
export function buildAgentToolsSpec(agentAlias) {
    const agentConfig = getAgent(agentAlias);
    const agentTimezone = agentConfig?.userTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
    return {
        name: "callboard",
        version: "1.0.0",
        tools: [
            // ── Agent Orchestration ────────────────────────────────────
            defineTool("talk_to_agent", "Send a message to another agent and wait for their response. The target agent runs in its own workspace with its own identity and tools. Use this for inter-agent conversation where you need the result. For fire-and-forget, use deploy_agent instead.", {
                targetAlias: z.string().describe("Alias of the agent to talk to"),
                message: z.string().describe("The message to send to the target agent"),
                maxTurns: z.number().optional().describe("Maximum agentic turns for the target agent (default: 50)"),
                ...providerModelSchema,
            }, async (args) => {
                try {
                    // 1. Validate target agent exists
                    const targetConfig = getAgent(args.targetAlias);
                    if (!targetConfig) {
                        return { content: [{ type: "text", text: `Agent "${args.targetAlias}" not found` }] };
                    }
                    // 2. Prevent self-talk
                    if (args.targetAlias === agentAlias) {
                        return { content: [{ type: "text", text: "Error: An agent cannot talk to itself" }] };
                    }
                    const providerModel = resolveProviderModelArgs(args);
                    if (!providerModel.ok) {
                        return { content: [{ type: "text", text: `Error: ${providerModel.error}` }] };
                    }
                    // 3. Compile target agent's identity and workspace context
                    const workspacePath = getAgentWorkspacePath(args.targetAlias);
                    const fullSystemPrompt = compileSystemPrompt(targetConfig, workspacePath).prompt;
                    // 4. Build prompt with caller context
                    const callerConfig = getAgent(agentAlias);
                    const callerName = callerConfig?.name || agentAlias;
                    const contextualPrompt = `[Message from ${callerName} (${agentAlias})]\n\n${args.message}`;
                    const sendMessage = getSendMessage();
                    // 5. Build async generator prompt
                    const promptIterable = (async function* () {
                        yield {
                            type: "user",
                            message: { role: "user", content: contextualPrompt },
                        };
                    })();
                    // 6. Start target agent session
                    const emitter = await sendMessage({
                        prompt: promptIterable,
                        folder: workspacePath,
                        systemPrompt: fullSystemPrompt,
                        agentAlias: args.targetAlias,
                        maxTurns: args.maxTurns ?? 50,
                        defaultPermissions: { fileRead: "allow", fileWrite: "allow", codeExecution: "allow", webAccess: "allow" },
                        provider: providerModel.provider,
                        ...(providerModel.model && { model: providerModel.model }),
                    });
                    // 7. Wait for chat_created to get chatId
                    const chatId = await new Promise((resolve, reject) => {
                        const timeout = setTimeout(() => reject(new Error("Timed out waiting for target agent session to start")), 30_000);
                        emitter.on("event", (event) => {
                            if (event.type === "chat_created" && event.chatId) {
                                clearTimeout(timeout);
                                resolve(event.chatId);
                            }
                            else if (event.type === "error") {
                                clearTimeout(timeout);
                                reject(new Error(event.content || "Target agent session failed to start"));
                            }
                        });
                    });
                    // 8. Collect text output and wait for completion
                    const responseTexts = [];
                    await new Promise((resolve, reject) => {
                        const timeout = setTimeout(() => resolve(), 600_000); // 10 min safety timeout
                        emitter.on("event", (event) => {
                            if (event.type === "text" && event.content) {
                                responseTexts.push(event.content);
                            }
                            else if (event.type === "done") {
                                clearTimeout(timeout);
                                resolve();
                            }
                            else if (event.type === "error") {
                                clearTimeout(timeout);
                                reject(new Error(event.content || "Target agent session errored"));
                            }
                        });
                    });
                    // 9. Log activity
                    log.info(`Agent ${agentAlias} talked to ${args.targetAlias}, session ${chatId}`);
                    appendActivity(agentAlias, {
                        type: "system",
                        message: `Talked to agent ${args.targetAlias}`,
                        metadata: { chatId, targetAlias: args.targetAlias },
                    });
                    // 10. Return the collected response
                    const response = responseTexts.join("") || "(No text response from target agent)";
                    return {
                        content: [{ type: "text", text: JSON.stringify({ chatId, targetAlias: args.targetAlias, response }) }],
                    };
                }
                catch (err) {
                    log.error(`talk_to_agent failed: ${err.message}`);
                    return { content: [{ type: "text", text: `Error talking to agent: ${err.message}` }] };
                }
            }),
            defineTool("deploy_agent", "Start a new session AS another agent. The agent runs in its own workspace with its own identity, system prompt, and tools. The session runs asynchronously — use get_session_status and read_session_messages to monitor it. Unlike talk_to_agent, this does NOT wait for completion.", {
                targetAlias: z.string().describe("Alias of the agent to deploy (start a session as)"),
                prompt: z.string().describe("The task or message to give the agent"),
                maxTurns: z.number().optional().describe("Maximum agentic turns (default: 200)"),
                ...providerModelSchema,
            }, async (args) => {
                try {
                    // 1. Validate target agent exists
                    const targetConfig = getAgent(args.targetAlias);
                    if (!targetConfig) {
                        return { content: [{ type: "text", text: `Agent "${args.targetAlias}" not found` }] };
                    }
                    const providerModel = resolveProviderModelArgs(args);
                    if (!providerModel.ok) {
                        return { content: [{ type: "text", text: `Error: ${providerModel.error}` }] };
                    }
                    // 2. Execute the agent using the shared helper
                    const result = await executeAgent({
                        agentAlias: args.targetAlias,
                        prompt: args.prompt,
                        triggeredBy: "tool",
                        metadata: { deployedBy: agentAlias },
                        maxTurns: args.maxTurns,
                        provider: providerModel.provider,
                        ...(providerModel.model && { model: providerModel.model }),
                    });
                    if (!result) {
                        return { content: [{ type: "text", text: `Failed to deploy agent "${args.targetAlias}" — check agent config` }] };
                    }
                    log.info(`Agent ${agentAlias} deployed agent ${args.targetAlias}, session ${result.chatId}`);
                    appendActivity(agentAlias, {
                        type: "system",
                        message: `Deployed agent ${args.targetAlias}`,
                        metadata: { chatId: result.chatId, targetAlias: args.targetAlias },
                    });
                    return {
                        content: [
                            {
                                type: "text",
                                text: JSON.stringify({ chatId: result.chatId, status: "started", targetAlias: args.targetAlias }),
                            },
                        ],
                    };
                }
                catch (err) {
                    log.error(`deploy_agent failed: ${err.message}`);
                    return { content: [{ type: "text", text: `Error deploying agent: ${err.message}` }] };
                }
            }),
            // ── Cron Job Management ──────────────────────────────────
            defineTool("list_cron_jobs", "List all scheduled cron jobs for your agent.", {}, async () => {
                try {
                    const jobs = listCronJobs(agentAlias);
                    return { content: [{ type: "text", text: JSON.stringify(jobs, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error listing cron jobs: ${err.message}` }] };
                }
            }),
            defineTool("create_cron_job", `Create a new scheduled cron job for your agent. The job will execute on the specified schedule interpreted in the ${agentTimezone} timezone.`, {
                name: z.string().describe("Human-readable name for the job"),
                schedule: z
                    .string()
                    .describe(`Cron expression (e.g., '0 9 * * *' for daily at 9am, '*/30 * * * *' for every 30 min). Times are in ${agentTimezone}.`),
                prompt: z.string().describe("The task prompt that will be sent to your agent when the job fires"),
                type: z.enum(["one-off", "recurring", "indefinite"]).optional().describe("Job type (default: recurring)"),
                description: z.string().optional().describe("Description of what this job does"),
                skipIfRunning: z.boolean().optional().describe("Skip execution if the previous run is still active (default: false)"),
            }, async (args) => {
                try {
                    const job = createCronJob(agentAlias, {
                        name: args.name,
                        schedule: args.schedule,
                        type: args.type || "recurring",
                        status: "active",
                        description: args.description || "",
                        action: {
                            type: "start_session",
                            prompt: args.prompt,
                        },
                        ...(args.skipIfRunning !== undefined && { skipIfRunning: args.skipIfRunning }),
                    });
                    // Sync scheduler: register with node-cron so it actually fires
                    if (job.status === "active") {
                        scheduleJob(agentAlias, job);
                    }
                    log.info(`Agent ${agentAlias} created cron job: ${job.id} — ${args.name}`);
                    appendActivity(agentAlias, {
                        type: "cron",
                        message: `Created cron job: ${args.name} (${args.schedule})`,
                        metadata: { jobId: job.id },
                    });
                    return { content: [{ type: "text", text: JSON.stringify(job, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error creating cron job: ${err.message}` }] };
                }
            }),
            defineTool("update_cron_job", `Update an existing cron job. You can change the name, schedule, prompt, status (active/paused), or type. Schedule times are interpreted in the ${agentTimezone} timezone.`, {
                jobId: z.string().describe("The ID of the cron job to update"),
                name: z.string().optional().describe("New name"),
                schedule: z.string().optional().describe(`New cron expression. Times are in ${agentTimezone}.`),
                prompt: z.string().optional().describe("New task prompt"),
                status: z.enum(["active", "paused", "completed"]).optional().describe("New status"),
                type: z.enum(["one-off", "recurring", "indefinite"]).optional().describe("New type"),
                skipIfRunning: z.boolean().optional().describe("Skip execution if the previous run is still active"),
            }, async (args) => {
                try {
                    const updates = {};
                    if (args.name !== undefined)
                        updates.name = args.name;
                    if (args.schedule !== undefined)
                        updates.schedule = args.schedule;
                    if (args.status !== undefined)
                        updates.status = args.status;
                    if (args.type !== undefined)
                        updates.type = args.type;
                    if (args.skipIfRunning !== undefined)
                        updates.skipIfRunning = args.skipIfRunning;
                    if (args.prompt !== undefined) {
                        updates.action = { type: "start_session", prompt: args.prompt };
                    }
                    const updated = updateCronJob(agentAlias, args.jobId, updates);
                    if (!updated) {
                        return { content: [{ type: "text", text: `Cron job "${args.jobId}" not found` }] };
                    }
                    // Sync scheduler: cancel old schedule, re-schedule if active
                    cancelJob(args.jobId);
                    if (updated.status === "active") {
                        scheduleJob(agentAlias, updated);
                    }
                    log.info(`Agent ${agentAlias} updated cron job: ${args.jobId}`);
                    return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error updating cron job: ${err.message}` }] };
                }
            }),
            defineTool("delete_cron_job", "Delete a cron job by its ID.", {
                jobId: z.string().describe("The ID of the cron job to delete"),
            }, async (args) => {
                try {
                    // Cancel from scheduler before deleting
                    cancelJob(args.jobId);
                    const deleted = deleteCronJob(agentAlias, args.jobId);
                    if (!deleted) {
                        return { content: [{ type: "text", text: `Cron job "${args.jobId}" not found` }] };
                    }
                    log.info(`Agent ${agentAlias} deleted cron job: ${args.jobId}`);
                    return { content: [{ type: "text", text: `Cron job "${args.jobId}" deleted successfully` }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error deleting cron job: ${err.message}` }] };
                }
            }),
            // ── Trigger Management ──────────────────────────────────
            defineTool("list_triggers", "List all event triggers for your agent. Triggers automatically start sessions when matching events arrive.", {}, async () => {
                try {
                    const triggers = listTriggers(agentAlias);
                    return { content: [{ type: "text", text: JSON.stringify(triggers, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error listing triggers: ${err.message}` }] };
                }
            }),
            defineTool("create_trigger", "Create a new event trigger. When events matching the filter arrive, a session starts with the prompt template. Use {{event.source}}, {{event.eventType}}, {{event.data}}, {{event.data.fieldPath}} in the prompt. You can add conditions to filter on specific data fields (AND logic).", {
                name: z.string().describe("Human-readable name for the trigger"),
                description: z.string().optional().describe("What this trigger does"),
                source: z.string().optional().describe("Connection alias to filter (e.g. 'discord-bot'). Omit for any source."),
                eventType: z.string().optional().describe("Event type to filter (e.g. 'MESSAGE_CREATE'). Omit for any type."),
                conditions: z
                    .array(z.object({
                    field: z.string().describe("Dot-notation path into event.data (e.g. 'author.username')"),
                    operator: z.enum(["equals", "contains", "matches", "exists", "not_exists"]).describe("Comparison operator"),
                    value: z.string().optional().describe("Value to compare against. Not needed for exists/not_exists. For 'matches', use a regex pattern."),
                }))
                    .optional()
                    .describe("Data field conditions (AND logic). Each condition checks a field in the event data."),
                prompt: z.string().describe("Prompt template. Use {{event.source}}, {{event.eventType}}, {{event.data}}, {{event.data.fieldPath}} for event data."),
            }, async (args) => {
                try {
                    const trigger = createTrigger(agentAlias, {
                        name: args.name,
                        description: args.description || "",
                        status: "active",
                        filter: {
                            ...(args.source && { source: args.source }),
                            ...(args.eventType && { eventType: args.eventType }),
                            ...(args.conditions?.length && { conditions: args.conditions }),
                        },
                        action: { type: "start_session", prompt: args.prompt },
                        triggerCount: 0,
                    });
                    log.info(`Agent ${agentAlias} created trigger: ${trigger.id} — ${args.name}`);
                    appendActivity(agentAlias, {
                        type: "event",
                        message: `Created trigger: ${args.name}`,
                        metadata: { triggerId: trigger.id },
                    });
                    return { content: [{ type: "text", text: JSON.stringify(trigger, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error creating trigger: ${err.message}` }] };
                }
            }),
            defineTool("update_trigger", "Update an existing event trigger. You can change the name, status, filter source/eventType/conditions, or prompt.", {
                triggerId: z.string().describe("The ID of the trigger to update"),
                name: z.string().optional().describe("New name"),
                status: z.enum(["active", "paused"]).optional().describe("New status"),
                source: z.string().optional().describe("New source filter (connection alias)"),
                eventType: z.string().optional().describe("New event type filter"),
                conditions: z
                    .array(z.object({
                    field: z.string().describe("Dot-notation path into event.data (e.g. 'author.username')"),
                    operator: z.enum(["equals", "contains", "matches", "exists", "not_exists"]).describe("Comparison operator"),
                    value: z.string().optional().describe("Value to compare against. Not needed for exists/not_exists. For 'matches', use a regex pattern."),
                }))
                    .optional()
                    .describe("New data field conditions (AND logic). Pass empty array to remove all conditions."),
                prompt: z.string().optional().describe("New prompt template"),
            }, async (args) => {
                try {
                    const updates = {};
                    if (args.name !== undefined)
                        updates.name = args.name;
                    if (args.status !== undefined)
                        updates.status = args.status;
                    if (args.source !== undefined || args.eventType !== undefined || args.conditions !== undefined) {
                        const existing = getTrigger(agentAlias, args.triggerId);
                        updates.filter = {
                            ...(existing?.filter || {}),
                            ...(args.source !== undefined && { source: args.source || undefined }),
                            ...(args.eventType !== undefined && { eventType: args.eventType || undefined }),
                            ...(args.conditions !== undefined && { conditions: args.conditions.length ? args.conditions : undefined }),
                        };
                    }
                    if (args.prompt !== undefined) {
                        updates.action = { type: "start_session", prompt: args.prompt };
                    }
                    const updated = updateTrigger(agentAlias, args.triggerId, updates);
                    if (!updated) {
                        return { content: [{ type: "text", text: `Trigger "${args.triggerId}" not found` }] };
                    }
                    log.info(`Agent ${agentAlias} updated trigger: ${args.triggerId}`);
                    return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error updating trigger: ${err.message}` }] };
                }
            }),
            defineTool("delete_trigger", "Delete an event trigger by its ID.", {
                triggerId: z.string().describe("The ID of the trigger to delete"),
            }, async (args) => {
                try {
                    const deleted = deleteTrigger(agentAlias, args.triggerId);
                    if (!deleted) {
                        return { content: [{ type: "text", text: `Trigger "${args.triggerId}" not found` }] };
                    }
                    log.info(`Agent ${agentAlias} deleted trigger: ${args.triggerId}`);
                    return { content: [{ type: "text", text: `Trigger "${args.triggerId}" deleted successfully` }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error deleting trigger: ${err.message}` }] };
                }
            }),
            // ── Activity & Events ────────────────────────────────────
            defineTool("get_activity", "Query your agent's activity log. Returns recent activity entries sorted newest-first.", {
                type: z.enum(["chat", "event", "cron", "connection", "system"]).optional().describe("Filter by activity type"),
                limit: z.number().optional().describe("Maximum entries to return (default: 20)"),
            }, async (args) => {
                try {
                    const entries = getActivity(agentAlias, {
                        type: args.type,
                        limit: args.limit || 20,
                    });
                    return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error reading activity: ${err.message}` }] };
                }
            }),
            defineTool("log_activity", "Record an entry in your agent's activity log. Use this to track notable events, decisions, or actions.", {
                activityType: z.enum(["chat", "event", "cron", "connection", "system"]).describe("Type of activity"),
                message: z.string().describe("Human-readable description of what happened"),
                metadata: z.record(z.string(), z.unknown()).optional().describe("Additional structured data to store with the entry"),
            }, async (args) => {
                try {
                    const entry = appendActivity(agentAlias, {
                        type: args.activityType,
                        message: args.message,
                        metadata: args.metadata,
                    });
                    return { content: [{ type: "text", text: JSON.stringify(entry, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error logging activity: ${err.message}` }] };
                }
            }),
            // ── Agent Discovery ──────────────────────────────────────
            defineTool("list_agents", "List all agents on the platform. Returns basic info: alias, name, emoji, role, and description.", {}, async () => {
                try {
                    const agents = listAgents();
                    const summaries = agents.map((a) => ({
                        alias: a.alias,
                        name: a.name,
                        emoji: a.emoji || null,
                        role: a.role || null,
                        description: a.description,
                    }));
                    return { content: [{ type: "text", text: JSON.stringify(summaries, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error listing agents: ${err.message}` }] };
                }
            }),
            defineTool("get_agent_info", "Get public information about another agent. Returns name, emoji, role, description, and personality.", {
                alias: z.string().describe("The alias of the agent to look up"),
            }, async (args) => {
                try {
                    const config = getAgent(args.alias);
                    if (!config) {
                        return { content: [{ type: "text", text: `Agent "${args.alias}" not found` }] };
                    }
                    const info = {
                        alias: config.alias,
                        name: config.name,
                        emoji: config.emoji || null,
                        role: config.role || null,
                        description: config.description,
                        personality: config.personality || null,
                        tone: config.tone || null,
                        guidelines: config.guidelines || [],
                    };
                    return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error getting agent info: ${err.message}` }] };
                }
            }),
            // ── Agent Management ──────────────────────────────────
            defineTool("create_agent", "Create a new agent on the platform. The agent will have its own workspace, identity, and can be targeted by start_agent_session. Returns the created agent config.", {
                name: z.string().min(1).max(128).describe("Display name for the agent (1-128 characters)"),
                alias: z
                    .string()
                    .min(2)
                    .max(64)
                    .describe("Unique alias/identifier (2-64 chars: lowercase letters, numbers, hyphens, underscores, must start with letter or number)"),
                description: z.string().min(1).max(512).describe("What this agent does (1-512 characters)"),
                systemPrompt: z.string().optional().describe("Custom system prompt for the agent"),
                emoji: z.string().optional().describe("Emoji icon for the agent (e.g. '\uD83E\uDD16')"),
                personality: z.string().optional().describe("Personality description"),
                role: z.string().optional().describe("Role description (e.g. 'Research Assistant')"),
                tone: z.string().optional().describe("Communication tone (e.g. 'friendly and concise')"),
            }, async (args) => {
                try {
                    // Validate alias format
                    if (!isValidAlias(args.alias)) {
                        return {
                            content: [
                                {
                                    type: "text",
                                    text: "Error: Alias must be 2-64 characters: lowercase letters, numbers, hyphens, underscores. Must start with a letter or number.",
                                },
                            ],
                        };
                    }
                    // Check uniqueness
                    if (agentExists(args.alias)) {
                        return {
                            content: [{ type: "text", text: `Error: An agent with alias "${args.alias}" already exists` }],
                        };
                    }
                    // Build config
                    const config = {
                        name: args.name.trim(),
                        alias: args.alias.trim(),
                        description: args.description.trim(),
                        createdAt: Date.now(),
                        ...(args.systemPrompt !== undefined && { systemPrompt: args.systemPrompt.trim() || undefined }),
                        ...(args.emoji && { emoji: args.emoji }),
                        ...(args.personality && { personality: args.personality.trim() }),
                        ...(args.role && { role: args.role.trim() }),
                        ...(args.tone && { tone: args.tone.trim() }),
                    };
                    // Persist agent config
                    createAgent(config);
                    // Ensure workspace directory exists and scaffold initial files
                    const workspacePath = ensureAgentWorkspaceDir(config.alias);
                    scaffoldWorkspace(workspacePath);
                    log.info(`Agent ${agentAlias} created new agent: ${config.alias}`);
                    appendActivity(agentAlias, {
                        type: "system",
                        message: `Created new agent: ${config.name} (${config.alias})`,
                        metadata: { createdAlias: config.alias },
                    });
                    return { content: [{ type: "text", text: JSON.stringify({ ...config, workspacePath }, null, 2) }] };
                }
                catch (err) {
                    log.error(`create_agent failed: ${err.message}`);
                    return { content: [{ type: "text", text: `Error creating agent: ${err.message}` }] };
                }
            }),
            defineTool("update_agent", "Update an existing agent's configuration. Only the fields you provide will be changed — all other fields remain unchanged. You can update any agent, including yourself. Returns the full updated config.", {
                alias: z.string().describe("The alias of the agent to update"),
                name: z.string().min(1).max(128).optional().describe("New display name (1-128 characters)"),
                description: z.string().min(1).max(512).optional().describe("New description (1-512 characters)"),
                systemPrompt: z.string().optional().describe("New system prompt (pass empty string to clear)"),
                emoji: z.string().optional().describe("New emoji icon (pass empty string to clear)"),
                personality: z.string().optional().describe("New personality description (pass empty string to clear)"),
                role: z.string().optional().describe("New role description (pass empty string to clear)"),
                tone: z.string().optional().describe("New communication tone (pass empty string to clear)"),
                pronouns: z.string().optional().describe("New pronouns (pass empty string to clear)"),
                languages: z.array(z.string()).optional().describe("Languages the agent speaks"),
                guidelines: z.array(z.string()).optional().describe("Behavioral guidelines for the agent"),
                userName: z.string().optional().describe("Name of the user the agent serves"),
                userTimezone: z.string().optional().describe("User's timezone (e.g. 'America/New_York')"),
                userLocation: z.string().optional().describe("User's location"),
                userContext: z.string().optional().describe("Additional context about the user"),
                mcpKeyAlias: z.string().optional().describe("MCP secure proxy key alias for this agent"),
            }, async (args) => {
                try {
                    // Check agent exists
                    const existing = getAgent(args.alias);
                    if (!existing) {
                        return { content: [{ type: "text", text: `Error: Agent "${args.alias}" not found` }] };
                    }
                    // Build updated config — only override fields present in args
                    let updated = {
                        ...existing,
                        ...(args.name !== undefined && { name: args.name.trim() }),
                        ...(args.description !== undefined && { description: args.description.trim() }),
                        ...(args.systemPrompt !== undefined && { systemPrompt: args.systemPrompt?.trim() || undefined }),
                        ...(args.emoji !== undefined && { emoji: args.emoji || undefined }),
                        ...(args.personality !== undefined && { personality: args.personality?.trim() || undefined }),
                        ...(args.role !== undefined && { role: args.role?.trim() || undefined }),
                        ...(args.tone !== undefined && { tone: args.tone?.trim() || undefined }),
                        ...(args.pronouns !== undefined && { pronouns: args.pronouns?.trim() || undefined }),
                        ...(args.languages !== undefined && { languages: args.languages }),
                        ...(args.guidelines !== undefined && { guidelines: args.guidelines }),
                        ...(args.userName !== undefined && { userName: args.userName?.trim() || undefined }),
                        ...(args.userTimezone !== undefined && { userTimezone: args.userTimezone?.trim() || undefined }),
                        ...(args.userLocation !== undefined && { userLocation: args.userLocation?.trim() || undefined }),
                        ...(args.userContext !== undefined && { userContext: args.userContext?.trim() || undefined }),
                    };
                    // Route mcpKeyAlias to the correct per-mode field
                    if (args.mcpKeyAlias !== undefined) {
                        updated = routeKeyAliasForPersist(updated, args.mcpKeyAlias);
                    }
                    else {
                        delete updated.mcpKeyAlias;
                    }
                    // Validate required fields after merge
                    if (!updated.name || updated.name.length === 0 || updated.name.length > 128) {
                        return { content: [{ type: "text", text: "Error: Name must be 1-128 characters" }] };
                    }
                    if (!updated.description || updated.description.length === 0 || updated.description.length > 512) {
                        return { content: [{ type: "text", text: "Error: Description must be 1-512 characters" }] };
                    }
                    // Persist (createAgent acts as upsert)
                    createAgent(updated);
                    const workspacePath = ensureAgentWorkspaceDir(args.alias);
                    log.info(`Agent ${agentAlias} updated agent: ${args.alias}`);
                    appendActivity(agentAlias, {
                        type: "system",
                        message: `Updated agent: ${updated.name} (${args.alias})`,
                        metadata: { updatedAlias: args.alias },
                    });
                    return { content: [{ type: "text", text: JSON.stringify({ ...resolveAgentKeyAlias(updated), workspacePath }, null, 2) }] };
                }
                catch (err) {
                    log.error(`update_agent failed: ${err.message}`);
                    return { content: [{ type: "text", text: `Error updating agent: ${err.message}` }] };
                }
            }),
            // ── Themes ─────────────────────────────────────────────────
            defineTool("list_themes", "List all custom UI themes available on the Callboard instance.", {}, async () => {
                try {
                    const themes = themeFileService.listThemes();
                    return { content: [{ type: "text", text: JSON.stringify({ themes }, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error listing themes: ${err.message}` }] };
                }
            }),
            defineTool("get_theme", "Get the full details of a custom UI theme by name, including all CSS variable values for dark and light modes.", {
                name: z.string().describe("The theme name"),
            }, async (args) => {
                try {
                    const theme = themeFileService.getTheme(args.name);
                    if (!theme) {
                        return { content: [{ type: "text", text: `Theme "${args.name}" not found.` }] };
                    }
                    return { content: [{ type: "text", text: JSON.stringify(theme, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error getting theme: ${err.message}` }] };
                }
            }),
            defineTool("generate_theme", "Generate a new custom UI theme using AI. Provide a name and a natural language description of the desired look and feel. The AI will create appropriate CSS variable values for both dark and light modes.", {
                name: z.string().describe("Name for the new theme (e.g. 'Ocean Breeze', 'Sunset Warmth')"),
                description: z
                    .string()
                    .describe("Natural language description of the desired theme colors and mood (e.g. 'warm sunset colors with orange and purple accents')"),
            }, async (args) => {
                try {
                    const existing = themeFileService.getTheme(args.name);
                    if (existing) {
                        return { content: [{ type: "text", text: `Theme "${args.name}" already exists. Use a different name or delete it first.` }] };
                    }
                    const theme = await generateThemeCSS(args.name, args.description);
                    if (!theme) {
                        return { content: [{ type: "text", text: "Failed to generate theme. The AI did not produce valid theme data." }] };
                    }
                    themeFileService.createTheme(theme);
                    return { content: [{ type: "text", text: JSON.stringify({ message: `Theme "${theme.name}" created successfully.`, theme }, null, 2) }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error generating theme: ${err.message}` }] };
                }
            }),
            defineTool("update_theme", "Update an existing custom UI theme. You only need to provide the CSS variables you want to change — any variables not included will keep their existing values. You can also optionally rename the theme.", {
                name: z.string().describe("Current name of the theme to update"),
                new_name: z.string().optional().describe("New name for the theme (omit to keep current name)"),
                dark: z.record(z.string(), z.string()).optional().describe("Dark mode CSS variables to update — only include variables you want to change"),
                light: z.record(z.string(), z.string()).optional().describe("Light mode CSS variables to update — only include variables you want to change"),
            }, async (args) => {
                try {
                    const existing = themeFileService.getTheme(args.name);
                    if (!existing) {
                        return { content: [{ type: "text", text: `Theme "${args.name}" not found. Use get_theme to see available themes.` }] };
                    }
                    const updated = {
                        name: args.new_name?.trim() || existing.name,
                        dark: { ...existing.dark, ...args.dark },
                        light: { ...existing.light, ...args.light },
                        createdAt: existing.createdAt,
                        updatedAt: new Date().toISOString(),
                    };
                    themeFileService.updateTheme(args.name, updated);
                    return {
                        content: [{ type: "text", text: JSON.stringify({ message: `Theme "${updated.name}" updated successfully.`, theme: updated }, null, 2) }],
                    };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error updating theme: ${err.message}` }] };
                }
            }),
            defineTool("delete_theme", "Delete a custom UI theme by name.", {
                name: z.string().describe("The name of the theme to delete"),
            }, async (args) => {
                try {
                    const existing = themeFileService.getTheme(args.name);
                    if (!existing) {
                        return { content: [{ type: "text", text: `Theme "${args.name}" not found.` }] };
                    }
                    themeFileService.deleteTheme(args.name);
                    return { content: [{ type: "text", text: `Theme "${args.name}" deleted successfully.` }] };
                }
                catch (err) {
                    return { content: [{ type: "text", text: `Error deleting theme: ${err.message}` }] };
                }
            }),
            // ── Jobs: deterministic multi-step workflows ──────────────────
            // Definitions are reusable templates; spawning one creates a run — a
            // persisted state machine driven by the backend job runner, with agent
            // sessions doing the work inside steps. Shared with the chat-session
            // callboard-tools server — see job-management-tools.ts.
            ...buildJobManagementTools({
                getCreatedBy: () => ({ kind: "agent", ref: agentAlias }),
                via: "agent",
            }),
        ],
    };
}
