/**
 * Task tool — spawn a subagent to handle complex tasks autonomously.
 *
 * The LLM calls `task(subagent_type, name, query)` to delegate work
 * to a lightweight in-process child agent. The child inherits the relevant
 * tools, provider registry, and auth from the parent runtime.
 *
 * Multiple task() calls execute in parallel (our forked agent loop does
 * Promise.all on tool calls).
 */
import { Type } from '@sinclair/typebox';
import { randomUUID } from 'node:crypto';
import { StringEnum } from '../../../ai/typebox.js';
import { RUNTIME_CAPABILITY } from '../../../core/permission-policy.js';
import { getActiveUserMessageId } from '../../../core/user-message-id.js';
import { SHORTCUT_MODEL_REF } from '../../../model-ids.js';
import { MCP_TOOL_NAMES, TASK } from '../../../tool-names.js';
import { ABORT_AGENT_MESSAGE } from '../../../utils/abort.js';
import { DEFAULT_MAX_CHARS, ERROR_MAX_CHARS_LLM, truncateErrorOutput, truncateOutput } from '../../../utils/truncate.js';
import { getDeclaredCapabilityIds, hasCapability } from '../../permissions/capability-catalog.js';
import { requirePermissionPolicy } from '../../permissions/require-policy.js';
import { DEFAULT_SUBAGENT_MODEL, DEFAULT_SUBAGENT_TIMEOUT_SECONDS, TASK_MODEL_CHOICES } from '../../subagents/defaults.js';
import { buildTaskToolDescription, getSubagent, getSubagentNames } from '../../subagents/index.js';
import { TaskRunCoordinator } from './lifecycle.js';
import { taskManager as defaultTaskManager, formatTaskNotification, formatTaskNotificationDisplay, getTaskScopeId } from './manager.js';
import { resolveTaskQuery } from './query.js';
import { createTaskOutputFile, createTaskSidechainArtifact } from './sidechain.js';
import { emptyTaskUsage } from './types.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const TOOL_NAME = TASK;
const TOOL_LABEL = 'Task';
const TOOL_DESCRIPTION = buildTaskToolDescription();
const RUN_IN_BACKGROUND_DESCRIPTION = 'Launch the subagent in the background and return its agent id immediately. ' +
    'Use when the user explicitly asks for background work or when the task is independent ' +
    'and likely to take more than 20 minutes.';
/** Truncate task display text to a safe length unless the runner already truncated it. */
function boundTaskDisplayText(text, isError = false) {
    if (!text) {
        return '';
    }
    const alreadyTruncated = text.includes('[Truncated: showing ') || text.includes('[... truncated ');
    if (alreadyTruncated) {
        return text;
    }
    const maxChars = isError ? ERROR_MAX_CHARS_LLM : DEFAULT_MAX_CHARS;
    if (text.length <= maxChars) {
        return text;
    }
    return isError
        ? truncateErrorOutput(text, ERROR_MAX_CHARS_LLM)
        : truncateOutput(text, DEFAULT_MAX_CHARS);
}
/** Merge persistent and session-grant filesystem approvals into a deduplicated list. */
function getInheritedFilesystemGrants(ctx) {
    const policyGrants = ctx?.permissionPolicy?.filesystem.persistentApprovals ?? [];
    const sessionGrants = ctx?.sessionState?.getFilesystemApprovalGrants() ?? [];
    const deduped = new Map();
    for (const grant of [...policyGrants, ...sessionGrants]) {
        deduped.set(`${grant.path}:${grant.access}:${grant.scope}`, grant);
    }
    return Array.from(deduped.values());
}
/** Combine the parent's workspace roots with the subagent's working directory. */
function getInheritedWorkspaceRoots(permissionPolicy, taskCwd) {
    return Array.from(new Set([...permissionPolicy.filesystem.workspaceRoots, taskCwd]));
}
/** Compute the capability flags implied by a subagent's declared tool list. */
function getChildCapabilities(childToolNames) {
    const declaredToolNames = [...new Set(childToolNames)];
    const declaredCapabilityIds = getDeclaredCapabilityIds(declaredToolNames);
    return {
        declaredToolNames,
        declaredCapabilityIds,
        filesystemRead: hasCapability(declaredCapabilityIds, RUNTIME_CAPABILITY.FILESYSTEM_READ),
        filesystemWrite: hasCapability(declaredCapabilityIds, RUNTIME_CAPABILITY.FILESYSTEM_WRITE),
        shell: hasCapability(declaredCapabilityIds, RUNTIME_CAPABILITY.SHELL_EXECUTE),
        spreadsheet: hasCapability(declaredCapabilityIds, RUNTIME_CAPABILITY.SPREADSHEET_MUTATE),
        subagents: hasCapability(declaredCapabilityIds, RUNTIME_CAPABILITY.SUBAGENT_SPAWN)
    };
}
/** Derive the subagent's permission policy from the parent policy and approval flags. */
export function buildInheritedSubagentPermissionPolicy(input) {
    const capabilities = getChildCapabilities(input.childToolNames);
    const fullWorker = input.fullWorker === true;
    return {
        modeName: undefined,
        dangerouslySkipRuntimePermissions: input.parentPolicy.dangerouslySkipRuntimePermissions || fullWorker,
        declaredToolNames: capabilities.declaredToolNames,
        declaredCapabilityIds: capabilities.declaredCapabilityIds,
        filesystem: {
            workspaceRoots: getInheritedWorkspaceRoots(input.parentPolicy, input.taskCwd),
            persistentApprovals: [...input.persistentApprovals],
            defaultReadScope: !capabilities.filesystemRead
                ? 'none'
                : fullWorker
                    ? 'unrestricted'
                    : input.parentPolicy.filesystem.defaultReadScope,
            defaultWriteScope: !capabilities.filesystemWrite
                ? 'none'
                : fullWorker
                    ? 'unrestricted'
                    : input.parentPolicy.filesystem.defaultWriteScope,
            requireApprovalOutsideWorkspace: fullWorker
                ? false
                : input.parentPolicy.filesystem.requireApprovalOutsideWorkspace
        },
        shell: {
            enabled: capabilities.shell && (fullWorker || input.parentPolicy.shell.enabled),
            mutationBehavior: !capabilities.shell
                ? 'deny'
                : fullWorker
                    ? 'allow'
                    : input.parentPolicy.shell.mutationBehavior
        },
        spreadsheet: {
            enabled: capabilities.spreadsheet && (fullWorker || input.parentPolicy.spreadsheet.enabled),
            mutationBehavior: !capabilities.spreadsheet
                ? 'deny'
                : fullWorker
                    ? 'require_approval'
                    : input.parentPolicy.spreadsheet.mutationBehavior,
            allowHeadlessApproval: fullWorker || input.parentPolicy.spreadsheet.allowHeadlessApproval
        },
        subagents: {
            enabled: capabilities.subagents && input.parentPolicy.subagents.enabled,
            inheritParentPolicy: input.parentPolicy.subagents.inheritParentPolicy
        },
        approvals: {
            runtimeOwned: input.parentPolicy.approvals.runtimeOwned,
            allowHeadlessApproval: input.parentPolicy.approvals.allowHeadlessApproval
        }
    };
}
/** Pick the most informative error string from a subagent result for display. */
export function getSubagentDisplayError(result) {
    return result.errorMessage || result.text || '(subagent failed with no output)';
}
/** Build the typebox schema for the task tool, optionally excluding subagent types. */
function buildSchema(excludeSubagents) {
    const allNames = getSubagentNames();
    const names = excludeSubagents ? allNames.filter((n) => !excludeSubagents.includes(n)) : allNames;
    return Type.Object({
        subagent_type: StringEnum(names, {
            description: 'The type of specialized agent to use for this task'
        }),
        name: Type.String({
            description: "Descriptive name for the task (max 3 words). Use the target being analyzed, e.g. 'Revenue Sheet', 'Income Statement', 'Format Summary'. NEVER use generic names like 'Task 1' or 'Analysis'. Shown in the UI."
        }),
        query: Type.String({
            description: 'The task for the subagent. Be specific: include file paths, sheet names, cell ranges, and concrete instructions. The subagent has its own context window and cannot see your conversation history. ' +
                'Can also be a file path ending in .txt (e.g., "/tmp/query.txt") — the file contents will be used as the query. Useful for batch/parallel calls to programmatically write .txt query files with variables.'
        }),
        model: Type.Optional(StringEnum(TASK_MODEL_CHOICES, {
            description: `${DEFAULT_SUBAGENT_MODEL} (default): best all-around choice. ${SHORTCUT_MODEL_REF.Gpt55}: stronger reasoning but weaker formatting — best for auditing, reviewing, and verification tasks, especially when run concurrently with Opus tasks for diverse perspectives.`
        })),
        run_in_background: Type.Optional(Type.Boolean({
            description: RUN_IN_BACKGROUND_DESCRIPTION
        }))
    });
}
/** Build the child runtime tool catalog by replacing the task tool with a scoped clone. */
export function buildChildRuntimeTools(tools, options) {
    return tools.map((tool) => tool.name === TASK
        ? createTaskTool({
            cwd: options.cwd,
            excludeSubagents: options.excludeSubagents ? [...options.excludeSubagents] : undefined,
            getRuntimeTools: options.getRuntimeTools,
            notifyParent: options.notifyParent,
            taskManager: options.taskManager
        })
        : tool);
}
/** Return any declared tool names that are missing from the runtime tool catalog. */
function getMissingDeclaredTools(declaredToolNames, runtimeTools) {
    const available = new Set(runtimeTools.map((tool) => tool.name));
    const optionalTools = new Set(MCP_TOOL_NAMES);
    return declaredToolNames.filter((toolName) => !available.has(toolName) && !optionalTools.has(toolName));
}
/** Build the `task` tool that spawns subagents in the foreground or background. */
export function createTaskTool(options = {}) {
    const { cwd: cwdOverride, excludeSubagents, getRuntimeTools, notifyParent, taskManager = defaultTaskManager } = options;
    const schema = buildSchema(excludeSubagents);
    return {
        name: TOOL_NAME,
        label: TOOL_LABEL,
        description: TOOL_DESCRIPTION,
        parameters: schema,
        async execute(toolCallId, params, signal, onUpdate, ctx) {
            const { subagent_type, name, query: rawQuery, model: modelOverride, run_in_background } = params;
            // A spawn-approval grant can force background mode regardless of what
            // the model asked for; otherwise honor the LLM's run_in_background flag.
            const effectiveRunInBackground = ctx?.subagentSpawnGrant?.runInBackground ?? run_in_background === true;
            // Look up agent config
            const agentConfig = getSubagent(subagent_type);
            if (!agentConfig) {
                const available = getSubagentNames().join(', ');
                return {
                    content: [
                        {
                            type: 'text',
                            text: `Unknown subagent_type '${subagent_type}'. Available: ${available}`
                        }
                    ],
                    details: undefined,
                    isError: true
                };
            }
            const taskCwd = cwdOverride ?? process.cwd();
            const query = resolveTaskQuery(rawQuery);
            // Apply model override if provided
            const effectiveConfig = modelOverride
                ? { ...agentConfig, model: modelOverride }
                : agentConfig;
            const effectiveModel = effectiveConfig.model ?? DEFAULT_SUBAGENT_MODEL;
            const permissionPolicy = requirePermissionPolicy(ctx, TASK);
            // Resolve model info for context window display and API key
            const [modelProvider, ...modelIdParts] = effectiveModel.split('/');
            const modelId = modelIdParts.join('/');
            const extCtx = ctx;
            const userMessageId = getActiveUserMessageId(extCtx.getMessages?.() ?? []);
            const modelInfo = extCtx.modelRegistry.find(modelProvider, modelId);
            const contextWindow = modelInfo?.contextWindow ?? 0;
            const inheritedFilesystemGrants = getInheritedFilesystemGrants(ctx);
            const fullWorkerChild = permissionPolicy.dangerouslySkipRuntimePermissions ||
                ctx?.subagentSpawnGrant?.fullWorker === true;
            const inheritedPermissionPolicy = buildInheritedSubagentPermissionPolicy({
                parentPolicy: permissionPolicy,
                childToolNames: effectiveConfig.tools,
                taskCwd,
                persistentApprovals: inheritedFilesystemGrants,
                fullWorker: fullWorkerChild
            });
            const timeoutMs = (agentConfig.timeoutSeconds ?? DEFAULT_SUBAGENT_TIMEOUT_SECONDS) * 1000;
            const abortController = new AbortController();
            // Compose signals: external abort + timeout
            const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
            if (signal) {
                signal.addEventListener('abort', () => abortController.abort(), { once: true });
            }
            let result;
            let finishedForegroundTask;
            let finalTaskAgentId;
            let finalTaskOutputFile;
            try {
                // Child subagents select from the runtime catalog supplied by the task
                // tool factory. ExtensionContext stays a host boundary; it is not a
                // source of subagent tool policy.
                const runtimeToolCatalog = getRuntimeTools?.() ?? extCtx.getTools?.();
                if (runtimeToolCatalog === undefined && effectiveConfig.tools.length > 0) {
                    return {
                        content: [
                            {
                                type: 'text',
                                text: 'Error: task subagents require a runtime tool catalog when the selected subagent declares tools.'
                            }
                        ],
                        details: undefined,
                        isError: true
                    };
                }
                const runtimeTools = buildChildRuntimeTools(runtimeToolCatalog ?? [], {
                    cwd: taskCwd,
                    excludeSubagents: effectiveConfig.excludeSubagents,
                    getRuntimeTools,
                    notifyParent,
                    taskManager
                });
                const missingToolNames = getMissingDeclaredTools(effectiveConfig.tools, runtimeTools);
                if (missingToolNames.length > 0) {
                    return {
                        content: [
                            {
                                type: 'text',
                                text: `Error: subagent '${subagent_type}' declares unavailable tools: ${missingToolNames.join(', ')}`
                            }
                        ],
                        details: undefined,
                        isError: true
                    };
                }
                const agentId = `subagent-${randomUUID()}`;
                finalTaskAgentId = agentId;
                const sidechain = extCtx.sessionManager
                    ? createTaskSidechainArtifact(extCtx.sessionManager, effectiveConfig.name, agentId)
                    : undefined;
                const makeRunCoordinator = (scopeId) => new TaskRunCoordinator({
                    taskManager,
                    scopeId,
                    agentId,
                    agent: effectiveConfig,
                    tools: runtimeTools,
                    context: extCtx,
                    taskCwd,
                    permissionPolicy: inheritedPermissionPolicy,
                    userMessageId,
                    sidechain,
                    timeoutMs,
                    effectiveModel,
                    notifyParent
                });
                if (effectiveRunInBackground) {
                    if (!extCtx.sessionManager) {
                        throw new Error('Background task requires a session manager for output files.');
                    }
                    const taskScopeId = getTaskScopeId(extCtx.sessionManager);
                    if (!taskScopeId) {
                        throw new Error('Background task requires a persisted session.');
                    }
                    // Disallow nested background launches: a backgrounded subagent
                    // would have no live parent loop to deliver its terminal
                    // notification to once the immediate parent itself returns.
                    const sessionHeader = extCtx.sessionManager.getHeader();
                    if (sessionHeader.isSubagent) {
                        throw new Error('Background task launches are only supported from the top-level session.');
                    }
                    const outputFile = createTaskOutputFile(extCtx.sessionManager, agentId);
                    const runCoordinator = makeRunCoordinator(taskScopeId);
                    const backgroundAbort = new AbortController();
                    taskManager.register({
                        scopeId: taskScopeId,
                        agentId,
                        name,
                        subagentType: subagent_type,
                        prompt: query,
                        isBackgrounded: true,
                        transcriptFile: sidechain?.sessionFile,
                        outputFile,
                        abortController: backgroundAbort,
                        resumeMessage: (message) => runCoordinator.startDetached(message, { resumeFromSidechain: true })
                    });
                    runCoordinator.startDetached(query, {
                        abortController: backgroundAbort,
                        useRegisteredRun: true
                    });
                    const handleText = [
                        `async_launched: ${agentId}`,
                        `Output: ${outputFile}`,
                        sidechain?.sessionFile ? `Transcript: ${sidechain.sessionFile}` : undefined
                    ]
                        .filter((line) => line !== undefined)
                        .join('\n');
                    const details = {
                        text: handleText,
                        toolCalls: [],
                        usage: emptyTaskUsage(),
                        model: effectiveModel,
                        contextWindow,
                        isRunning: false,
                        isError: false,
                        sessionId: sidechain?.sessionId,
                        sessionFile: sidechain?.sessionFile,
                        agentId,
                        outputFile,
                        status: 'running'
                    };
                    return {
                        content: [{ type: 'text', text: handleText }],
                        details
                    };
                }
                const taskScopeId = extCtx.sessionManager
                    ? getTaskScopeId(extCtx.sessionManager)
                    : undefined;
                const runCoordinator = makeRunCoordinator(taskScopeId);
                const foregroundOutputFile = extCtx.sessionManager && taskScopeId
                    ? createTaskOutputFile(extCtx.sessionManager, agentId)
                    : undefined;
                finalTaskOutputFile = foregroundOutputFile;
                const foregroundTask = taskScopeId && foregroundOutputFile
                    ? taskManager.register({
                        scopeId: taskScopeId,
                        agentId,
                        name,
                        subagentType: subagent_type,
                        prompt: query,
                        isBackgrounded: false,
                        transcriptFile: sidechain?.sessionFile,
                        outputFile: foregroundOutputFile,
                        abortController,
                        resumeMessage: (message) => startForegroundResumeRun(message)
                    })
                    : undefined;
                const startForegroundResumeRun = (nextPrompt) => {
                    if (!taskScopeId || !foregroundTask)
                        return;
                    const resumeAbort = new AbortController();
                    runCoordinator.startDetached(nextPrompt, {
                        resumeFromSidechain: Boolean(sidechain?.sessionFile),
                        abortController: resumeAbort
                    });
                };
                const subagentPromise = runCoordinator.runForeground(query, {
                    signal: abortController.signal,
                    onProgress: (progress) => {
                        const displayText = boundTaskDisplayText(progress.text);
                        const details = {
                            text: displayText,
                            toolCalls: progress.toolCalls,
                            usage: progress.usage,
                            model: progress.model,
                            contextWindow,
                            isRunning: true,
                            isError: false,
                            sessionId: progress.sessionId,
                            sessionFile: progress.sessionFile
                        };
                        onUpdate?.({
                            content: [{ type: 'text', text: displayText || '(running...)' }],
                            details
                        });
                    }
                });
                result = await (ctx?.heartbeatWrap
                    ? ctx.heartbeatWrap(subagentPromise, 'subagent', {
                        toolName: 'task',
                        toolCallId,
                        label: `Running ${subagent_type}…`
                    })
                    : subagentPromise);
                if (foregroundTask && taskScopeId) {
                    finishedForegroundTask = runCoordinator.finishCurrent(result);
                }
            }
            finally {
                clearTimeout(timeoutId);
            }
            if (!finishedForegroundTask) {
                extCtx.deductChildUsage({
                    input: result.usage.input,
                    output: result.usage.output,
                    turns: result.usage.turns
                });
            }
            // User cancel (or timeout) — surface a clean cancelled message, not a generic error
            if (result.aborted) {
                const taskContent = finishedForegroundTask
                    ? formatTaskNotification(finishedForegroundTask)
                    : ABORT_AGENT_MESSAGE;
                const taskDisplay = finishedForegroundTask
                    ? formatTaskNotificationDisplay(finishedForegroundTask)
                    : '';
                const details = {
                    text: taskDisplay,
                    toolCalls: result.toolCalls,
                    usage: result.usage,
                    model: result.model,
                    contextWindow,
                    isRunning: false,
                    isError: false,
                    isAborted: true,
                    sessionId: result.sessionId,
                    sessionFile: result.sessionFile,
                    agentId: finalTaskAgentId,
                    outputFile: finalTaskOutputFile,
                    status: finishedForegroundTask?.status
                };
                return {
                    content: [{ type: 'text', text: taskContent }],
                    details
                };
            }
            const isError = result.exitCode !== 0 || result.stopReason === 'error' || result.stopReason === 'aborted';
            const displayText = boundTaskDisplayText(result.text, isError);
            const displayErrorMessage = result.errorMessage
                ? boundTaskDisplayText(result.errorMessage, true)
                : undefined;
            const details = {
                text: finishedForegroundTask
                    ? formatTaskNotificationDisplay(finishedForegroundTask)
                    : displayText,
                toolCalls: result.toolCalls,
                usage: result.usage,
                model: result.model,
                contextWindow,
                isRunning: false,
                isError,
                errorMessage: displayErrorMessage,
                sessionId: result.sessionId,
                sessionFile: result.sessionFile,
                agentId: finalTaskAgentId,
                outputFile: finalTaskOutputFile,
                status: finishedForegroundTask?.status
            };
            if (isError) {
                const errorText = boundTaskDisplayText(getSubagentDisplayError(result), true);
                return {
                    content: [
                        {
                            type: 'text',
                            text: finishedForegroundTask
                                ? formatTaskNotification(finishedForegroundTask)
                                : `Error: ${errorText}`
                        }
                    ],
                    details,
                    isError: true
                };
            }
            return {
                content: [
                    {
                        type: 'text',
                        text: finishedForegroundTask
                            ? formatTaskNotification(finishedForegroundTask)
                            : displayText || '(no output)'
                    }
                ],
                details
            };
        }
    };
}
//# sourceMappingURL=task.js.map