import { mkdir } from "node:fs/promises";
import path from "node:path";
import { LAB_REPRESENTATIVE_AGENT } from "../../agents/identity.js";
import { ensureDir, nowIso, readJsonFile, withFileLock, writeJsonFile } from "../../shared/index.js";
import { agentThreadInputFromTeamMember, agentThreadStatusFromMemberStatus, getAgentThread, upsertAgentThread, patchAgentThread, writeAgentThreadToolManifest } from "../agent-thread/index.js";
import { runDir } from "../run-state/index.js";
export function teamRoot(store, runId) {
    return path.join(runDir(store, runId), "team");
}
export function teamRuntimePath(store, runId) {
    return path.join(teamRoot(store, runId), "runtime.json");
}
export function teamInboxRoot(store, runId) {
    return path.join(teamRoot(store, runId), "inbox");
}
export function teamTasksPath(store, runId) {
    return path.join(teamRoot(store, runId), "tasks.json");
}
export function teamCleanupPath(store, runId) {
    return path.join(teamRoot(store, runId), "cleanup.json");
}
function defaultMembers(store) {
    const timestamp = nowIso();
    const members = store.config.agent_order.filter((name) => !store.config.disabled_agents.includes(name));
    return members.map((name) => ({
        name,
        thread_id: name,
        role: name === store.config.default_run_agent ? "lead" : "member",
        status: name === store.config.default_run_agent ? "active" : "idle",
        joined_at: timestamp,
        helper_counts: { running: 0, completed: 0, failed: 0 },
    }));
}
export async function ensureTeamRuntime(store, runId) {
    const existing = await readJsonFile(teamRuntimePath(store, runId));
    if (existing)
        return existing;
    const timestamp = nowIso();
    const runtime = {
        version: 1,
        run_id: runId,
        status: "active",
        created_at: timestamp,
        updated_at: timestamp,
        members: defaultMembers(store),
    };
    await ensureDir(teamRoot(store, runId));
    await mkdir(teamInboxRoot(store, runId), { recursive: true });
    await writeJsonFile(teamRuntimePath(store, runId), runtime);
    await writeJsonFile(teamTasksPath(store, runId), []);
    await upsertTeamRuntimeAgentThreads(store, runId, runtime);
    return runtime;
}
export async function createDynamicTeamRuntime(store, runId, input) {
    const existing = await loadTeamRuntime(store, runId);
    if (existing && (existing.status === "creating" || existing.status === "active" || existing.status === "failed") && !isReplaceableDefaultTeamRuntime(existing)) {
        throw new Error(`team runtime already exists for run ${runId}: ${existing.status}`);
    }
    const timestamp = nowIso();
    const lead = {
        name: input.spec.lead_owner || LAB_REPRESENTATIVE_AGENT,
        role: "lead",
        status: "active",
        joined_at: timestamp,
        last_seen_at: timestamp,
        resolved_agent: input.spec.lead_owner || LAB_REPRESENTATIVE_AGENT,
        opencode_agent: LAB_REPRESENTATIVE_AGENT,
        parent_session_id: input.parentSessionId,
        session_id: input.parentSessionId,
        thread_id: input.spec.lead_owner || LAB_REPRESENTATIVE_AGENT,
        launch_status: "queued",
        helper_counts: { running: 0, completed: 0, failed: 0 },
    };
    const members = input.spec.members.map((member) => ({
        name: member.role_id,
        thread_id: member.role_id,
        parent_thread_id: input.spec.lead_owner || LAB_REPRESENTATIVE_AGENT,
        role: "member",
        status: "launching",
        joined_at: timestamp,
        role_label: member.role_label,
        capability_profile: member.capability_profile,
        resolved_agent: LAB_REPRESENTATIVE_AGENT,
        opencode_agent: LAB_REPRESENTATIVE_AGENT,
        parent_session_id: input.parentSessionId,
        coverage: member.coverage,
        mission_prompt: member.mission_prompt,
        prompt_budget: member.prompt_budget,
        model_route: member.model_route,
        spawn_policy: member.spawn_policy,
        launch_policy: member.launch_policy,
        delivery_policy: member.delivery_policy,
        evidence_contract: member.evidence_contract,
        authoritative_inputs: member.authoritative_inputs,
        expected_state_version: member.expected_state_version,
        read_paths: member.read_paths,
        write_paths: member.write_paths,
        evidence_paths: member.evidence_paths,
        artifact_paths: member.artifact_paths,
        expected_outputs: member.expected_outputs,
        stop_condition: member.stop_condition,
        dependencies: member.dependencies,
        artifact_inputs: member.artifact_inputs,
        evidence_inputs: member.evidence_inputs,
        verifier_obligations: member.verifier_obligations,
        oracle_scope: member.oracle_scope,
        semantic_conflict_policy: member.semantic_conflict_policy,
        human_in_the_loop: member.human_in_the_loop,
        tool_policy: member.tool_policy,
        mcp_policy: member.mcp_policy,
        skill_policy: member.skill_policy,
        pending_message_ids: [],
        helper_counts: { running: 0, completed: 0, failed: 0 },
        launch_status: "pending",
    }));
    const runtime = {
        version: 1,
        run_id: runId,
        team_id: input.spec.team_id,
        team_name: input.spec.team_name,
        spec_ref: input.specRef,
        spec_markdown_ref: input.specMarkdownRef,
        status: "creating",
        created_at: timestamp,
        updated_at: timestamp,
        members: [lead, ...members],
    };
    await ensureDir(teamRoot(store, runId));
    await mkdir(teamInboxRoot(store, runId), { recursive: true });
    await Promise.all(runtime.members.map((member) => mkdir(path.join(teamInboxRoot(store, runId), member.name), { recursive: true })));
    await writeJsonFile(teamRuntimePath(store, runId), runtime);
    await writeJsonFile(teamTasksPath(store, runId), []);
    await upsertTeamRuntimeAgentThreads(store, runId, runtime);
    return runtime;
}
export function isReplaceableDefaultTeamRuntime(runtime) {
    if (runtime.team_id || runtime.spec_ref || runtime.spec_markdown_ref)
        return false;
    if (runtime.status !== "active")
        return false;
    return runtime.members.every((member) => (!member.session_id || member.role === "lead") &&
        !member.background_id &&
        !member.agent_call_id &&
        !member.prompt_ref &&
        member.launch_status === undefined);
}
export async function loadTeamRuntime(store, runId) {
    return readJsonFile(teamRuntimePath(store, runId));
}
export async function resumeTeamRuntime(store, runId) {
    const runtime = await ensureTeamRuntime(store, runId);
    if (runtime.status === "active")
        return runtime;
    const timestamp = nowIso();
    const resumed = {
        ...runtime,
        status: "active",
        updated_at: timestamp,
        members: runtime.members.map((member) => ({
            ...member,
            status: member.status === "done" ? "idle" : member.status,
            last_seen_at: timestamp,
        })),
    };
    await writeJsonFile(teamRuntimePath(store, runId), resumed);
    await upsertTeamRuntimeAgentThreads(store, runId, resumed);
    return resumed;
}
export async function stopTeamRuntime(store, runId) {
    const runtime = await ensureTeamRuntime(store, runId);
    const timestamp = nowIso();
    const stopped = {
        ...runtime,
        status: "stopped",
        updated_at: timestamp,
        members: runtime.members.map((member) => ({
            ...member,
            status: member.role === "lead" ? member.status : "done",
            last_seen_at: timestamp,
        })),
    };
    await writeJsonFile(teamRuntimePath(store, runId), stopped);
    await upsertTeamRuntimeAgentThreads(store, runId, stopped);
    return stopped;
}
export async function updateTeamMemberStatus(store, runId, memberName, status) {
    await ensureTeamRuntime(store, runId);
    const file = teamRuntimePath(store, runId);
    return withFileLock(file, async () => {
        const runtime = await readJsonFile(file);
        if (!runtime)
            throw new Error(`team runtime not found: ${runId}`);
        const timestamp = nowIso();
        let found = false;
        const next = {
            ...runtime,
            updated_at: timestamp,
            members: runtime.members.map((member) => {
                if (member.name !== memberName)
                    return member;
                found = true;
                return {
                    ...member,
                    status,
                    last_seen_at: timestamp,
                };
            }),
        };
        if (!found)
            throw new Error(`team member not found: ${memberName}`);
        await writeJsonFile(file, next);
        await patchAgentThread(store, runId, memberName, { status: agentThreadStatusFromMemberStatus(status) }).catch(() => undefined);
        return next;
    });
}
export async function recordTeamMemberLaunch(store, runId, memberName, patch) {
    const file = teamRuntimePath(store, runId);
    return withFileLock(file, async () => {
        const runtime = await readJsonFile(file);
        if (!runtime)
            throw new Error(`team runtime not found: ${runId}`);
        const timestamp = nowIso();
        let found = false;
        const next = {
            ...runtime,
            updated_at: timestamp,
            members: runtime.members.map((member) => {
                if (member.name !== memberName)
                    return member;
                found = true;
                return {
                    ...member,
                    ...patch,
                    last_seen_at: timestamp,
                };
            }),
        };
        if (!found)
            throw new Error(`team member not found: ${memberName}`);
        await writeJsonFile(file, next);
        await patchAgentThread(store, runId, memberName, {
            status: patch.status ? agentThreadStatusFromMemberStatus(patch.status) : undefined,
            opencode_session_id: patch.session_id,
            background_id: patch.background_id,
            agent_call_id: patch.agent_call_id,
            prompt_ref: patch.prompt_ref,
        }).catch(() => undefined);
        return next;
    });
}
export async function addTeamMemberToRuntime(store, runId, member) {
    const file = teamRuntimePath(store, runId);
    const next = await withFileLock(file, async () => {
        const runtime = await readJsonFile(file);
        if (!runtime)
            throw new Error(`team runtime not found: ${runId}`);
        if (runtime.members.some((candidate) => candidate.name === member.name)) {
            throw new Error(`team member already exists: ${member.name}`);
        }
        const timestamp = nowIso();
        const updated = {
            ...runtime,
            updated_at: timestamp,
            members: [
                ...runtime.members,
                {
                    ...member,
                    joined_at: member.joined_at || timestamp,
                    last_seen_at: member.last_seen_at || timestamp,
                },
            ],
        };
        await writeJsonFile(file, updated);
        return updated;
    });
    await mkdir(path.join(teamInboxRoot(store, runId), member.name), { recursive: true });
    if (store.config.agent_threads.enabled)
        await upsertProjectedAgentThread(store, runId, next.members.find((candidate) => candidate.name === member.name) ?? member);
    return next;
}
async function upsertTeamRuntimeAgentThreads(store, runId, runtime) {
    if (!store.config.agent_threads.enabled)
        return;
    for (const member of runtime.members) {
        await upsertProjectedAgentThread(store, runId, member);
    }
}
async function upsertProjectedAgentThread(store, runId, member) {
    const input = agentThreadInputFromTeamMember(runId, member, store.config);
    const current = await getAgentThread(store, runId, input.thread_id);
    const helper_counts = current?.helper_counts ?? input.helper_counts;
    const thread = await upsertAgentThread(store, runId, {
        ...input,
        helper_counts,
        tool_manifest_ref: current?.tool_manifest_ref ?? input.tool_manifest_ref,
        memory_ref: current?.memory_ref ?? input.memory_ref,
    });
    await writeAgentThreadToolManifest(store, runId, thread.thread_id, {
        allowed_tools: thread.allowed_tools,
        denied_tools: thread.denied_tools,
        native_tools: nativeToolsFromAllowedTools(thread.allowed_tools, thread.denied_tools, thread.write_paths.length > 0),
        session_tools: sessionToolsForManifest(member),
        allowed_skills: thread.allowed_skills,
        denied_skills: thread.denied_skills,
        allowed_mcps: thread.allowed_mcps,
        denied_mcps: thread.denied_mcps,
        read_paths: thread.read_paths,
        write_paths: thread.write_paths,
        helper_limits: thread.helper_limits,
    });
}
function sessionToolsForManifest(member) {
    const tools = {
        question: false,
        task: member.spawn_policy?.allowed === true && member.spawn_policy.requires_lab_representative_approval === false,
        seed_delegate: false,
        seed_delegate_background: false,
        seed_delegate_followup: false,
    };
    for (const safe of [
        "seed_team_inbox",
        "seed_team_ack",
        "seed_team_send",
        "seed_team_broadcast",
        "seed_team_status",
        "seed_team_roster",
        "seed_team_conversation",
        "seed_team_meeting_create",
        "seed_team_meeting_post",
        "seed_team_meeting_status",
        "seed_team_meeting_close",
        "seed_team_member_request",
        "seed_team_task_list",
        "seed_context_bundle_list",
        "seed_context_bundle_read",
        "seed_context_metrics",
        "seed_orchestrator_next",
        "seed_orchestrator_record_decision",
        "seed_orchestrator_tick",
        "seed_session_info",
    ]) {
        tools[safe] = true;
    }
    for (const allowed of member.tool_policy?.allowed_tools ?? []) {
        for (const permission of permissionsForToolName(allowed))
            tools[permission] = true;
    }
    for (const denied of member.tool_policy?.denied_tools ?? []) {
        for (const permission of permissionsForToolName(denied))
            tools[permission] = false;
    }
    const canSpawn = member.spawn_policy?.allowed === true &&
        (member.spawn_policy.max_depth ?? 0) > 0 &&
        member.spawn_policy.requires_lab_representative_approval === false;
    if (canSpawn) {
        for (const toolName of member.tool_policy?.allowed_tools ?? []) {
            if (toolName === "seed_delegate" || toolName === "seed_delegate_background" || toolName === "seed_delegate_followup") {
                tools[toolName] = true;
            }
        }
    }
    return tools;
}
function nativeToolsFromAllowedTools(allowedTools, deniedTools, hasWritePaths) {
    const allowed = normalizedToolSet(allowedTools);
    const denied = normalizedToolSet(deniedTools);
    const nativeReadTools = ["read", "grep", "glob", "ls", "find", "rg"];
    const nativeWriteTools = ["apply_patch", "edit", "multi_edit", "notebook_edit", "patch", "str_replace", "write"];
    const nativeShellTools = ["bash", "run", "shell", "sh", "terminal"];
    return [...nativeReadTools, ...nativeWriteTools, ...nativeShellTools].filter((tool) => {
        if (denied.has(tool))
            return false;
        if (allowed.size > 0)
            return allowed.has(tool);
        if (nativeWriteTools.includes(tool))
            return hasWritePaths;
        return true;
    });
}
function permissionsForToolName(toolName) {
    const normalized = toolName.trim().toLowerCase();
    if (!normalized)
        return [];
    if (normalized.startsWith("opencode:"))
        return [normalized.slice("opencode:".length)];
    return [normalized];
}
function normalizedToolSet(values) {
    const result = new Set();
    for (const value of values) {
        const normalized = value.trim().toLowerCase();
        if (!normalized)
            continue;
        result.add(normalized);
        if (normalized.startsWith("opencode:"))
            result.add(normalized.slice("opencode:".length));
    }
    return result;
}
export async function finishDynamicTeamLaunch(store, runId, input) {
    const file = teamRuntimePath(store, runId);
    return withFileLock(file, async () => {
        const runtime = await readJsonFile(file);
        if (!runtime)
            throw new Error(`team runtime not found: ${runId}`);
        const timestamp = nowIso();
        const next = {
            ...runtime,
            status: input.status,
            updated_at: timestamp,
            launch_error: input.launchError,
            cleanup_instructions: input.cleanupInstructions,
            members: runtime.members.map((member) => ({
                ...member,
                status: member.status === "launching" && input.status === "active" ? "idle" : member.status,
                last_seen_at: member.status === "launching" ? timestamp : member.last_seen_at,
            })),
        };
        await writeJsonFile(file, next);
        await upsertTeamRuntimeAgentThreads(store, runId, next);
        return next;
    });
}
export async function assertTeamMember(store, runId, memberName) {
    const runtime = await ensureTeamRuntime(store, runId);
    const member = runtime.members.find((candidate) => candidate.name === memberName);
    if (!member)
        throw new Error(`team member not found: ${memberName}`);
    return member;
}
//# sourceMappingURL=store.js.map