import { v4 as uuid } from 'uuid';
import { copyFileSync, cpSync, existsSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { basename, extname, isAbsolute, join, normalize, relative } from 'node:path';
import { BUILT_IN_AGENT_TOOLS } from '@agent-spaces/shared';
import { listAgentSessions, getAgentSession, createAgentSession, updateAgentSession, deleteAgentSession, getAgentUsageDashboard, recordAgentUsage, } from '../storage/agent-store.js';
import { getWorkspace, listWorkspaces } from '../storage/workspace-store.js';
import { listIssues, updateIssue } from '../storage/issue-store.js';
import { listChannels, updateChannel } from './channel.js';
import { ensureDir, getDataDir } from '../storage/json-store.js';
import { listModels, listProviders } from '../storage/llm-store.js';
import { extractUsageFromOutput } from '../storage/usage.js';
const DEFAULT_AGENT_ROLE = 'agent';
export const AGENT_GENERATOR_PRESET_ID = 'agent-generator';
export const AGENT_COMMIT_PRESET_ID = 'commit-agent';
export const AGENT_TITLE_GENERATOR_PRESET_ID = 'title-generator';
const BUILT_IN_AGENT_IDS = [
    AGENT_GENERATOR_PRESET_ID,
    AGENT_COMMIT_PRESET_ID,
    AGENT_TITLE_GENERATOR_PRESET_ID,
];
const VALID_RUNTIME_KINDS = ['open-agent-sdk', 'claude-code', 'codex', 'langchain', 'hermes', 'oh-my-pi'];
const VALID_TOOL_NAMES = new Set(BUILT_IN_AGENT_TOOLS.map((tool) => tool.name));
const CLAUDE_BUILT_IN_DIRS = ['agents', 'commands'];
const ANTHROPIC_BRIDGE_PROVIDERS = [
    'openai-responses-to-anthropic-messages',
    'openai-chat-completions-to-anthropic-messages',
];
export function listPresets(_workspaceId) {
    return listTemplates();
}
export function isValidRole(role) {
    return typeof role === 'string' && role.trim().length > 0;
}
export function listTemplates() {
    const root = getGlobalAgentTemplatesDir();
    if (!existsSync(root))
        return getDefaultBuiltInAgentPresets();
    const templates = readdirSync(root, { withFileTypes: true })
        .filter((entry) => entry.isDirectory())
        .map((entry) => readAgentTemplate(entry.name))
        .filter((template) => Boolean(template));
    return withBuiltInTemplatesFirst(templates);
}
export async function testConnection(workspaceId, data) {
    if (workspaceId) {
        const ws = getWorkspace(workspaceId);
        if (!ws)
            return null;
    }
    const providerConfig = resolveProviderConfig(data);
    const apiBase = (providerConfig?.apiBase ?? data.apiBase)?.trim();
    const apiKey = (providerConfig?.apiKey ?? data.apiKey)?.trim();
    const model = data.modelId?.trim();
    const provider = data.modelProvider || inferProvider(apiBase);
    const debug = {
        provider,
        apiBase,
        model,
        hasApiKey: Boolean(apiKey),
        apiKeyPreview: maskApiKey(apiKey),
    };
    if (!apiBase)
        return { success: false, message: 'apiBase is required', debug };
    if (!apiKey)
        return { success: false, message: 'apiKey is required', debug };
    if (!model)
        return { success: false, message: 'modelId is required', debug };
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 15_000);
    try {
        const requestUrl = getConnectionTestUrl(provider, apiBase, model);
        debug.requestUrl = requestUrl;
        console.info('[agent:test-connection] request', debug);
        const response = await testProviderConnection(provider, requestUrl, apiKey, model, controller.signal);
        if (!response.ok) {
            const { message, body } = await readErrorMessage(response);
            debug.responseBody = body;
            console.warn('[agent:test-connection] failed', {
                ...debug,
                status: response.status,
                message,
            });
            return {
                success: false,
                status: response.status,
                message,
                debug,
            };
        }
        console.info('[agent:test-connection] succeeded', {
            ...debug,
            status: response.status,
        });
        return {
            success: true,
            status: response.status,
            message: 'Connection test succeeded',
            debug,
        };
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        const resultMessage = message === 'This operation was aborted' ? 'Connection test timed out' : message;
        console.error('[agent:test-connection] error', {
            ...debug,
            message: resultMessage,
        });
        return {
            success: false,
            message: resultMessage,
            debug,
        };
    }
    finally {
        clearTimeout(timeout);
    }
}
async function testProviderConnection(provider, requestUrl, apiKey, model, signal) {
    switch (provider) {
        case 'anthropic-messages':
            return testAnthropicMessagesConnection(requestUrl, apiKey, model, signal);
        case 'openai-responses':
        case 'openai-responses-to-anthropic-messages':
            return testOpenAIResponsesConnection(requestUrl, apiKey, model, signal);
        case 'openai-chat-completions-to-anthropic-messages':
            return testOpenAIChatCompletionsConnection(requestUrl, apiKey, model, signal);
        case 'gemini-generate-content':
            return testGeminiGenerateContentConnection(requestUrl, apiKey, signal);
        case 'openai-chat-completions':
            return testOpenAIChatCompletionsConnection(requestUrl, apiKey, model, signal);
    }
}
async function testOpenAIChatCompletionsConnection(requestUrl, apiKey, model, signal) {
    return fetch(requestUrl, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model,
            messages: [{ role: 'user', content: 'Reply with OK.' }],
            max_tokens: 8,
            temperature: 0,
        }),
        signal,
    });
}
async function testOpenAIResponsesConnection(requestUrl, apiKey, model, signal) {
    return fetch(requestUrl, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model,
            input: 'Reply with OK.',
            max_output_tokens: 8,
            temperature: 0,
        }),
        signal,
    });
}
async function testAnthropicMessagesConnection(requestUrl, apiKey, model, signal) {
    return fetch(requestUrl, {
        method: 'POST',
        headers: {
            'x-api-key': apiKey,
            'anthropic-version': '2023-06-01',
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            model,
            messages: [{ role: 'user', content: 'Reply with OK.' }],
            max_tokens: 8,
            temperature: 0,
        }),
        signal,
    });
}
async function testGeminiGenerateContentConnection(requestUrl, apiKey, signal) {
    return fetch(requestUrl, {
        method: 'POST',
        headers: {
            'x-goog-api-key': apiKey,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            contents: [{ parts: [{ text: 'Reply with OK.' }] }],
            generationConfig: {
                temperature: 0,
                maxOutputTokens: 8,
            },
        }),
        signal,
    });
}
function inferProvider(apiBase) {
    if (apiBase?.includes('anthropic.com'))
        return 'anthropic-messages';
    if (apiBase?.includes('generativelanguage.googleapis.com'))
        return 'gemini-generate-content';
    return 'openai-chat-completions';
}
function getConnectionTestUrl(provider, apiBase, model) {
    switch (provider) {
        case 'anthropic-messages':
            return getAnthropicMessagesUrl(apiBase);
        case 'openai-chat-completions':
        case 'openai-chat-completions-to-anthropic-messages':
            return joinUrl(apiBase, '/chat/completions');
        case 'openai-responses':
        case 'openai-responses-to-anthropic-messages':
            return joinUrl(apiBase, '/responses');
        case 'gemini-generate-content':
            return joinUrl(apiBase, `/models/${encodeURIComponent(model)}:generateContent`);
    }
}
function joinUrl(base, path) {
    return `${base.replace(/\/+$/, '')}${path}`;
}
function getAnthropicMessagesUrl(apiBase) {
    try {
        const url = new URL(apiBase);
        if (url.hostname === 'api.anthropic.com' && !url.pathname.endsWith('/messages')) {
            return joinUrl(apiBase, '/messages');
        }
    }
    catch {
        // Fall through to using the user-provided value so the debug output exposes the bad URL.
    }
    return apiBase;
}
function maskApiKey(apiKey) {
    if (!apiKey)
        return undefined;
    if (apiKey.length <= 8)
        return `${apiKey.slice(0, 2)}***`;
    return `${apiKey.slice(0, 4)}...${apiKey.slice(-4)}`;
}
async function readErrorMessage(response) {
    const body = await response.text();
    if (!body) {
        return {
            message: `Connection test failed with status ${response.status}`,
            body: '',
        };
    }
    try {
        const json = JSON.parse(body);
        const message = typeof json.error === 'string'
            ? json.error
            : json.error?.message || json.message || body;
        return { message, body: body.slice(0, 2000) };
    }
    catch {
        return { message: body, body: body.slice(0, 2000) };
    }
}
export function createPreset(_workspaceId, data) {
    const id = uuid();
    const workingDir = data.workingDir?.trim();
    const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
        ? data.runtimeKind
        : 'open-agent-sdk';
    const requestedModelProvider = normalizeModelProvider(data.modelProvider);
    const presetRuntimeKind = isAnthropicBridgeProvider(requestedModelProvider) ? 'claude-code' : runtimeKind;
    const preset = {
        id,
        name: data.name?.trim() || 'New Agent',
        role: isValidRole(data.role) ? data.role.trim() : DEFAULT_AGENT_ROLE,
        description: data.description || '',
        runtimeKind: presetRuntimeKind,
        modelProvider: requestedModelProvider,
        providerId: resolveProviderId(data),
        modelId: data.modelId || 'claude-sonnet-4-6',
        workingDir: workingDir || '',
        mcps: normalizeMcpConfig(data.mcps),
        skills: normalizeSkillNames(data.skills),
        tools: normalizeToolNames(data.tools ?? BUILT_IN_AGENT_TOOLS.map((tool) => tool.name)),
        systemPrompt: data.systemPrompt || '',
        outputStyle: data.outputStyle,
        suggestions: data.suggestions,
        temperature: data.temperature ?? 0.3,
        maxTokens: data.maxTokens ?? 4096,
        avatarUrl: data.avatarUrl,
        icon: data.icon,
        sandboxDirs: data.sandboxDirs,
        maxRetries: data.maxRetries,
        templateId: data.templateId,
        enabled: data.enabled ?? true,
    };
    writeAgentTemplate(preset, data.skills);
    return hydrateAgentProvider(preset);
}
export function updatePreset(_workspaceId, presetId, data) {
    const existing = readAgentTemplate(presetId);
    if (!existing)
        return null;
    const role = isValidRole(data.role) ? data.role.trim() : existing.role;
    const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
        ? data.runtimeKind
        : existing.runtimeKind || 'open-agent-sdk';
    const requestedModelProvider = normalizeModelProvider(data.modelProvider);
    const updatedRuntimeKind = isAnthropicBridgeProvider(requestedModelProvider) ? 'claude-code' : runtimeKind;
    const updated = {
        ...existing,
        ...data,
        id: existing.id,
        role,
        runtimeKind: updatedRuntimeKind,
        name: data.name?.trim() || existing.name || 'New Agent',
        modelProvider: requestedModelProvider,
        providerId: resolveProviderId(data) || existing.providerId,
        mcps: normalizeMcpConfig(data.mcps),
        skills: normalizeSkillNames(data.skills),
        tools: normalizeToolNames(data.tools ?? existing.tools),
        enabled: data.enabled ?? existing.enabled ?? true,
    };
    writeAgentTemplate(updated, data.skills);
    return hydrateAgentProvider(updated);
}
export function getAllowedTools(mcps) {
    const servers = getMcpServers(mcps);
    return servers ? Object.keys(servers) : undefined;
}
export function getMcpServers(mcps) {
    if (!mcps)
        return undefined;
    const servers = mcps.mcpServers;
    if (!servers || typeof servers !== 'object' || Array.isArray(servers))
        return undefined;
    const resolved = Object.fromEntries(Object.entries(servers).flatMap(([name, server]) => {
        const config = getRunnableMcpServerConfig(server) ?? readStoredMcpServerConfig(name);
        return config ? [[name, config]] : [];
    }));
    return Object.keys(resolved).length > 0 ? resolved : undefined;
}
export function getAgentConfigDir(workspaceId, preset) {
    const ws = getWorkspace(workspaceId);
    if (!ws)
        return undefined;
    const workspaceAgentDir = getWorkspaceAgentDir(ws.agentspaceDir, preset.id);
    ensureWorkspaceAgentCopy(preset, ws.agentspaceDir);
    return workspaceAgentDir;
}
export function syncTemplatesToAllWorkspaces() {
    const templates = listTemplates().filter((template) => existsSync(getGlobalAgentTemplateDir(template.id)));
    const workspaces = listWorkspaces();
    let agents = 0;
    for (const workspace of workspaces) {
        if (!workspace.agentspaceDir)
            continue;
        for (const template of templates) {
            writeWorkspaceAgentCopy(template, workspace.agentspaceDir);
            agents++;
        }
    }
    return { workspaces: workspaces.length, agents };
}
export function getAvailableSkillNames(agentDir, skills) {
    if (!agentDir || !Array.isArray(skills))
        return [];
    return skills
        .map((skill) => skill.trim().replace(/\.md$/i, ''))
        .filter((skill) => {
        if (!skill)
            return false;
        const skillsBase = join(agentDir, 'skills');
        if (hasReadableSkillSource(skillsBase, skill))
            return true;
        const globalSkillFile = join(getDataDir(), 'skills', skill, 'SKILL.md');
        if (existsSync(globalSkillFile) && statSync(globalSkillFile).size > 0)
            return true;
        const globalLegacySkillFile = join(getDataDir(), 'skills', `${skill}.md`);
        return existsSync(globalLegacySkillFile) && statSync(globalLegacySkillFile).size > 0;
    });
}
export function resolveWorkingDir(workspaceId, preset) {
    const ws = getWorkspace(workspaceId);
    const mappedWorkingDir = resolveWorkspacePath(ws?.boundDirs, preset.workingDir);
    if (mappedWorkingDir)
        return mappedWorkingDir;
    const userDir = homedir();
    if (!ws)
        return userDir;
    return ws.boundDirs[0] || userDir;
}
function resolveWorkspacePath(boundDirs, workingDir) {
    const raw = workingDir?.trim();
    if (!raw)
        return undefined;
    if (raw === '/workspace' || raw.startsWith('/workspace/')) {
        const root = boundDirs?.[0];
        if (!root)
            return undefined;
        const suffix = raw.slice('/workspace'.length).replace(/^\/+/, '');
        const mapped = suffix ? join(root, suffix) : root;
        return existsSync(mapped) ? mapped : root;
    }
    if (!isAbsolute(raw)) {
        const root = boundDirs?.[0];
        if (!root)
            return undefined;
        const mapped = join(root, raw);
        return existsSync(mapped) ? mapped : undefined;
    }
    if (existsSync(raw))
        return raw;
    const root = boundDirs?.[0];
    if (!root)
        return undefined;
    const rootRelative = relative(normalize(root), normalize(raw));
    if (!rootRelative.startsWith('..') && rootRelative !== '')
        return root;
    return undefined;
}
function getGlobalAgentTemplateDir(agentId) {
    return join(getGlobalAgentTemplatesDir(), agentId);
}
function getGlobalAgentTemplatesDir() {
    return join(getDataDir(), 'agent-templates');
}
function getWorkspaceAgentDir(agentspaceDir, agentId) {
    return join(agentspaceDir, 'agents', agentId);
}
function normalizeMcpConfig(mcps) {
    if (!mcps)
        return {};
    return mcps;
}
function getRunnableMcpServerConfig(server) {
    if (!server || typeof server !== 'object' || Array.isArray(server))
        return null;
    const record = server;
    if (typeof record.command === 'string' && record.command.trim())
        return record;
    if (typeof record.url === 'string' && record.url.trim())
        return record;
    return null;
}
function readStoredMcpServerConfig(name) {
    const filePath = join(getDataDir(), 'mcps', `${sanitizeMcpName(name)}.json`);
    if (!existsSync(filePath))
        return null;
    try {
        const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
        return getRunnableMcpServerConfig(parsed.config);
    }
    catch {
        return null;
    }
}
function sanitizeMcpName(name) {
    return name.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'mcp-server';
}
function normalizeModelProvider(provider) {
    return provider || undefined;
}
function isAnthropicBridgeProvider(provider) {
    return Boolean(provider && ANTHROPIC_BRIDGE_PROVIDERS.includes(provider));
}
function normalizeSkillNames(skills) {
    if (!Array.isArray(skills))
        return [];
    return skills
        .map((skill) => typeof skill === 'string' ? skill : skill.name)
        .filter((name) => Boolean(name?.trim()))
        .map((name) => name.trim());
}
function normalizeToolNames(tools) {
    if (!Array.isArray(tools))
        return [];
    return tools.filter((name) => VALID_TOOL_NAMES.has(name));
}
function resolveProviderConfig(data) {
    const providers = listProviders();
    if (data.providerId) {
        const byId = providers.find((provider) => provider.id === data.providerId);
        if (byId)
            return byId;
    }
    const apiBase = data.apiBase?.trim();
    const apiKey = data.apiKey?.trim();
    if (apiBase || apiKey) {
        return providers.find((provider) => (!apiBase || provider.apiBase === apiBase)
            && (!apiKey || provider.apiKey === apiKey));
    }
    return undefined;
}
function resolveProviderId(data) {
    return data.providerId || resolveProviderConfig(data)?.id;
}
function hydrateAgentProvider(preset) {
    const provider = resolveProviderConfig(preset);
    if (!provider)
        return preset;
    return {
        ...preset,
        providerId: provider.id,
        apiBase: provider.apiBase,
        apiKey: provider.apiKey,
    };
}
function toStoredAgentTemplate(preset) {
    const { apiBase: _apiBase, apiKey: _apiKey, provider: _provider, baseURL: _baseURL, model: _model, ...stored } = preset;
    void _apiBase;
    void _apiKey;
    void _provider;
    void _baseURL;
    void _model;
    return stored;
}
function writeAgentTemplate(preset, skillInputs) {
    const dir = getGlobalAgentTemplateDir(preset.id);
    const skillsDir = join(dir, 'skills');
    ensureDir(skillsDir);
    const storedPreset = toStoredAgentTemplate(preset);
    writeFileSync(join(dir, 'agent.json'), JSON.stringify(storedPreset, null, 2), 'utf-8');
    writeFileSync(join(dir, 'mcp.json'), JSON.stringify(storedPreset.mcps ?? {}, null, 2), 'utf-8');
    const hasObjectSkills = skillInputs?.some((skill) => typeof skill !== 'string');
    console.log('[writeAgentTemplate]', {
        agentId: preset.id,
        skillsCount: preset.skills?.length ?? 0,
        skills: preset.skills,
        hasObjectSkills,
        skillInputTypes: skillInputs?.map((s) => typeof s),
    });
    if (hasObjectSkills) {
        rmSync(skillsDir, { recursive: true, force: true });
        ensureDir(skillsDir);
        for (const skill of skillInputs) {
            if (typeof skill === 'string' || !skill.name?.trim())
                continue;
            const filename = sanitizeMarkdownFilename(skill.name);
            writeFileSync(join(skillsDir, filename), skill.content ?? '', 'utf-8');
        }
    }
    else if (preset.skills?.length) {
        const globalSkillsDir = join(getDataDir(), 'skills');
        const keepFiles = new Set(preset.skills.map((s) => s.endsWith('.md') ? s : `${s}.md`));
        const keepNames = new Set(preset.skills.map((s) => s.replace(/\.md$/i, '')));
        // Remove skill entries no longer in the list (flat .md files and folders)
        if (existsSync(skillsDir)) {
            for (const existing of readdirSync(skillsDir)) {
                const name = existing.endsWith('.md') ? existing.replace(/\.md$/i, '') : existing;
                if (!keepNames.has(name)) {
                    rmSync(join(skillsDir, existing), { recursive: true, force: true });
                    console.log('[writeAgentTemplate] removed stale skill:', existing);
                }
            }
        }
        // Copy / ensure skill files (global skills are folders: skills/{name}/SKILL.md)
        for (const skillName of keepNames) {
            const globalFolder = join(globalSkillsDir, skillName);
            const flatTarget = join(skillsDir, `${skillName}.md`);
            const folderTarget = join(skillsDir, skillName);
            console.log('[writeAgentTemplate] skill:', skillName, 'globalFolder:', globalFolder, 'globalExists:', existsSync(globalFolder) && statSync(globalFolder).isDirectory());
            if (existsSync(globalFolder) && statSync(globalFolder).isDirectory()) {
                // Copy entire skill folder
                if (existsSync(folderTarget)) {
                    rmSync(folderTarget, { recursive: true, force: true });
                }
                cpSync(globalFolder, folderTarget, { recursive: true, force: true });
                // Remove stale flat .md if it exists
                if (existsSync(flatTarget)) {
                    rmSync(flatTarget, { force: true });
                }
            }
            else {
                // Fallback: single SKILL.md or legacy flat file
                const globalSkillFile = join(globalFolder, 'SKILL.md');
                if (existsSync(globalSkillFile)) {
                    copyFileSync(globalSkillFile, flatTarget);
                }
                else if (!existsSync(flatTarget) && !existsSync(folderTarget)) {
                    writeFileSync(flatTarget, '', 'utf-8');
                }
            }
        }
    }
    else {
        // No skills — clean skills dir
        if (existsSync(skillsDir)) {
            for (const existing of readdirSync(skillsDir)) {
                if (existing.endsWith('.md')) {
                    rmSync(join(skillsDir, existing), { force: true });
                }
            }
        }
    }
}
function copyAgentTemplateToWorkspace(agentId, agentspaceDir) {
    const sourceDir = getGlobalAgentTemplateDir(agentId);
    const targetDir = getWorkspaceAgentDir(agentspaceDir, agentId);
    ensureDir(targetDir);
    if (!existsSync(sourceDir))
        return;
    cpSync(sourceDir, targetDir, { recursive: true, force: true });
    const skillsDir = join(targetDir, 'skills');
    if (existsSync(skillsDir)) {
        const workspaceSkillsDir = join(agentspaceDir, 'skills');
        ensureDir(workspaceSkillsDir);
        for (const entry of readdirSync(skillsDir)) {
            const source = join(skillsDir, entry);
            const sourceStat = statSync(source);
            if (sourceStat.isDirectory()) {
                cpSync(source, join(workspaceSkillsDir, entry), { recursive: true, force: true });
            }
            else if (sourceStat.isFile() && extname(entry).toLowerCase() === '.md') {
                copyFileSync(source, join(workspaceSkillsDir, entry));
            }
        }
    }
}
function writeWorkspaceAgentCopy(preset, agentspaceDir) {
    copyAgentTemplateToWorkspace(preset.id, agentspaceDir);
    const workspaceAgentDir = getWorkspaceAgentDir(agentspaceDir, preset.id);
    const workspacePreset = {
        ...preset,
        workingDir: workspaceAgentDir,
    };
    ensureDir(workspaceAgentDir);
    writeFileSync(join(workspaceAgentDir, 'agent.json'), JSON.stringify(toStoredAgentTemplate(workspacePreset), null, 2), 'utf-8');
}
function ensureWorkspaceAgentCopy(preset, agentspaceDir) {
    const workspaceAgentDir = getWorkspaceAgentDir(agentspaceDir, preset.id);
    const requiredFiles = ['agent.json', 'mcp.json'];
    const sourceDir = getGlobalAgentTemplateDir(preset.id);
    const missingRequiredFile = requiredFiles.some((file) => !existsSync(join(workspaceAgentDir, file)));
    const missingBuiltInDir = CLAUDE_BUILT_IN_DIRS.some((dir) => {
        const sourceBuiltInDir = join(sourceDir, dir);
        const targetBuiltInDir = join(workspaceAgentDir, dir);
        return existsSync(sourceBuiltInDir)
            && statSync(sourceBuiltInDir).isDirectory()
            && countFiles(sourceBuiltInDir) > countFiles(targetBuiltInDir);
    });
    const missingSkill = (preset.skills ?? []).some((skill) => {
        const skillName = skill.replace(/\.md$/i, '');
        const targetSkillsDir = join(workspaceAgentDir, 'skills');
        return !hasReadableSkillSource(targetSkillsDir, skillName);
    });
    if (!missingRequiredFile && !missingBuiltInDir && !missingSkill)
        return;
    writeWorkspaceAgentCopy(preset, agentspaceDir);
}
function hasReadableSkillSource(skillsDir, skillName) {
    const skillDir = join(skillsDir, skillName);
    const skillFile = join(skillDir, 'SKILL.md');
    if (existsSync(skillDir) && statSync(skillDir).isDirectory()) {
        return existsSync(skillFile) && statSync(skillFile).size > 0;
    }
    const legacySkillFile = join(skillsDir, `${skillName}.md`);
    return existsSync(legacySkillFile) && statSync(legacySkillFile).size > 0;
}
function countFiles(dir) {
    if (!existsSync(dir) || !statSync(dir).isDirectory())
        return 0;
    return readdirSync(dir, { withFileTypes: true }).reduce((count, entry) => {
        const fullPath = join(dir, entry.name);
        if (entry.isDirectory())
            return count + countFiles(fullPath);
        return entry.isFile() ? count + 1 : count;
    }, 0);
}
export function readAgentTemplate(agentId) {
    const filePath = join(getGlobalAgentTemplateDir(agentId), 'agent.json');
    if (!existsSync(filePath)) {
        return getDefaultBuiltInAgentPreset(agentId);
    }
    try {
        return hydrateAgentProvider(JSON.parse(readFileSync(filePath, 'utf-8')));
    }
    catch {
        return getDefaultBuiltInAgentPreset(agentId);
    }
}
function getDefaultBuiltInAgentPresets() {
    return [
        getDefaultAgentGeneratorPreset(),
        getDefaultCommitAgentPreset(),
        getDefaultTitleGeneratorPreset(),
    ];
}
function getDefaultBuiltInAgentPreset(agentId) {
    if (agentId === AGENT_GENERATOR_PRESET_ID)
        return getDefaultAgentGeneratorPreset();
    if (agentId === AGENT_COMMIT_PRESET_ID)
        return getDefaultCommitAgentPreset();
    if (agentId === AGENT_TITLE_GENERATOR_PRESET_ID)
        return getDefaultTitleGeneratorPreset();
    return null;
}
function withBuiltInTemplatesFirst(templates) {
    const byId = new Map(templates.map((template) => [template.id, template]));
    const builtIns = getDefaultBuiltInAgentPresets().map((preset) => byId.get(preset.id) ?? preset);
    const custom = templates.filter((template) => !BUILT_IN_AGENT_IDS.includes(template.id));
    return [...builtIns, ...custom];
}
function resolveGlobalDefaultModel() {
    const providers = listProviders();
    if (providers.length === 0)
        return null;
    const models = listModels();
    for (const model of models) {
        const provider = providers.find((entry) => entry.name === model.provider);
        if (provider?.apiBase && provider?.apiKey && model.modelId) {
            return {
                modelProvider: inferBuiltInModelProvider(provider.apiBase),
                providerId: provider.id,
                modelId: model.modelId,
                apiBase: provider.apiBase,
                apiKey: provider.apiKey,
            };
        }
    }
    return null;
}
function inferBuiltInModelProvider(apiBase) {
    if (apiBase.includes('anthropic'))
        return 'anthropic-messages';
    if (apiBase.includes('generativelanguage.googleapis.com'))
        return 'gemini-generate-content';
    return 'openai-chat-completions';
}
// Seed built-in presets to disk on startup so agent-generator/commit-agent/title-generator are
// first-class members of the agents list. Presets missing model settings inherit the first
// fully-configured global provider+model, so agent generation works out of the box.
export function ensureBuiltInAgentTemplates() {
    const defaultModel = resolveGlobalDefaultModel();
    const seeded = [];
    for (const preset of getDefaultBuiltInAgentPresets()) {
        const dir = getGlobalAgentTemplateDir(preset.id);
        if (existsSync(join(dir, 'agent.json')))
            continue;
        const resolved = defaultModel && !(preset.apiBase && preset.apiKey && preset.modelId)
            ? { ...preset, ...defaultModel }
            : preset;
        ensureDir(dir);
        writeFileSync(join(dir, 'agent.json'), JSON.stringify(toStoredAgentTemplate(resolved), null, 2), 'utf-8');
        if (!existsSync(join(dir, 'mcp.json'))) {
            writeFileSync(join(dir, 'mcp.json'), JSON.stringify(resolved.mcps ?? {}, null, 2), 'utf-8');
        }
        seeded.push(preset.id);
    }
    if (seeded.length > 0) {
        console.info('[agent] seeded built-in agent templates', {
            seeded,
            defaultModel: defaultModel ? { providerId: defaultModel.providerId, modelId: defaultModel.modelId, apiBase: defaultModel.apiBase } : null,
        });
    }
    return { seeded, defaultModel };
}
function getDefaultAgentGeneratorPreset() {
    return {
        id: AGENT_GENERATOR_PRESET_ID,
        name: 'Agent Generator',
        role: 'agent',
        description: '根据提示词生成其他 Agent 的名称、描述和 Markdown 系统提示。',
        runtimeKind: 'claude-code',
        modelProvider: undefined,
        modelId: '',
        apiBase: '',
        apiKey: '',
        workingDir: '',
        mcps: {},
        skills: [],
        tools: [],
        systemPrompt: [
            '# Role',
            'You are an Agent Spaces agent designer.',
            '',
            '# Responsibilities',
            '- Convert user requirements into production-ready Agent preset metadata.',
            '- Return only valid JSON with name, description, and systemPrompt.',
            '- Write the systemPrompt in clear Markdown with role, responsibilities, workflow, constraints, and output expectations.',
            '',
            '# Constraints',
            '- Do not include Markdown code fences around JSON.',
            '- Keep names concise and descriptions practical.',
        ].join('\n'),
        temperature: 0.2,
        maxTokens: 4096,
        enabled: true,
    };
}
export function getDefaultCommitAgentPreset() {
    return {
        id: AGENT_COMMIT_PRESET_ID,
        name: 'Commit Agent',
        role: 'commit',
        description: '根据 git diff 自动生成 conventional commit message。',
        runtimeKind: 'claude-code',
        modelProvider: undefined,
        modelId: '',
        apiBase: '',
        apiKey: '',
        workingDir: '',
        mcps: {},
        skills: [],
        tools: [],
        systemPrompt: [
            'You are a git commit message generator.',
            'Return exactly one concise commit message for the provided git diff.',
            'Use conventional format: type: description.',
            'Allowed types: feat, fix, docs, style, refactor, perf, test, chore.',
            'Keep the subject under 72 characters.',
            'If a body is needed, add one blank line and at most 3 short bullet lines.',
            'Do not greet, explain, ask questions, provide options, use markdown, or wrap in code fences.',
            'Output only the final commit message text.',
        ].join(' '),
        temperature: 0,
        maxTokens: 512,
        enabled: true,
    };
}
export function getDefaultTitleGeneratorPreset() {
    return {
        id: AGENT_TITLE_GENERATOR_PRESET_ID,
        name: 'Title Generator',
        role: 'agent',
        description: 'Generate short titles for channels and issues from user requirements.',
        runtimeKind: 'claude-code',
        modelProvider: undefined,
        modelId: '',
        apiBase: '',
        apiKey: '',
        workingDir: '',
        mcps: {},
        skills: [],
        tools: [],
        systemPrompt: [
            'You generate objective scene titles for Agent Spaces channels and issues.',
            'Return exactly one title and nothing else.',
            'The title must describe the scenario, task, or intent objectively.',
            'Do not include a speaking subject, assistant identity, user identity, first-person wording, second-person wording, greetings, replies, questions, or conversational text.',
            'For Chinese, prefer a short noun phrase ending with 场景, 任务, 问题, 需求, 讨论, or 分析 when appropriate.',
            'Examples: user says "你好" -> "打招呼场景"; user says "帮我修复登录报错" -> "登录报错修复"; user says "分析这段代码" -> "代码分析任务".',
            'Use the same language as the user request when it is clear.',
            'Keep the title under 20 Chinese characters or under 8 English words.',
            'Do not use quotes, markdown, punctuation-only endings, prefixes, explanations, or multiple options.',
        ].join(' '),
        temperature: 0.2,
        maxTokens: 64,
        enabled: true,
    };
}
function sanitizeMarkdownFilename(name) {
    const raw = basename(name).replace(/\.md$/i, '');
    const safe = raw.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'skill';
    return `${safe}.md`;
}
export function deletePreset(workspaceId, presetId) {
    if (BUILT_IN_AGENT_IDS.includes(presetId))
        return false;
    const ws = getWorkspace(workspaceId);
    if (!ws)
        return null;
    const template = readAgentTemplate(presetId);
    if (!template)
        return false;
    // Delete global template
    const templateDir = getGlobalAgentTemplateDir(presetId);
    if (existsSync(templateDir))
        rmSync(templateDir, { recursive: true, force: true });
    // Remove agent from all channels' members
    for (const ch of listChannels(workspaceId)) {
        if (ch.members.includes(presetId)) {
            updateChannel(workspaceId, ch.id, { members: ch.members.filter((m) => m !== presetId) });
        }
    }
    // Remove agent from all issues' members
    for (const issue of listIssues(workspaceId)) {
        const membersChanged = issue.members.includes(presetId);
        if (membersChanged) {
            updateIssue({
                ...issue,
                members: issue.members.filter((m) => m !== presetId),
            });
        }
    }
    return true;
}
export function createGlobalPreset(data) {
    const id = uuid();
    const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
        ? data.runtimeKind
        : 'open-agent-sdk';
    const requestedModelProvider = normalizeModelProvider(data.modelProvider);
    const presetRuntimeKind = isAnthropicBridgeProvider(requestedModelProvider) ? 'claude-code' : runtimeKind;
    const preset = {
        id,
        name: data.name?.trim() || 'New Agent',
        role: isValidRole(data.role) ? data.role.trim() : DEFAULT_AGENT_ROLE,
        description: data.description || '',
        runtimeKind: presetRuntimeKind,
        modelProvider: requestedModelProvider,
        providerId: resolveProviderId(data),
        modelId: data.modelId || 'claude-sonnet-4-6',
        workingDir: '',
        mcps: normalizeMcpConfig(data.mcps),
        skills: normalizeSkillNames(data.skills),
        tools: normalizeToolNames(data.tools ?? BUILT_IN_AGENT_TOOLS.map((tool) => tool.name)),
        systemPrompt: data.systemPrompt || '',
        temperature: data.temperature ?? 0.3,
        maxTokens: data.maxTokens ?? 4096,
        sandboxDirs: data.sandboxDirs,
        maxRetries: data.maxRetries,
        templateId: data.templateId,
        enabled: data.enabled ?? true,
    };
    writeAgentTemplate(preset, data.skills);
    return hydrateAgentProvider(preset);
}
export function updateGlobalPreset(presetId, data) {
    const existing = readAgentTemplate(presetId);
    if (!existing)
        return null;
    const role = isValidRole(data.role) ? data.role.trim() : existing.role;
    const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
        ? data.runtimeKind
        : existing.runtimeKind || 'open-agent-sdk';
    const requestedModelProvider = normalizeModelProvider(data.modelProvider);
    const updatedRuntimeKind = isAnthropicBridgeProvider(requestedModelProvider) ? 'claude-code' : runtimeKind;
    const updated = {
        ...existing,
        ...data,
        id: existing.id,
        role,
        runtimeKind: updatedRuntimeKind,
        name: data.name?.trim() || existing.name || 'New Agent',
        modelProvider: requestedModelProvider,
        providerId: resolveProviderId(data) || existing.providerId,
        mcps: normalizeMcpConfig(data.mcps),
        skills: normalizeSkillNames(data.skills),
        tools: normalizeToolNames(data.tools ?? existing.tools),
        enabled: data.enabled ?? existing.enabled ?? true,
    };
    writeAgentTemplate(updated, data.skills);
    return hydrateAgentProvider(updated);
}
export function deleteGlobalPreset(presetId) {
    if (BUILT_IN_AGENT_IDS.includes(presetId))
        return false;
    const templateDir = getGlobalAgentTemplateDir(presetId);
    if (!existsSync(templateDir))
        return false;
    rmSync(templateDir, { recursive: true, force: true });
    return true;
}
export function listGlobalPresets() {
    return listTemplates();
}
export function list(workspaceId) {
    return listAgentSessions(workspaceId);
}
export function getById(workspaceId, sessionId) {
    return getAgentSession(workspaceId, sessionId);
}
export function create(workspaceId, role, configId) {
    const now = new Date().toISOString();
    const session = {
        id: uuid(),
        workspaceId,
        agentConfigId: configId || uuid(),
        role,
        status: 'idle',
        startedAt: now,
        lastActivityAt: now,
    };
    createAgentSession(session);
    return session;
}
export function getOrCreateSessionForConfig(workspaceId, config) {
    const existing = listAgentSessions(workspaceId).find((session) => session.agentConfigId === config.id &&
        session.role === config.role &&
        (session.status === 'idle' || session.status === 'completed' || session.status === 'crashed'));
    if (!existing)
        return create(workspaceId, config.role, config.id);
    return updateStatus(workspaceId, existing.id, 'idle', {
        currentTaskId: undefined,
        error: undefined,
    }) ?? existing;
}
export function updateStatus(workspaceId, sessionId, status, extra) {
    const session = getAgentSession(workspaceId, sessionId);
    if (!session)
        return null;
    session.status = status;
    session.lastActivityAt = new Date().toISOString();
    if (extra)
        Object.assign(session, extra);
    updateAgentSession(session);
    return session;
}
export function assignTask(workspaceId, sessionId, taskId) {
    return updateStatus(workspaceId, sessionId, 'active', { currentTaskId: taskId });
}
export function complete(workspaceId, sessionId, error, details) {
    const session = updateStatus(workspaceId, sessionId, error ? 'crashed' : 'completed', {
        currentTaskId: undefined,
        error,
    });
    if (!session)
        return null;
    const usage = details?.usage ?? extractUsageFromOutput(details?.output ?? []);
    if (usage.totalTokens || usage.inputTokens || usage.outputTokens || usage.cachedInputTokens || usage.reasoningTokens) {
        recordAgentUsage({
            session,
            runtime: details?.runtime,
            model: details?.model,
            summary: details?.summary,
            durationMs: details?.durationMs,
            usage,
            costUsd: details?.costUsd,
        });
    }
    return session;
}
export function remove(workspaceId, sessionId) {
    const session = getAgentSession(workspaceId, sessionId);
    if (!session)
        return false;
    deleteAgentSession(workspaceId, sessionId);
    return true;
}
export function findActiveByRole(workspaceId, role) {
    return listAgentSessions(workspaceId).find((s) => s.role === role && (s.status === 'active' || s.status === 'idle'));
}
export function usageDashboard(days) {
    return getAgentUsageDashboard(days);
}
//# sourceMappingURL=agent.js.map