import { createAgentRuntime } from '../adapters/agent-runtime.js';
import * as agentService from '../services/agent.js';
import * as channelService from '../services/channel.js';
import * as issueService from '../services/issue.js';
import * as taskService from '../services/task.js';
import * as workspaceService from '../services/workspace.js';
import { getWorkflow, mapWorkflowToTaskDrafts, validateWorkflowForRun } from '../services/workflow.js';
import { createIssueFunctionTools } from '../services/builtin-tools/index.js';
import { getThinkingRuntimeConfig } from '../services/llm-model-config.js';
import { prependPersistentAgentContext } from '../services/persistent-agent-context.js';
import { completeIssueAgentProgress, createIssueAgentProgress, createIssueAgentProgressTracker } from './issue-agent-progress.js';
import { wrapOnEventWithHooks } from '../services/hook-engine.js';
import { executeCommandNode } from '../services/workflow-command-runner.js';
const ACTIVE_TASK_STATUSES = ['running', 'reviewing', 'retrying', 'waiting_review'];
const activeIssueRuntimes = new Map();
export async function syncIssueTasksAfterPlanning(workspaceId, issueId, input, ctx) {
    const issue = issueService.getById(workspaceId, issueId);
    if (!issue) {
        console.warn(`[issue-task-controller] issue not found workspaceId=${workspaceId} issueId=${issueId}`);
        return;
    }
    const taskSyncPreset = findTaskSyncAgentForIssue(workspaceId, issue, input.plannerPreset);
    if (!taskSyncPreset) {
        console.warn(`[issue-task-controller] no task sync agent member found workspaceId=${workspaceId} issueId=${issueId}`);
        updateIssueStatus(workspaceId, issueId, 'error', ctx);
        return;
    }
    const taskSyncAgent = agentService.getOrCreateSessionForConfig(workspaceId, taskSyncPreset);
    ctx.broadcast('agent.started', taskSyncAgent);
    const fromStatus = taskSyncAgent.status;
    agentService.updateStatus(workspaceId, taskSyncAgent.id, 'active');
    issueService.addAgent(workspaceId, issueId, taskSyncPreset.id);
    ctx.broadcast('agent.status_changed', { agentId: taskSyncAgent.id, from: fromStatus, to: 'active' });
    ctx.broadcast('agent.output', { agentId: taskSyncAgent.id, data: `Syncing issue tasks: ${issueId}` });
    const startTime = Date.now();
    const taskSyncWorkingDir = resolveIssueWorkspaceRoot(workspaceId);
    const workspace = workspaceService.getById(workspaceId);
    const progress = createIssueAgentProgress(workspaceId, issue, taskSyncPreset, taskSyncAgent.id, {
        runtime: taskSyncPreset.runtimeKind,
        model: taskSyncPreset.modelId,
        phase: 'task_creator',
    });
    const taskSyncTracker = createIssueAgentProgressTracker({
        workspaceId,
        issue,
        progress,
        agentSessionId: taskSyncAgent.id,
        onOutput: (line) => {
            ctx.broadcast('agent.output', { agentId: taskSyncAgent.id, data: line });
        },
    });
    const runtime = createRuntimeForPreset(taskSyncPreset);
    const taskSyncUserPrompt = buildTaskSyncPrompt(issue, input, taskSyncWorkingDir);
    const result = await runtime.execute(buildIssueAgentPrompt(workspaceId, taskSyncUserPrompt, taskSyncWorkingDir, taskSyncPreset), taskSyncWorkingDir, {
        maxTurns: 20,
        userPrompt: taskSyncUserPrompt,
        mcpServers: undefined,
        functionTools: createTaskSyncTools(workspaceId, issue, taskSyncPreset, ctx),
        skills: [],
        configDir: agentService.getAgentConfigDir(workspaceId, taskSyncPreset),
        sandboxDirs: taskSyncPreset.sandboxDirs,
        outputStyle: taskSyncPreset.outputStyle,
        onEvent: wrapOnEventWithHooks(taskSyncTracker.handleEvent.bind(taskSyncTracker), workspaceId, workspace?.hooksEnabled),
    });
    if (taskSyncTracker.output.length === 0) {
        for (const line of result.output) {
            taskSyncTracker.output.push(line);
            ctx.broadcast('agent.output', { agentId: taskSyncAgent.id, data: line });
        }
    }
    completeIssueAgentProgress(workspaceId, issue, progress, result.summary, taskSyncTracker.output, {
        runtime: taskSyncPreset.runtimeKind,
        model: taskSyncPreset.modelId,
        duration: Date.now() - startTime,
        messageStatus: result.success ? 'completed' : 'error',
        parts: taskSyncTracker.buildParts({
            sessionId: taskSyncAgent.id,
            model: taskSyncPreset.modelId,
            usage: result.usage,
            success: result.success,
            error: result.error,
        }),
    });
    agentService.complete(workspaceId, taskSyncAgent.id, result.success ? undefined : result.error || result.summary, {
        runtime: taskSyncPreset.runtimeKind,
        model: taskSyncPreset.modelId,
        summary: result.summary,
        output: taskSyncTracker.output.length ? taskSyncTracker.output : result.output,
        durationMs: Date.now() - startTime,
        usage: result.usage,
        costUsd: result.costUsd,
    });
    ctx.broadcast('agent.completed', { agentId: taskSyncAgent.id, result });
    if (!result.success) {
        updateIssueStatus(workspaceId, issueId, 'error', ctx);
        return;
    }
    if (taskService.list(workspaceId, issueId).length === 0) {
        const fallbackTasks = replaceIssueTasksFromDrafts(workspaceId, issueId, ctx, [
            {
                key: 'implementation',
                title: `Implement: ${issue.title}`,
                description: issue.description || input.planSummary || input.planOutput?.join('\n') || `Implement issue: ${issue.title}`,
            },
        ]);
        console.warn(`[issue-task-controller] task sync produced no tasks; created fallback count=${fallbackTasks.length}`);
    }
    updateIssueStatus(workspaceId, issueId, 'in_progress', ctx);
    await scheduleRunnableIssueTasks(workspaceId, issueId, ctx);
}
export async function scheduleRunnableIssueTasks(workspaceId, issueId, ctx, _options = {}) {
    const issue = issueService.getById(workspaceId, issueId);
    if (!issue)
        return;
    const tasks = taskService.list(workspaceId, issueId);
    const doneIds = new Set(tasks.filter((task) => task.status === 'done').map((task) => task.id));
    const hasActive = tasks.some((task) => ACTIVE_TASK_STATUSES.includes(task.status));
    const runnable = tasks.filter((task) => task.status === 'pending'
        && (task.dependsOnTaskIds ?? []).every((dependencyId) => doneIds.has(dependencyId)));
    if (runnable.length === 0) {
        if (!hasActive && tasks.length > 0 && tasks.every((task) => task.status === 'done')) {
            updateIssueStatus(workspaceId, issueId, 'completed', ctx);
        }
        return;
    }
    const tasksToRun = issue.continuousRun === false ? runnable.slice(0, 1) : runnable;
    for (const task of tasksToRun) {
        await runIssueTask(workspaceId, issueId, task.id, ctx);
    }
}
export async function runIssueTask(workspaceId, issueId, taskId, ctx) {
    const issue = issueService.getById(workspaceId, issueId);
    const task = taskService.getById(workspaceId, taskId);
    if (!issue || !task) {
        console.warn(`[issue-task-controller] missing issue/task workspaceId=${workspaceId} issueId=${issueId} taskId=${taskId}`);
        return;
    }
    if (task.status !== 'pending') {
        return;
    }
    // Check if this task came from a command workflow node
    const workflow = findWorkflowForIssue(workspaceId, issue);
    const commandNode = findCommandNodeForTask(workflow, task);
    if (commandNode) {
        await runCommandTask(workspaceId, issueId, taskId, commandNode, ctx);
        return;
    }
    const taskAgentPreset = findAgentForTask(workspaceId, issue, task);
    if (!taskAgentPreset) {
        if (issueService.getById(workspaceId, issueId)?.status === 'error')
            return;
        const missingExecutorResult = {
            success: false,
            summary: 'No runnable agent configured in issue channel members',
            artifacts: [],
            error: 'No runnable agent member found for issue channel',
        };
        const failed = taskService.updateStatus(workspaceId, taskId, 'failed', {
            result: {
                success: false,
                summary: missingExecutorResult.summary,
                artifacts: [],
                error: missingExecutorResult.error,
            },
        });
        broadcastTaskUpdate(ctx, failed, task.status);
        await handleTaskFailure(workspaceId, issueId, taskId, missingExecutorResult, ctx);
        return;
    }
    const taskAgent = agentService.getOrCreateSessionForConfig(workspaceId, taskAgentPreset);
    ctx.broadcast('agent.started', taskAgent);
    const agentFromStatus = taskAgent.status;
    agentService.updateStatus(workspaceId, taskAgent.id, 'active');
    issueService.addAgent(workspaceId, issueId, taskAgentPreset.id);
    ctx.broadcast('agent.status_changed', { agentId: taskAgent.id, from: agentFromStatus, to: 'active' });
    const runningTask = taskService.assignAgent(workspaceId, taskId, taskAgent.id);
    if (!runningTask) {
        agentService.complete(workspaceId, taskAgent.id, 'Task not found');
        return;
    }
    broadcastTaskUpdate(ctx, runningTask, task.status);
    agentService.assignTask(workspaceId, taskAgent.id, taskId);
    const runtime = createRuntimeForPreset(taskAgentPreset);
    registerActiveIssueRuntime(workspaceId, issueId, taskAgent.id, runtime);
    const agentWorkingDir = resolveIssueWorkspaceRoot(workspaceId);
    const startTime = Date.now();
    const progress = createIssueAgentProgress(workspaceId, issue, taskAgentPreset, taskAgent.id, {
        runtime: taskAgentPreset.runtimeKind,
        model: taskAgentPreset.modelId,
        taskId,
        phase: taskAgentPreset.role,
    });
    const agentTracker = createIssueAgentProgressTracker({
        workspaceId,
        issue,
        progress,
        agentSessionId: taskAgent.id,
        workspaceRoot: agentWorkingDir,
        onOutput: (line) => {
            ctx.broadcast('agent.output', { agentId: taskAgent.id, data: line });
            ctx.broadcast('task.output', { taskId, data: line });
        },
    });
    const workspace = workspaceService.getById(workspaceId);
    ctx.broadcast('agent.output', { agentId: taskAgent.id, data: `Executing task: ${runningTask.title}` });
    ctx.broadcast('agent.output', {
        agentId: taskAgent.id,
        data: `[debug] workflow agentConfigId=${taskAgentPreset.id} role=${taskAgentPreset.role} taskAgentConfigId=${runningTask.agentConfigId || '(fallback)'} runtime=${taskAgentPreset.runtimeKind ?? 'open-agent-sdk'}`,
    });
    let result;
    try {
        const taskUserPrompt = buildTaskAgentPrompt(issue, runningTask, taskAgentPreset, agentWorkingDir);
        result = await runtime.execute(buildIssueAgentPrompt(workspaceId, taskUserPrompt, agentWorkingDir, taskAgentPreset), agentWorkingDir, {
            maxTurns: 100,
            userPrompt: taskUserPrompt,
            mcpServers: agentService.getMcpServers(taskAgentPreset.mcps),
            functionTools: createCurrentIssueTools(workspaceId, issue, taskAgentPreset),
            skills: agentService.getAvailableSkillNames(agentService.getAgentConfigDir(workspaceId, taskAgentPreset), taskAgentPreset.skills),
            configDir: agentService.getAgentConfigDir(workspaceId, taskAgentPreset),
            sandboxDirs: runningTask.sandboxDirs ?? taskAgentPreset.sandboxDirs,
            outputStyle: taskAgentPreset.outputStyle,
            onEvent: wrapOnEventWithHooks(agentTracker.handleEvent.bind(agentTracker), workspaceId, workspace?.hooksEnabled),
        });
    }
    finally {
        unregisterActiveIssueRuntime(workspaceId, issueId, taskAgent.id);
    }
    if (issueService.getById(workspaceId, issueId)?.status === 'error') {
        const failedTask = taskService.updateStatus(workspaceId, taskId, 'failed', {
            result: {
                success: false,
                summary: 'Interrupted by user',
                artifacts: [],
                error: 'Interrupted by user',
            },
        });
        broadcastTaskUpdate(ctx, failedTask, 'running');
        return;
    }
    if (agentTracker.output.length === 0) {
        for (const line of result.output) {
            agentTracker.output.push(line);
            ctx.broadcast('agent.output', { agentId: taskAgent.id, data: line });
            ctx.broadcast('task.output', { taskId, data: line });
        }
    }
    completeIssueAgentProgress(workspaceId, issue, progress, result.summary, agentTracker.output, {
        runtime: taskAgentPreset.runtimeKind,
        model: taskAgentPreset.modelId,
        duration: Date.now() - startTime,
        messageStatus: result.success ? 'completed' : 'error',
        parts: agentTracker.buildParts({
            sessionId: taskAgent.id,
            workspaceRoot: agentWorkingDir,
            model: taskAgentPreset.modelId,
            usage: result.usage,
            success: result.success,
            error: result.error,
        }),
    });
    if (!result.success) {
        const failedTask = taskService.updateStatus(workspaceId, taskId, 'failed', { result });
        broadcastTaskUpdate(ctx, failedTask, 'running');
    }
    agentService.complete(workspaceId, taskAgent.id, result.success ? undefined : result.error || result.summary, {
        runtime: taskAgentPreset.runtimeKind,
        model: taskAgentPreset.modelId,
        summary: result.summary,
        output: agentTracker.output.length ? agentTracker.output : result.output,
        durationMs: Date.now() - startTime,
        usage: result.usage,
        costUsd: result.costUsd,
    });
    ctx.broadcast('agent.completed', { agentId: taskAgent.id, result });
    if (!result.success) {
        await handleTaskFailure(workspaceId, issueId, taskId, result, ctx);
        return;
    }
    const completedTask = taskService.complete(workspaceId, taskId, result);
    broadcastTaskUpdate(ctx, completedTask, 'running');
    if (issueService.getById(workspaceId, issueId)?.continuousRun !== false) {
        await scheduleRunnableIssueTasks(workspaceId, issueId, ctx);
    }
}
async function runCommandTask(workspaceId, issueId, taskId, commandNode, ctx) {
    const task = taskService.getById(workspaceId, taskId);
    if (!task || task.status !== 'pending')
        return;
    const runningTask = taskService.updateStatus(workspaceId, taskId, 'running');
    if (!runningTask)
        return;
    broadcastTaskUpdate(ctx, runningTask, 'pending');
    const cmdData = commandNode.data;
    const cmdScript = cmdData.script;
    ctx.broadcast('task.output', { taskId, data: `$ ${cmdScript.split('\n')[0]}${cmdScript.includes('\n') ? ' ...' : ''}` });
    const result = await executeCommandNode(workspaceId, commandNode);
    ctx.broadcast('task.output', { taskId, data: result.stdout });
    if (result.stderr) {
        ctx.broadcast('task.output', { taskId, data: result.stderr });
    }
    if (!result.success) {
        const failedTask = taskService.updateStatus(workspaceId, taskId, 'failed', {
            result: {
                success: false,
                summary: `Command failed with exit code ${result.exitCode}`,
                artifacts: [],
                error: result.stderr || `Exit code: ${result.exitCode}`,
            },
        });
        broadcastTaskUpdate(ctx, failedTask, 'running');
        await handleTaskFailure(workspaceId, issueId, taskId, {
            success: false,
            summary: `Command failed with exit code ${result.exitCode}`,
            artifacts: [],
            error: result.stderr || `Exit code: ${result.exitCode}`,
        }, ctx);
        return;
    }
    const completedTask = taskService.complete(workspaceId, taskId, {
        success: true,
        summary: result.stdout.slice(-500) || 'Command completed',
        artifacts: [],
    });
    broadcastTaskUpdate(ctx, completedTask, 'running');
    if (issueService.getById(workspaceId, issueId)?.continuousRun !== false) {
        await scheduleRunnableIssueTasks(workspaceId, issueId, ctx);
    }
}
export async function continueIssueTaskScheduling(workspaceId, issueId, ctx) {
    await scheduleRunnableIssueTasks(workspaceId, issueId, ctx, { force: true });
}
export function stopIssueAutomation(workspaceId, issueId, reason = 'Interrupted by user') {
    const key = issueRunKey(workspaceId, issueId);
    const runtimes = activeIssueRuntimes.get(key);
    if (runtimes) {
        for (const [agentId, runtime] of runtimes.entries()) {
            try {
                runtime.stop();
            }
            catch {
                // Runtime may already be unwinding.
            }
            agentService.complete(workspaceId, agentId, reason);
        }
        activeIssueRuntimes.delete(key);
    }
    const activeTasks = taskService.list(workspaceId, issueId)
        .filter((task) => ACTIVE_TASK_STATUSES.includes(task.status));
    return activeTasks.map((task) => {
        const from = task.status;
        const failed = taskService.updateStatus(workspaceId, task.id, 'failed', {
            result: {
                success: false,
                summary: reason,
                artifacts: [],
                error: reason,
            },
        });
        return failed ? { task: failed, from } : null;
    }).filter((item) => Boolean(item));
}
function createTaskSyncTools(workspaceId, issue, preset, ctx) {
    return [
        ...createCurrentIssueTools(workspaceId, issue, preset),
        {
            name: 'ReplaceIssueTasks',
            description: 'Replace current non-running issue tasks. Use stable task keys and dependsOnKeys; the controller maps keys to stored task ids.',
            inputSchema: {
                type: 'object',
                properties: {
                    issueId: { type: 'string' },
                    tasks: {
                        type: 'array',
                        items: {
                            type: 'object',
                            properties: {
                                key: { type: 'string' },
                                title: { type: 'string' },
                                description: { type: 'string' },
                                agentConfigId: { type: 'string' },
                                dependsOnKeys: { type: 'array', items: { type: 'string' } },
                                sandboxDirs: { type: 'array', items: { type: 'string' } },
                            },
                            required: ['key', 'title', 'description'],
                            additionalProperties: false,
                        },
                    },
                },
                required: ['issueId', 'tasks'],
                additionalProperties: false,
            },
            annotations: { destructive: true, openWorld: false },
            execute: async (input) => {
                assertIssueId(issue.id, input);
                const data = input;
                const tasks = parseTaskDrafts(data.tasks);
                return replaceIssueTasksFromDrafts(workspaceId, issue.id, ctx, tasks);
            },
        },
    ];
}
export function replaceIssueTasksFromDrafts(workspaceId, issueId, ctx, drafts) {
    const issue = requireIssue(workspaceId, issueId);
    const validAgentIds = new Set(getIssueMemberPresets(workspaceId, issue).map((agent) => agent.id));
    const normalizedDrafts = drafts.map((task) => ({
        ...task,
        agentConfigId: task.agentConfigId && validAgentIds.has(task.agentConfigId) ? task.agentConfigId : undefined,
    }));
    const created = taskService.replaceIssueTasks(workspaceId, issueId, normalizedDrafts.map((task) => ({
        title: task.title,
        description: task.description,
        agentConfigId: task.agentConfigId,
        sandboxDirs: task.sandboxDirs,
    })));
    const idByKey = new Map(normalizedDrafts.map((task, index) => [task.key, created[index]?.id]));
    const updated = created.map((task, index) => {
        const dependencyIds = (normalizedDrafts[index]?.dependsOnKeys ?? [])
            .map((key) => idByKey.get(key))
            .filter((id) => Boolean(id));
        return taskService.update(workspaceId, task.id, { dependsOnTaskIds: dependencyIds }) ?? task;
    });
    const runningTaskIds = taskService.list(workspaceId, issueId)
        .filter((task) => task.status === 'running')
        .map((task) => task.id);
    const issueTasks = issueService.replaceTasks(workspaceId, issueId, [
        ...runningTaskIds,
        ...updated.map((task) => task.id),
    ]);
    for (const task of updated)
        ctx.broadcast('task.created', task);
    if (issueTasks)
        ctx.broadcast('issue.updated', issueTasks);
    return updated;
}
export function createTasksFromWorkflow(workspaceId, issueId, template, ctx) {
    const issue = requireIssue(workspaceId, issueId);
    ensureWorkflowAgentsForRun(workspaceId, issue, template, ctx);
    // Validate all agents exist, are enabled, and are in channel members
    const channel = channelService.getChannel(workspaceId, issue.channelId);
    const memberAgentIds = new Set(channel?.members ?? []);
    const validationError = validateWorkflowForRun(workspaceId, template, memberAgentIds);
    if (validationError) {
        throw new Error(`Workflow validation failed: ${validationError}`);
    }
    const drafts = mapWorkflowToTaskDrafts(template);
    issueService.updateStatus(workspaceId, issueId, 'planned');
    ctx.broadcast('issue.status_changed', { issueId, from: issue.status, to: 'planned' });
    replaceIssueTasksFromDrafts(workspaceId, issueId, ctx, drafts);
    issueService.updateStatus(workspaceId, issueId, 'in_progress');
    ctx.broadcast('issue.status_changed', { issueId, from: 'planned', to: 'in_progress' });
    ctx.broadcast('issue.updated', issueService.getById(workspaceId, issueId));
    scheduleRunnableIssueTasks(workspaceId, issueId, ctx).catch((err) => {
        console.error(`[workflow] task scheduling failed for issue ${issueId}:`, err);
    });
}
function ensureWorkflowAgentsForRun(workspaceId, issue, template, ctx) {
    const workflowAgentIds = [...new Set(template.nodes.filter((n) => n.type === 'agent').map((node) => node.data.agentConfigId).filter(Boolean))];
    const mergedMembers = [...new Set([...(issue.members ?? []), ...workflowAgentIds])];
    const membersChanged = mergedMembers.length !== (issue.members ?? []).length
        || mergedMembers.some((member, index) => member !== issue.members?.[index]);
    if (membersChanged) {
        issue.members = mergedMembers;
        const savedIssue = issueService.save(workspaceId, issue);
        ctx.broadcast('issue.updated', savedIssue);
    }
    const channel = channelService.getChannel(workspaceId, issue.channelId);
    const mergedChannelMembers = [...new Set([...(channel?.members ?? []), ...workflowAgentIds])];
    const channelMembersChanged = mergedChannelMembers.length !== (channel?.members ?? []).length
        || mergedChannelMembers.some((member, index) => member !== channel?.members?.[index]);
    if (channelMembersChanged) {
        const updatedChannel = channelService.updateChannel(workspaceId, issue.channelId, { members: mergedChannelMembers });
        if (updatedChannel)
            ctx.broadcast('channel.updated', updatedChannel);
    }
}
function findAgentForTask(workspaceId, issue, task) {
    const assignable = getIssueMemberPresets(workspaceId, issue);
    if (!task.agentConfigId)
        return null;
    return assignable.find((agent) => agent.id === task.agentConfigId) ?? null;
}
function findTaskSyncAgentForIssue(workspaceId, issue, plannerPreset) {
    const assignable = getIssueMemberPresets(workspaceId, issue);
    const findByRole = (role) => assignable.find((agent) => agent.role === role) ?? null;
    return plannerPreset
        ?? findByRole('task_creator')
        ?? findByRole('agent')
        ?? findByRole('custom')
        ?? findByRole('executor')
        ?? assignable.find((agent) => !['scheduler', 'bot'].includes(agent.role)) ?? null;
}
function getIssueMemberPresets(workspaceId, issue) {
    const channel = channelService.getChannel(workspaceId, issue.channelId);
    if (!channel)
        return [];
    const members = new Set(channel.members);
    return (agentService.listPresets(workspaceId) ?? [])
        .filter((agent) => members.has(agent.id) && agent.enabled !== false);
}
function updateIssueStatus(workspaceId, issueId, status, ctx) {
    const issue = issueService.getById(workspaceId, issueId);
    if (!issue)
        return null;
    if (issue.status === status) {
        ctx.broadcast('issue.updated', issue);
        return issue;
    }
    const updated = issueService.updateStatus(workspaceId, issueId, status);
    if (!updated)
        return null;
    ctx.broadcast('issue.status_changed', { issueId, from: issue.status, to: status });
    ctx.broadcast('issue.updated', updated);
    return updated;
}
async function handleTaskFailure(workspaceId, issueId, taskId, result, ctx) {
    const task = taskService.getById(workspaceId, taskId);
    if (!task)
        return;
    if ((task.retryCount ?? 0) < (task.maxRetries ?? 3)) {
        const reset = taskService.resetForRetry(workspaceId, taskId, { incrementRetry: true });
        if (reset) {
            ctx.broadcast('task.status_changed', { taskId, from: task.status, to: reset.status });
            ctx.broadcast('task.updated', reset);
            ctx.broadcast('task.output', {
                taskId,
                data: `Retrying task after agent error (${reset.retryCount}/${reset.maxRetries}).`,
            });
        }
        await scheduleRunnableIssueTasks(workspaceId, issueId, ctx);
        return;
    }
    const issue = issueService.getById(workspaceId, issueId);
    const updated = issueService.markError(workspaceId, issueId, result.error || result.summary || 'Task failed after retries');
    if (!updated)
        return;
    ctx.broadcast('issue.status_changed', { issueId, from: issue?.status ?? 'in_progress', to: 'error' });
    ctx.broadcast('issue.updated', updated);
}
function registerActiveIssueRuntime(workspaceId, issueId, agentId, runtime) {
    const key = issueRunKey(workspaceId, issueId);
    const runtimes = activeIssueRuntimes.get(key) ?? new Map();
    runtimes.set(agentId, runtime);
    activeIssueRuntimes.set(key, runtimes);
}
function unregisterActiveIssueRuntime(workspaceId, issueId, agentId) {
    const key = issueRunKey(workspaceId, issueId);
    const runtimes = activeIssueRuntimes.get(key);
    if (!runtimes)
        return;
    runtimes.delete(agentId);
    if (runtimes.size === 0)
        activeIssueRuntimes.delete(key);
}
function issueRunKey(workspaceId, issueId) {
    return `${workspaceId}:${issueId}`;
}
function findWorkflowForIssue(workspaceId, issue) {
    if (!issue.workflowId)
        return null;
    return getWorkflow(issue.workflowId);
}
function findCommandNodeForTask(workflow, task) {
    if (!workflow)
        return null;
    if (task.agentConfigId)
        return null;
    for (const node of workflow.nodes) {
        if (node.type === 'command' && (task.title === node.label || task.description?.startsWith('Command:'))) {
            return node;
        }
    }
    return null;
}
function broadcastTaskUpdate(ctx, task, from) {
    if (!task)
        return;
    ctx.broadcast('task.status_changed', { taskId: task.id, from, to: task.status });
    ctx.broadcast('task.updated', task);
}
function createRuntimeForPreset(preset) {
    return createAgentRuntime({
        kind: preset.runtimeKind,
        provider: preset.modelProvider,
        model: preset.modelId,
        apiKey: preset.apiKey,
        baseURL: preset.apiBase,
        ...getThinkingRuntimeConfig(preset),
    });
}
function resolveIssueWorkspaceRoot(workspaceId) {
    return workspaceService.getById(workspaceId)?.boundDirs?.[0] || process.cwd();
}
function buildIssueAgentPrompt(workspaceId, prompt, workingDir, preset) {
    return prependPersistentAgentContext(prompt, {
        workspaceId,
        workingDir,
        boundDirs: workspaceService.getById(workspaceId)?.boundDirs,
        includeWorkspacePrompt: false,
        excludeNativeClaudeMd: preset.runtimeKind === 'claude-code',
    });
}
function buildTaskSyncPrompt(issue, input, workingDir) {
    return [
        'You are the issue task synchronization controller.',
        'First call ViewCurrentChannelIssue with the current channel id to load the shared issue context, comments, tasks, channel members, and valid assignable agent config ids.',
        `The current workspace working directory is: ${workingDir}`,
        'All implementation tasks must be scoped to this workspace unless the issue explicitly says otherwise.',
        'For Bash commands that create or modify files under the current working directory, use relative paths instead of absolute paths.',
        'Use ReplaceIssueTasks to write tasks. Do not rely on private planner-only context.',
        'Create coarse-grained, independently deliverable tasks. Default to a single implementation task for one cohesive issue.',
        'NEVER create review/audit/审查 tasks. The review phase is handled automatically by the system after all implementation tasks complete — do not include it as a task.',
        'Split into multiple tasks only when the issue clearly requires major cross-area work, such as separate frontend and backend changes, database/API contract changes plus UI changes, or independent workstreams that different agents can complete without stepping on each other.',
        'Do not split by tiny implementation steps such as "update types", "add route", "adjust UI text", "run tests", or "write docs" unless that item is itself a substantial deliverable.',
        'Each task description should include the relevant implementation scope and expected verification, so agents do not need a separate task for every small step.',
        'Assign only valid agentConfigId values from ViewCurrentChannelIssue.validAgentConfigIds. Express task dependencies with dependsOnKeys only when one task truly cannot start before another completes.',
        '',
        'Current issue:',
        `- Issue id: ${issue.id}`,
        `- Channel id: ${issue.channelId}`,
        `- Title: ${issue.title}`,
        `- Status: ${issue.status}`,
        `- Description: ${issue.description || '(empty)'}`,
        '',
        'Planner result:',
        `Summary: ${input.planSummary || '(empty)'}`,
        input.planOutput?.join('\n').trim(),
    ].filter(Boolean).join('\n');
}
function buildTaskAgentPrompt(issue, task, preset, workingDir) {
    return [
        preset.systemPrompt?.trim(),
        'Before executing, call ViewCurrentChannelIssue with the current channel id to load the latest shared issue context and comments.',
        `The current workspace working directory is: ${workingDir}`,
        'Create and modify project files under this working directory. Do not place deliverables in /tmp unless the task explicitly asks for temporary scratch output.',
        'For Bash commands that create or modify files under this working directory, use relative paths such as `mkdir -p css js` instead of absolute paths.',
        'When reporting created files, use paths under the workspace working directory.',
        '',
        'Current issue:',
        `- Issue id: ${issue.id}`,
        `- Channel id: ${issue.channelId}`,
        `- Title: ${issue.title}`,
        '',
        'Current task:',
        `- Task id: ${task.id}`,
        `- Assigned agent: ${preset.name} (${preset.role})`,
        `- Title: ${task.title}`,
        `- Description: ${task.description}`,
    ].filter(Boolean).join('\n');
}
function createCurrentIssueTools(workspaceId, issue, preset) {
    const channel = channelService.getChannel(workspaceId, issue.channelId);
    return createIssueFunctionTools(workspaceId, channel, {
        senderId: preset.id,
        senderRole: preset.role,
    }, ['ViewCurrentChannelIssue', 'AddCurrentChannelComment']);
}
function parseTaskDrafts(value) {
    if (!Array.isArray(value))
        throw new Error('tasks must be an array.');
    const seen = new Set();
    return value.map((item, index) => {
        if (!item || typeof item !== 'object' || Array.isArray(item)) {
            throw new Error(`tasks[${index}] must be an object.`);
        }
        const data = item;
        const key = asNonEmptyString(data.key, `tasks[${index}].key`);
        if (seen.has(key))
            throw new Error(`duplicate task key: ${key}`);
        seen.add(key);
        return {
            key,
            title: asNonEmptyString(data.title, `tasks[${index}].title`),
            description: typeof data.description === 'string' ? data.description.trim() : '',
            agentConfigId: typeof data.agentConfigId === 'string' ? data.agentConfigId.trim() || undefined : undefined,
            dependsOnKeys: asStringArray(data.dependsOnKeys),
            sandboxDirs: asStringArray(data.sandboxDirs),
        };
    });
}
function assertIssueId(issueId, input) {
    if (!input || typeof input !== 'object' || Array.isArray(input))
        throw new Error('Tool input must be an object.');
    if (input.issueId !== issueId)
        throw new Error(`issueId must match current issue id: ${issueId}`);
}
function requireIssue(workspaceId, issueId) {
    const issue = issueService.getById(workspaceId, issueId);
    if (!issue)
        throw new Error(`Issue not found: ${issueId}`);
    return issue;
}
function asNonEmptyString(value, field) {
    if (typeof value !== 'string' || !value.trim())
        throw new Error(`${field} is required.`);
    return value.trim();
}
function asStringArray(value) {
    if (value === undefined)
        return undefined;
    if (!Array.isArray(value))
        return undefined;
    const normalized = value.filter((item) => typeof item === 'string').map((item) => item.trim()).filter(Boolean);
    return normalized.length ? [...new Set(normalized)] : undefined;
}
//# sourceMappingURL=issue-task-controller.js.map