#!/usr/bin/env node
import assert from "node:assert/strict"
import { existsSync } from "node:fs"
import { appendFile, chmod, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
const defaultFixtureDir = path.join(repoRoot, "evals", "black-box")

export async function runBlackBoxE2E(options = {}) {
  const modules = options.modules ?? await loadBuiltModules(repoRoot)
  const fixturePaths = options.fixturePaths ?? await defaultFixturePaths()
  const collector = await createEvalCollector(options)
  const summaries = []

  for (const fixturePath of fixturePaths) {
    const fixture = await readFixture(fixturePath)
    if (!options.quiet) console.log(`[black-box] ${fixture.id}`)
    const summary = await runFixture(fixture, modules, options)
    summaries.push(summary)
    await collector?.record(summary)
    if (!options.quiet) console.log(`[black-box] ${fixture.id} passed (${summary.trace_counts.tool_calls} tool calls)`)
  }

  await collector?.finalize(summaries)
  return summaries
}

async function defaultFixturePaths() {
  const entries = await readdir(defaultFixtureDir)
  return entries
    .filter((entry) => entry.endsWith(".fixture.json"))
    .sort()
    .map((entry) => path.join(defaultFixtureDir, entry))
}

async function readFixture(file) {
  const fixture = JSON.parse(await readFile(file, "utf8"))
  assert.equal(fixture.schema_version, 1, `${file}: schema_version must be 1`)
  assert.equal(typeof fixture.id, "string", `${file}: id must be a string`)
  assert.match(fixture.id, /^[a-z0-9.-]+$/, `${file}: id must be stable`)
  assert.equal(typeof fixture.goal, "string", `${file}: goal must be a string`)
  assert.ok(Array.isArray(fixture.files), `${file}: files must be an array`)
  if (fixture.workflow === "paper-review" || fixture.workflow === "claude-runtime-ux") {
    assert.equal(typeof fixture.document?.path, "string", `${file}: document.path is required`)
    assert.equal(typeof fixture.document?.extracted_text, "string", `${file}: document.extracted_text is required`)
  } else {
    assert.equal(typeof fixture.delegation?.target_agent, "string", `${file}: delegation.target_agent is required`)
    assert.equal(typeof fixture.delegation?.prompt, "string", `${file}: delegation.prompt is required`)
    assert.equal(typeof fixture.delegation?.output, "string", `${file}: delegation.output is required`)
  }
  assert.ok(Array.isArray(fixture.oracle?.final_report_contains), `${file}: oracle.final_report_contains must be an array`)
  assert.ok(Array.isArray(fixture.oracle?.trace_events), `${file}: oracle.trace_events must be an array`)
  assert.ok(Array.isArray(fixture.oracle?.activity_contains), `${file}: oracle.activity_contains must be an array`)
  assert.ok(Array.isArray(fixture.oracle?.artifact_paths), `${file}: oracle.artifact_paths must be an array`)
  assert.ok(Array.isArray(fixture.oracle?.transcript_contains), `${file}: oracle.transcript_contains must be an array`)
  assert.ok(!fixture.oracle.model_calls_min || Number.isInteger(fixture.oracle.model_calls_min), `${file}: oracle.model_calls_min must be an integer`)
  assert.ok(!fixture.oracle.tool_calls_min || Number.isInteger(fixture.oracle.tool_calls_min), `${file}: oracle.tool_calls_min must be an integer`)
  return fixture
}

async function runFixture(fixture, modules, options) {
  const root = await mkdtemp(path.join(os.tmpdir(), "seed-black-box-e2e-"))
  const directory = path.join(root, "target-repo")
  const transcriptPath = path.join(root, "transcript.jsonl")
  const keep = options.keep ?? process.env.SEED_BLACK_BOX_E2E_KEEP === "1"

  try {
    await mkdir(directory, { recursive: true })
    for (const file of fixture.files) {
      assert.equal(typeof file.path, "string", `${fixture.id}: fixture file path must be a string`)
      assert.equal(path.isAbsolute(file.path), false, `${fixture.id}: fixture file path must be relative`)
      assert.equal(file.path.includes(".."), false, `${fixture.id}: fixture file path must not traverse directories`)
      const target = path.join(directory, file.path)
      await mkdir(path.dirname(target), { recursive: true })
      await writeFile(target, String(file.content ?? ""), "utf8")
    }

    const client = createMockOpenCodeClient({ directory, fixture, transcriptPath })
    const config = modules.mergeSeedConfig(options.config ?? {}, fixture.config ?? {})
    const tools = modules.createSeedTools({ directory, worktree: directory, client }, config)
    const context = toolContext(directory)

    const callTool = async (name, args, contextOverride = {}) => {
      await appendJsonl(transcriptPath, { kind: "tool_call", name, args: summarizeValue(args) })
      const result = await tools[name].execute(args, { ...context, ...contextOverride })
      const parsed = parseToolJson(result)
      await appendJsonl(transcriptPath, { kind: "tool_result", name, result: summarizeValue(parsed) })
      return parsed
    }

    if (fixture.workflow === "paper-review") {
      return await runPaperReviewFixture({
        fixture,
        modules,
        options,
        directory,
        root,
        transcriptPath,
        client,
        config,
        tools,
        context,
        keep,
        callTool,
      })
    }
    if (fixture.workflow === "claude-runtime-ux") {
      return await runClaudeRuntimeUxFixture({
        fixture,
        modules,
        options,
        directory,
        root,
        transcriptPath,
        client,
        config,
        tools,
        context,
        keep,
        callTool,
      })
    }

    const task = await callTool("seed_task_create", {
      title: "Complete professor goal",
      description: fixture.goal,
      owner_agent: "lab-representative",
      acceptance_criteria: fixture.acceptance_criteria ?? [],
    })
    const delegated = await callTool("seed_delegate", {
      run_id: task.run_id,
      target_agent: fixture.delegation.target_agent,
      prompt: fixture.delegation.prompt,
      prompt_summary: fixture.delegation.prompt_summary,
    })
    const evidence = await callTool("seed_evidence_add", {
      run_id: task.run_id,
      title: "Black-box fixture evidence",
      body: `Delegated result: ${delegated.output_summary ?? fixture.delegation.output}`,
      refs: ["README.md", delegated.output_ref].filter(Boolean),
    })
    await callTool("seed_task_update", {
      run_id: task.run_id,
      task_id: task.task.task_id,
      evidence_refs: [evidence.evidence_ref],
    })
    await callTool("seed_run_check", {
      run_id: task.run_id,
      command: [process.execPath, "-e", "console.log('black box fixture ok')"],
    })
    const report = await callTool("seed_report_write", {
      run_id: task.run_id,
      content: `# Final Report\n\nGoal: ${fixture.goal}\n\nEvidence: ${evidence.evidence_ref}\n\nDelegate artifact: ${delegated.output_ref}\n`,
      status: "completed",
    })
    const gate = await callTool("seed_report_check", { run_id: task.run_id })
    const telemetry = await callTool("seed_run_telemetry", { run_id: task.run_id })
    const artifacts = await callTool("seed_artifact_list", { run_id: task.run_id })

    const runRoot = path.join(directory, ".seed-agent", "runs", task.run_id)
    const finalReport = await readFile(path.join(runRoot, "final_report.md"), "utf8")
    const events = await readJsonl(path.join(runRoot, "events.jsonl"))
    const agentCalls = await readJsonl(path.join(runRoot, "agent_calls.jsonl"))
    const modelCalls = await readJsonl(path.join(runRoot, "model_calls.jsonl"))
    const toolCalls = await readJsonl(path.join(runRoot, "tool_calls.jsonl"))
    const latestActivity = await readFile(path.join(runRoot, "activity", "latest.md"), "utf8")
    const transcript = await readFile(transcriptPath, "utf8")
    const artifactPaths = artifacts.index.artifacts.map((artifact) => artifact.path)

    for (const expected of fixture.oracle.final_report_contains) assert.match(finalReport, new RegExp(escapeRegex(expected)))
    for (const expected of fixture.oracle.trace_events) assert.ok(events.some((event) => event.type === expected), `missing trace event: ${expected}`)
    for (const expected of fixture.oracle.activity_contains) assert.match(latestActivity, new RegExp(escapeRegex(expected)))
    for (const expected of fixture.oracle.artifact_paths) assert.ok(artifactPaths.includes(expected), `missing artifact path: ${expected}`)
    for (const expected of fixture.oracle.transcript_contains) assert.match(transcript, new RegExp(escapeRegex(expected)))

    assert.equal(report.review_gate.status, "passed")
    assert.equal(gate.gate.status, "passed")
    assert.equal(telemetry.telemetry.tasks.open, 0)
    assert.equal(agentCalls.some((call) => call.status === "completed" && call.child_session_id), true)
    assert.equal(toolCalls.some((call) => call.tool_name === "seed_report_check" && call.status === "completed"), true)

    return {
      fixture_id: fixture.id,
      run_id: task.run_id,
      preserved: keep,
      target_repo: keep ? directory : undefined,
      transcript_path: keep ? transcriptPath : undefined,
      final_report_path: keep ? path.join(runRoot, "final_report.md") : undefined,
      activity_latest_path: keep ? path.join(runRoot, "activity", "latest.md") : undefined,
      trace_counts: {
        events: events.length,
        agent_calls: agentCalls.length,
        model_calls: modelCalls.length,
        tool_calls: toolCalls.length,
      },
      artifact_paths: artifactPaths,
      verified: {
        final_report: true,
        trace: true,
        activity_journal: true,
        artifact_index: true,
        transcript: true,
      },
    }
  } finally {
    if (!keep) await rm(root, { recursive: true, force: true })
  }
}

async function runPaperReviewFixture(input) {
  const { fixture, modules, options, directory, root, transcriptPath, client, config, tools, context, keep, callTool } = input
  const fakePdfToText = await writeFakePdfToText(root, fixture.document.extracted_text)
  const previousPdfToText = process.env.SEED_PDFTOTEXT_BIN
  process.env.SEED_PDFTOTEXT_BIN = fakePdfToText
  try {
    const pdfPath = fixture.document.path
    const task = await callTool("seed_task_create", {
      title: "Complete professor goal",
      description: fixture.goal,
      owner_agent: "lab-representative",
      acceptance_criteria: fixture.acceptance_criteria ?? [],
    })
    const team = await callTool("seed_team_create", {
      run_id: task.run_id,
      inline_spec: buildPaperReviewInlineSpec(fixture, pdfPath),
    })
    const extraction = await callTool("seed_document_extract", {
      run_id: task.run_id,
      path: pdfPath,
      max_chunk_chars: fixture.document.max_chunk_chars ?? 1200,
    })
    await callTool("seed_workspace_lease_acquire", {
      run_id: task.run_id,
      owner: "lab-representative",
      path: "final_report.md",
      mode: "write",
    })
    await callTool("seed_task_update", {
      run_id: task.run_id,
      task_id: task.task.task_id,
      evidence_refs: [extraction.evidence_ref],
    })
    await callTool("seed_run_check", {
      run_id: task.run_id,
      command: [process.execPath, "-e", "console.log('paper review fixture ok')"],
    })

    const meetingTranscript = []
    if (fixture.lab_meeting?.enabled) {
      const plugin = modules.createSeedAgentPlugin()
      const hooks = await plugin.server({ directory, worktree: directory, client, project: {}, experimental_workspace: { register() {} }, serverUrl: new URL("http://127.0.0.1:4096"), $: {} }, config)
      const output = { parts: [] }
      await hooks["command.execute.before"]?.({ command: "seed-lab-meeting", sessionID: "black-box-parent", arguments: "" }, output)
      meetingTranscript.push(textOfPart(output.parts?.[0]))
      await appendJsonl(transcriptPath, { kind: "command", command: "/seed-lab-meeting", output: summarizeValue(output.parts) })
      const feedbackOutput = labMeetingFeedbackOutput("black-box-parent", fixture.lab_meeting.feedback ?? "Continue with the paper review.")
      await hooks["chat.message"]?.({ sessionID: "black-box-parent", agent: "lab-representative" }, { parts: [] })
      await hooks["experimental.chat.messages.transform"]?.({}, feedbackOutput)
      meetingTranscript.push(textOfPart(feedbackOutput.messages?.[0]?.parts?.[0]))
      await appendJsonl(transcriptPath, { kind: "chat", command: "normal lab meeting feedback", output: summarizeValue(feedbackOutput.messages?.[0]?.parts) })
    }

    const reportContent = fixture.report_content
      ?? `# Final Report

Goal: ${fixture.goal}

Document: ${pdfPath}

Evidence: ${extraction.evidence_ref}

Summary: The inline-spec document review fixture extracted document chunks, created a dynamic team contract, and recorded verifier-facing evidence.

Limitations: This black-box fixture uses a deterministic OpenCode mock and validates product surfaces rather than semantic review quality.
`
    const report = await callTool("seed_report_write", {
      run_id: task.run_id,
      content: reportContent,
      status: "completed",
    })
    const gate = await callTool("seed_report_check", { run_id: task.run_id })
    const telemetry = await callTool("seed_run_telemetry", { run_id: task.run_id })
    const artifacts = await callTool("seed_artifact_list", { run_id: task.run_id })

    const runRoot = path.join(directory, ".seed-agent", "runs", task.run_id)
    const finalReport = await readFile(path.join(runRoot, "final_report.md"), "utf8")
    const events = await readJsonl(path.join(runRoot, "events.jsonl"))
    const agentCalls = await readJsonl(path.join(runRoot, "agent_calls.jsonl"))
    const modelCalls = await readJsonl(path.join(runRoot, "model_calls.jsonl"))
    const toolCalls = await readJsonl(path.join(runRoot, "tool_calls.jsonl"))
    const latestActivity = await readFile(path.join(runRoot, "activity", "latest.md"), "utf8")
    const transcript = await readFile(transcriptPath, "utf8")
    const artifactPaths = artifacts.index.artifacts.map((artifact) => artifact.path)
    const meetingFiles = await listFilesIfExists(path.join(runRoot, "meetings"))
    const workspaceLeases = await readJsonFileIfExists(path.join(runRoot, "workspace", "leases.json"), [])
    const workspaceConflicts = await readJsonFileIfExists(path.join(runRoot, "workspace", "conflicts.json"), [])

    runCommonOracles(fixture, { finalReport, events, latestActivity, artifactPaths, transcript })
    if (fixture.oracle.model_calls_min) assert.ok(modelCalls.length >= fixture.oracle.model_calls_min, `expected at least ${fixture.oracle.model_calls_min} model calls, got ${modelCalls.length}`)
    if (fixture.oracle.tool_calls_min) assert.ok(toolCalls.length >= fixture.oracle.tool_calls_min, `expected at least ${fixture.oracle.tool_calls_min} tool calls, got ${toolCalls.length}`)
    assert.equal(team.status === "active" || team.status === "failed", true, "team runtime should be created")
    assert.equal(extraction.status, "extracted", "PDF should be extracted by fixture pdftotext")
    assert.equal(report.review_gate.status, "passed")
    assert.equal(gate.gate.status, "passed")
    assert.equal(telemetry.telemetry.evidence.final_report_covers_task_evidence, true)
    assert.equal(workspaceLeases.length > 0, true, "workspace lease state was not created")
    assert.equal(Array.isArray(workspaceConflicts), true, "workspace conflict state should be readable")
    if (fixture.lab_meeting?.enabled) {
      assert.ok(meetingFiles.some((file) => file.endsWith(".md")), "Lab Meeting markdown artifact missing")
      assert.match(meetingTranscript.join("\n"), /SEED Lab Meeting/)
    }
    assertNoSensitiveLeak(`${finalReport}\n${latestActivity}\n${transcript}`, fixture.id)
    assertBudgets(fixture, { agentCalls, modelCalls, toolCalls })

    return {
      fixture_id: fixture.id,
      run_id: task.run_id,
      preserved: keep,
      target_repo: keep ? directory : undefined,
      transcript_path: keep ? transcriptPath : undefined,
      final_report_path: keep ? path.join(runRoot, "final_report.md") : undefined,
      activity_latest_path: keep ? path.join(runRoot, "activity", "latest.md") : undefined,
      meeting_paths: keep ? meetingFiles.map((file) => path.join(runRoot, "meetings", file)) : undefined,
      trace_counts: {
        events: events.length,
        agent_calls: agentCalls.length,
        model_calls: modelCalls.length,
        tool_calls: toolCalls.length,
      },
      artifact_paths: artifactPaths,
      verified: {
        final_report: true,
        trace: true,
        activity_journal: true,
        representative_briefing: true,
        artifact_index: true,
        transcript: true,
        document_extraction: true,
        workspace_state: true,
        lab_meeting: Boolean(fixture.lab_meeting?.enabled),
      },
    }
  } finally {
    if (previousPdfToText === undefined) delete process.env.SEED_PDFTOTEXT_BIN
    else process.env.SEED_PDFTOTEXT_BIN = previousPdfToText
  }
}

async function runClaudeRuntimeUxFixture(input) {
  const { fixture, modules, directory, root, transcriptPath, client, config, keep, callTool } = input
  const fakePdfToText = await writeFakePdfToText(root, fixture.document.extracted_text)
  const previousPdfToText = process.env.SEED_PDFTOTEXT_BIN
  process.env.SEED_PDFTOTEXT_BIN = fakePdfToText
  try {
    const pdfPath = fixture.document.path
    const task = await callTool("seed_task_create", {
      title: "Complete professor goal",
      description: fixture.goal,
      owner_agent: "lab-representative",
      acceptance_criteria: fixture.acceptance_criteria ?? [],
    })
    const team = await callTool("seed_team_create", {
      run_id: task.run_id,
      inline_spec: buildRuntimeUxInlineSpec(fixture, pdfPath),
    })
    assert.equal(team.status, "active", "runtime UX fixture must launch an active team")

    const runRoot = path.join(directory, ".seed-agent", "runs", task.run_id)
    const runtime = await readJsonFileIfExists(path.join(runRoot, "team", "runtime.json"), undefined)
    const implementer = runtime?.members?.find((member) => member.name === "runtime_implementer")
    const verifier = runtime?.members?.find((member) => member.name === "runtime_verifier")
    assert.ok(implementer?.session_id, "runtime implementer child session missing")
    assert.ok(verifier?.session_id, "runtime verifier child session missing")

    const extraction = await callTool("seed_document_extract", {
      run_id: task.run_id,
      path: pdfPath,
      max_chunk_chars: fixture.document.max_chunk_chars ?? 1200,
    })
    assert.equal(extraction.status, "extracted", "PDF should be extracted by fixture pdftotext")

    const subplan = await callTool("seed_subplan_create", {
      run_id: task.run_id,
      title: "Write cited HTML paper report",
      objective: "Turn document evidence into a professor-readable HTML report with verifier review.",
      owner_member: "runtime_implementer",
      verifier_member: "runtime_verifier",
      required_for_final: true,
      read_paths: [pdfPath],
      write_paths: ["reports/runtime-report.html"],
      workspace_strategy: "exclusive_lease",
      workspace_conflict_policy: "meeting",
      evidence_refs: [extraction.evidence_ref],
    })
    const subplanId = subplan.subplan.subplan_id

    const earlyReport = await callTool("seed_report_write", {
      run_id: task.run_id,
      content: `# Final Report\n\nPremature report for ${fixture.goal}.\n\nEvidence: ${extraction.evidence_ref}\n`,
      status: "completed",
    })
    assert.equal(earlyReport.status, "completion_gate_failed", "final report should be blocked before required sub-plan acceptance")

    await callTool("seed_subplan_transition", {
      run_id: task.run_id,
      subplan_id: subplanId,
      status: "ready",
      summary: "Planner approved the implementation slice.",
      evidence_refs: [extraction.evidence_ref],
    })
    const lease = await callTool("seed_workspace_lease_acquire", {
      run_id: task.run_id,
      owner: "runtime_implementer",
      path: "reports/runtime-report.html",
      mode: "write",
      subplan_id: subplanId,
    })
    assert.equal(lease.granted, true, "runtime implementer should receive the planned report lease")
    await callTool("seed_subplan_transition", {
      run_id: task.run_id,
      subplan_id: subplanId,
      status: "executing",
      summary: "Implementation may now use native OpenCode write/edit/bash tools inside the leased path.",
    })

    const plugin = modules.createSeedAgentPlugin()
    const hooks = await plugin.server({ directory, worktree: directory, client, project: {}, experimental_workspace: { register() {} }, serverUrl: new URL("http://127.0.0.1:4096"), $: {} }, config)
    let unauthorizedWriteBlocked = false
    try {
      await hooks["tool.execute.before"]?.(
        { tool: "write", sessionID: implementer.session_id, callID: "black-box-unauthorized-write" },
        { args: { path: "reports/unauthorized.html", content: "<h1>outside lease</h1>" } },
      )
    } catch (error) {
      unauthorizedWriteBlocked = /SEED team member policy blocked/.test(error instanceof Error ? error.message : String(error))
    }
    assert.equal(unauthorizedWriteBlocked, true, "member write outside planned path should fail closed")
    await appendJsonl(transcriptPath, { kind: "policy", action: "unauthorized_write_blocked", session_id: implementer.session_id })

    const teamMessage = await callTool("seed_team_send", {
      run_id: task.run_id,
      to: "runtime_verifier",
      kind: "message",
      message: "Implementation draft is ready for verifier review; check the diff ref before accepting.",
    }, { agent: "runtime_implementer", sessionID: implementer.session_id })
    const helperDelegation = await callTool("seed_delegate_background", {
      run_id: task.run_id,
      target_agent: "runtime_table_helper",
      prompt: "As a bounded helper, inspect the extracted document text for one claim/evidence mapping risk and report back to runtime_implementer.",
      prompt_summary: "runtime implementer helper evidence check",
      idempotency_key: "runtime-ux-implementer-helper",
    }, { agent: "runtime_implementer", sessionID: implementer.session_id })
    assert.ok(helperDelegation.helper_id, "helper background delegation should record helper ownership")
    await callTool("seed_background_checkpoint", {
      run_id: task.run_id,
      background_id: helperDelegation.background_id,
      note: "Capture helper completion before opening the manual Lab Meeting.",
    })
    await callTool("seed_team_ack", {
      run_id: task.run_id,
      member: "runtime_verifier",
      message_ids: [teamMessage.message_id],
    }, { agent: "runtime_verifier", sessionID: verifier.session_id })

    const teamTask = await callTool("seed_team_task_create", {
      run_id: task.run_id,
      title: "Race-safe verifier handoff",
      description: "Exercise stale member output handling without mutating current state.",
    })
    await callTool("seed_team_task_claim", {
      run_id: task.run_id,
      team_task_id: teamTask.task.team_task_id,
      member: "runtime_verifier",
    }, { agent: "runtime_verifier", sessionID: verifier.session_id })
    const staleTeamTask = await callTool("seed_team_task_update", {
      run_id: task.run_id,
      team_task_id: teamTask.task.team_task_id,
      expected_revision: teamTask.task.revision,
      status: "completed",
      evidence_refs: ["evidence.md#stale-member-output"],
    }, { agent: "runtime_implementer", sessionID: implementer.session_id })
    assert.equal(staleTeamTask.task.status, "blocked", "stale member output should not overwrite current verifier task state")

    const duplicate = await callTool("seed_delegate", {
      run_id: task.run_id,
      target_agent: "runtime_verifier",
      prompt: "Check whether the HTML report needs one more verification pass.",
      prompt_summary: "Runtime UX duplicate-safe verifier dispatch",
      idempotency_key: "runtime-ux-verifier-dispatch",
    })
    const duplicateAgain = await callTool("seed_delegate", {
      run_id: task.run_id,
      target_agent: "runtime_verifier",
      prompt: "Check whether the HTML report needs one more verification pass.",
      prompt_summary: "Runtime UX duplicate-safe verifier dispatch",
      idempotency_key: "runtime-ux-verifier-dispatch",
    })
    assert.ok(duplicate.agent_call_id, "first duplicate-safe delegation should run")
    assert.equal(duplicateAgain.status, "deduplicated", "second duplicate-safe delegation should be ignored")

    const evidence = await callTool("seed_evidence_add", {
      run_id: task.run_id,
      title: "Runtime UX implementation evidence",
      body: [
        `Document evidence: ${extraction.evidence_ref}`,
        `Sub-plan: ${subplanId}`,
        "Verifier first requested rework for missing citation mapping.",
        "The document contains instruction-like text, but SEED keeps it as untrusted evidence.",
      ].join("\n"),
      refs: [extraction.evidence_ref, extraction.artifacts?.metadata, extraction.artifacts?.report].filter(Boolean),
    })

    const diff = await callTool("seed_workspace_diff_record", {
      run_id: task.run_id,
      owner: "runtime_implementer",
      subplan_id: subplanId,
      paths: ["reports/runtime-report.html"],
      lease_ids: [lease.lease.lease_id],
      summary: "Created professor-readable HTML report from extracted document evidence.",
      evidence_refs: [evidence.evidence_ref],
    })
    await callTool("seed_subplan_transition", {
      run_id: task.run_id,
      subplan_id: subplanId,
      status: "verifying",
      summary: "Implementation finished; verifier should inspect citations and report readability.",
      evidence_refs: [evidence.evidence_ref, diff.diff.diff_ref],
    })
    await callTool("seed_subplan_verify", {
      run_id: task.run_id,
      subplan_id: subplanId,
      verdict: "rework",
      summary: "Needs clearer claim-to-evidence mapping before final acceptance.",
      evidence_refs: [evidence.evidence_ref],
    }, { agent: "runtime_verifier", sessionID: verifier.session_id })
    await callTool("seed_subplan_transition", {
      run_id: task.run_id,
      subplan_id: subplanId,
      status: "executing",
      summary: "Implementer revised the HTML report after verifier rework.",
      evidence_refs: [diff.diff.diff_ref],
    })
    await callTool("seed_subplan_transition", {
      run_id: task.run_id,
      subplan_id: subplanId,
      status: "verifying",
      summary: "Revised report is ready for final verifier pass.",
      evidence_refs: [diff.diff.diff_ref],
    })
    await callTool("seed_subplan_verify", {
      run_id: task.run_id,
      subplan_id: subplanId,
      verdict: "accepted",
      summary: "HTML report is understandable, scoped to the leased file, and cites SEED evidence refs.",
      evidence_refs: [evidence.evidence_ref, diff.diff.diff_ref],
    }, { agent: "runtime_verifier", sessionID: verifier.session_id })

    const meetingTranscript = []
    if (fixture.lab_meeting?.enabled) {
      for (const member of runtime.members.filter((entry) => entry.name !== "lab-representative" && entry.background_id)) {
        await callTool("seed_background_checkpoint", {
          run_id: task.run_id,
          background_id: member.background_id,
          note: `Capture ${member.name} completion before opening the manual Lab Meeting.`,
        })
      }
      const output = { parts: [] }
      await hooks["command.execute.before"]?.({ command: "seed-lab-meeting", sessionID: "black-box-parent", arguments: "" }, output)
      meetingTranscript.push(textOfPart(output.parts?.[0]))
      await appendJsonl(transcriptPath, { kind: "command", command: "/seed-lab-meeting", output: summarizeValue(output.parts) })
      const feedbackOutput = labMeetingFeedbackOutput("black-box-parent", fixture.lab_meeting.feedback ?? "Continue with the runtime UX evaluation.")
      await hooks["chat.message"]?.({ sessionID: "black-box-parent", agent: "lab-representative" }, { parts: [] })
      await hooks["experimental.chat.messages.transform"]?.({}, feedbackOutput)
      meetingTranscript.push(textOfPart(feedbackOutput.messages?.[0]?.parts?.[0]))
      await appendJsonl(transcriptPath, { kind: "chat", command: "normal lab meeting feedback", output: summarizeValue(feedbackOutput.messages?.[0]?.parts) })
    }

    await callTool("seed_task_update", {
      run_id: task.run_id,
      task_id: task.task.task_id,
      evidence_refs: [extraction.evidence_ref, evidence.evidence_ref, diff.diff.diff_ref],
    })
    await callTool("seed_run_check", {
      run_id: task.run_id,
      command: [process.execPath, "-e", "console.log('claude runtime ux fixture ok')"],
    })
    const finalContent = `# Final Report

Goal: ${fixture.goal}

Document: ${pdfPath}

Evidence: ${extraction.evidence_ref}

Implementation evidence: ${evidence.evidence_ref}

Workspace diff: ${diff.diff.diff_ref}

Sub-plan accepted: ${subplanId}

The HTML report path is reports/runtime-report.html, and extracted document text was treated only as untrusted document evidence.
`
    const report = await callTool("seed_report_write", {
      run_id: task.run_id,
      content: finalContent,
      status: "completed",
    })
    const gate = await callTool("seed_report_check", { run_id: task.run_id })
    const telemetry = await callTool("seed_run_telemetry", { run_id: task.run_id })
    const artifacts = await callTool("seed_artifact_list", { run_id: task.run_id })
    const statusOutput = { parts: [] }
    await hooks["command.execute.before"]?.({ command: "seed-status", sessionID: "black-box-parent", arguments: "" }, statusOutput)
    await appendJsonl(transcriptPath, { kind: "command", command: "/seed-status", output: summarizeValue(statusOutput.parts) })

    const finalReport = await readFile(path.join(runRoot, "final_report.md"), "utf8")
    const events = await readJsonl(path.join(runRoot, "events.jsonl"))
    const agentCalls = await readJsonl(path.join(runRoot, "agent_calls.jsonl"))
    const modelCalls = await readJsonl(path.join(runRoot, "model_calls.jsonl"))
    const toolCalls = await readJsonl(path.join(runRoot, "tool_calls.jsonl"))
    const latestActivity = await readFile(path.join(runRoot, "activity", "latest.md"), "utf8")
    const transcript = await readFile(transcriptPath, "utf8")
    const artifactPaths = artifacts.index.artifacts.map((artifact) => artifact.path)
    const launchLedger = await readJsonl(path.join(runRoot, "team", "launch-ledger.jsonl"))
    const conversation = await readJsonl(path.join(runRoot, "team", "conversation.jsonl"))
    const threads = await readJsonFileIfExists(path.join(runRoot, "threads", "threads.json"), [])
    const implementerManifest = await readJsonFileIfExists(path.join(runRoot, "threads", "runtime_implementer", "tool-manifest.json"), undefined)
    const implementerHelpers = await readJsonl(path.join(runRoot, "threads", "runtime_implementer", "helpers.jsonl"))
    const messageEnvelopes = await readJsonFileIfExists(path.join(runRoot, "messages", "envelopes.json"), [])
    const deliveryAttempts = await readJsonl(path.join(runRoot, "messages", "delivery.jsonl"))
    const backgroundTasks = await readJsonFileIfExists(path.join(runRoot, "background_tasks.json"), [])
    const subplans = await readJsonFileIfExists(path.join(runRoot, "subplans.json"), [])
    const workspaceDiffs = await readJsonFileIfExists(path.join(runRoot, "workspace", "diffs.json"), [])
    const workspaceLeases = await readJsonFileIfExists(path.join(runRoot, "workspace", "leases.json"), [])
    const idempotencyRecords = await readJsonFileIfExists(path.join(runRoot, "idempotency", "records.json"), [])
    const budget = await readJsonFileIfExists(path.join(runRoot, "budget.json"), undefined)
    const metadata = await readJsonFileIfExists(extraction.artifacts.metadata, undefined)
    const extractionReport = await readFile(extraction.artifacts.report, "utf8")
    const meetingFiles = await listFilesIfExists(path.join(runRoot, "meetings"))
    const meetingText = (await Promise.all(meetingFiles.map(async (file) => readFile(path.join(runRoot, "meetings", file), "utf8")))).join("\n")

    runCommonOracles(fixture, { finalReport, events, latestActivity, artifactPaths, transcript })
    if (fixture.oracle.model_calls_min) assert.ok(modelCalls.length >= fixture.oracle.model_calls_min, `expected at least ${fixture.oracle.model_calls_min} model calls, got ${modelCalls.length}`)
    if (fixture.oracle.tool_calls_min) assert.ok(toolCalls.length >= fixture.oracle.tool_calls_min, `expected at least ${fixture.oracle.tool_calls_min} tool calls, got ${toolCalls.length}`)
    assert.equal(report.review_gate.status, "passed")
    assert.equal(gate.gate.status, "passed")
    assert.equal(telemetry.telemetry.evidence.final_report_covers_task_evidence, true)
    assert.ok(runtime.members.filter((member) => member.role === "member").every((member) => member.opencode_agent === "lab-representative"), "dynamic members should all run through lab-representative")
    assert.ok(launchLedger.some((entry) => entry.role_id === "runtime_planner" && entry.prompt_preview?.includes("[SEED dynamic team member]")), "planner launch prompt missing")
    assert.ok(launchLedger.some((entry) => entry.role_id === "runtime_implementer" && entry.allowed_tools?.includes("write") && entry.write_surface === "native_hook_enforced"), "implementer native write policy missing")
    assert.ok(launchLedger.some((entry) => entry.role_id === "runtime_verifier" && entry.context_refs?.some((ref) => ref.startsWith("team/specs/"))), "verifier context refs missing")
    assert.ok(conversation.some((entry) => entry.type === "message" && entry.to === "runtime_verifier"), "team conversation message missing")
    assert.ok(conversation.some((entry) => entry.type === "ack" && entry.from === "runtime_verifier" && entry.to === "team-runtime"), "team conversation ack missing")
    assert.ok(threads.some((thread) => thread.thread_id === "runtime_implementer" && thread.tool_manifest_ref === "threads/runtime_implementer/tool-manifest.json"), "runtime implementer agent-thread manifest ref missing")
    assert.ok(implementerManifest?.allowed_tools?.includes("seed_delegate_background"), "runtime implementer tool manifest missing helper delegation permission")
    assert.ok(messageEnvelopes.some((message) => message.message_id === teamMessage.message_id && message.from === "runtime_implementer" && message.to === "runtime_verifier"), "message envelope from implementer to verifier missing")
    assert.ok(deliveryAttempts.some((attempt) => attempt.message_id === teamMessage.message_id && attempt.metadata?.from === "runtime_implementer" && attempt.metadata?.to === "runtime_verifier"), "message delivery attempt missing from/to metadata")
    assert.ok(implementerHelpers.some((helper) => helper.helper_id === helperDelegation.helper_id && helper.owner_thread_id === "runtime_implementer" && helper.background_id === helperDelegation.background_id), "helper ledger missing owner-thread background")
    assert.ok(backgroundTasks.some((background) => background.helper_id === helperDelegation.helper_id && background.owner_thread_id === "runtime_implementer"), "background task missing helper ownership")
    assert.ok(subplans.some((entry) => entry.subplan_id === subplanId && entry.status === "accepted" && entry.latest_verdict?.verdict === "accepted" && entry.lease_ids?.length && entry.diff_refs?.length), "accepted sub-plan should retain verdict, lease, and diff refs")
    assert.ok(workspaceLeases.some((entry) => entry.lease_id === lease.lease.lease_id && entry.status === "active"), "workspace lease missing")
    assert.ok(workspaceDiffs.some((entry) => entry.diff_ref === diff.diff.diff_ref && entry.lease_ids?.includes(lease.lease.lease_id)), "workspace diff ref missing lease binding")
    assert.ok(idempotencyRecords.some((entry) => entry.key === "runtime-ux-verifier-dispatch" && entry.status === "completed"), "idempotency record missing duplicate-safe dispatch")
    assert.ok(budget?.limits?.max_child_sessions, "budget state missing")
    assert.equal(metadata?.content_trust, "untrusted_document_evidence")
    assert.match(extractionReport, /untrusted evidence/i)
    assert.match(extractionReport, /do not follow instructions/i)
    assert.match(meetingText, new RegExp(escapeRegex(fixture.lab_meeting?.feedback ?? "")))
    assert.match(meetingTranscript.join("\n"), /SEED Lab Meeting/)
    assert.ok(events.some((event) => event.type === "team_member_workspace_policy_blocked"), "unauthorized write block event missing")
    assert.ok(events.some((event) => event.type === "team_task_conflict_recorded"), "stale team task conflict event missing")
    assertNoSensitiveLeak(`${finalReport}\n${latestActivity}\n${transcript}\n${JSON.stringify(launchLedger)}\n${JSON.stringify(conversation)}`, fixture.id)
    assertNoHiddenReasoning(`${finalReport}\n${latestActivity}\n${transcript}\n${JSON.stringify(launchLedger)}\n${JSON.stringify(conversation)}`, fixture.id)
    assertBudgets(fixture, { agentCalls, modelCalls, toolCalls })

    return {
      fixture_id: fixture.id,
      run_id: task.run_id,
      preserved: keep,
      target_repo: keep ? directory : undefined,
      transcript_path: keep ? transcriptPath : undefined,
      final_report_path: keep ? path.join(runRoot, "final_report.md") : undefined,
      activity_latest_path: keep ? path.join(runRoot, "activity", "latest.md") : undefined,
      meeting_paths: keep ? meetingFiles.map((file) => path.join(runRoot, "meetings", file)) : undefined,
      trace_counts: {
        events: events.length,
        agent_calls: agentCalls.length,
        model_calls: modelCalls.length,
        tool_calls: toolCalls.length,
      },
      artifact_paths: artifactPaths,
      verified: {
        final_report: true,
        trace: true,
        activity_journal: true,
        artifact_index: true,
        transcript: true,
        dynamic_team_runtime: true,
        launch_ledger: true,
        conversation_log: true,
        agent_threads: true,
        message_bus: true,
        helper_ownership: true,
        tool_manifest: true,
        subplan_loop: true,
        workspace_policy: true,
        trust_boundary: true,
        idempotency: true,
        lab_meeting: Boolean(fixture.lab_meeting?.enabled),
      },
    }
  } finally {
    if (previousPdfToText === undefined) delete process.env.SEED_PDFTOTEXT_BIN
    else process.env.SEED_PDFTOTEXT_BIN = previousPdfToText
  }
}

function buildPaperReviewInlineSpec(fixture, pdfPath) {
  const authoritativeInputs = [pdfPath]
  return {
    team_name: "paper-review-black-box-team",
    objective: fixture.goal,
    lead_owner: "lab-representative",
    workflow_template: { name: "paper-review", required_coverage: ["planner", "executor", "verifier"] },
    authoritative_inputs: authoritativeInputs,
    expected_state_version: 1,
    expected_outputs: [
      "document extraction artifacts",
      "paper understanding notes",
      "critical analysis notes",
      "claim/evidence verification notes",
      "Korean final_report.md",
    ],
    stop_condition: "Final report is written only after document extraction and verifier-facing evidence are recorded or explicitly blocked.",
    members: [
      paperReviewMember("paper_review_planner", "Paper review planner", "planning", ["planner"], authoritativeInputs, {
        mission_prompt: "Plan the paper review, split work into team tasks, and keep planner/executor/verifier coverage visible.",
        expected_outputs: ["team task plan"],
        write_paths: ["artifacts/paper-review-planner.md"],
      }),
      paperReviewMember("document_ingestion", "PDF ingestion and extraction lead", "document_extraction", ["executor"], authoritativeInputs, {
        mission_prompt: `Extract ${pdfPath} with seed_document_extract and report text/page/chunk artifact refs.`,
        tool_policy: { allowed_tools: ["seed_document_extract", "seed_artifact_read", "seed_evidence_add", "seed_team_send", "seed_team_task_update"], denied_tools: [] },
        expected_outputs: ["document extraction report", "document chunk refs"],
        write_paths: ["artifacts/document-ingestion.md"],
        dependencies: ["paper_review_planner"],
      }),
      paperReviewMember("paper_understanding", "Paper understanding analyst", "research_analysis", ["executor"], authoritativeInputs, {
        mission_prompt: "Summarize the paper claims from extracted document chunks and cite evidence anchors.",
        expected_outputs: ["thesis/method/evidence summary with citations"],
        write_paths: ["artifacts/paper-understanding.md"],
        dependencies: ["document_ingestion"],
      }),
      paperReviewMember("critical_analysis", "Critical analysis specialist", "critical_review", ["executor"], authoritativeInputs, {
        mission_prompt: "Critique assumptions, limitations, reproducibility, and SEED Harness takeaways using evidence refs.",
        expected_outputs: ["critical analysis notes with citations"],
        write_paths: ["artifacts/critical-analysis.md"],
        dependencies: ["paper_understanding"],
      }),
      paperReviewMember("claim_evidence_verifier", "Claim and evidence verifier", "verification", ["verifier"], authoritativeInputs, {
        mission_prompt: "Verify final-review claims against document chunks, evidence anchors, tool results, or professor-provided facts.",
        tool_policy: { allowed_tools: ["seed_artifact_read", "seed_report_check", "seed_evidence_add", "seed_team_send", "seed_team_task_update"], denied_tools: [] },
        evidence_contract: { required_refs: ["document chunks", "evidence.md anchors"], required_artifacts: ["claim/evidence verification notes"], oracle_scope: ["final report claims", "document chunks", "team evidence"] },
        verifier_obligations: ["Flag unsupported final-report claims before completion."],
        oracle_scope: ["document chunks", "evidence ledger", "team outputs", "final report"],
        expected_outputs: ["claim/evidence verification notes"],
        write_paths: ["artifacts/claim-evidence-verification.md"],
        dependencies: ["critical_analysis"],
      }),
      paperReviewMember("report_composer", "Professor-facing Korean report composer", "reporting", ["executor"], authoritativeInputs, {
        mission_prompt: "Compose Korean final_report.md after verification, citing evidence anchors and document chunk refs.",
        tool_policy: { allowed_tools: ["seed_artifact_read", "seed_report_check", "seed_report_write", "seed_evidence_add", "seed_team_send", "seed_team_task_update"], denied_tools: [] },
        expected_outputs: ["Korean final_report.md"],
        write_paths: ["final_report.md"],
        dependencies: ["claim_evidence_verifier"],
      }),
    ],
  }
}

function buildRuntimeUxInlineSpec(fixture, pdfPath) {
  const authoritativeInputs = [pdfPath, "professor goal", "current workspace"]
  return {
    team_id: "claude-runtime-ux-team",
    team_name: "claude-runtime-ux-team",
    objective: fixture.goal,
    lead_owner: "lab-representative",
    workflow_template: { name: "dynamic-plan-execute-verify", required_coverage: ["planner", "executor", "verifier"] },
    authoritative_inputs: authoritativeInputs,
    expected_state_version: 1,
    expected_outputs: [
      "sub-plan with explicit verifier",
      "leased HTML report implementation",
      "rework verdict and accepted verifier verdict",
      "professor-facing final_report.md",
    ],
    stop_condition: "Final report is completed only after the required HTML-report sub-plan is accepted and evidence refs are cited.",
    members: [
      runtimeUxMember("runtime_planner", "Runtime planner", "planning", ["planner"], authoritativeInputs, {
        mission_prompt: "Plan the mission into sub-plans, assign owner/verifier roles, and keep final completion blocked until verification passes.",
        tool_policy: { allowed_tools: ["read", "grep", "seed_subplan_create", "seed_team_send", "seed_team_ack", "seed_team_task_create", "seed_team_task_update", "seed_evidence_add"], denied_tools: [] },
        expected_outputs: ["sub-plan proposal with evidence inputs"],
        write_paths: [],
      }),
      runtimeUxMember("runtime_implementer", "Runtime implementer", "coding", ["executor"], authoritativeInputs, {
        mission_prompt: "Implement the assigned HTML report using native OpenCode file tools only inside reports/runtime-report.html after a SEED workspace lease is active.",
        tool_policy: {
          allowed_tools: ["read", "grep", "write", "edit", "bash", "seed_delegate_background", "seed_workspace_lease_acquire", "seed_workspace_diff_record", "seed_team_send", "seed_team_ack", "seed_evidence_add", "seed_team_task_update"],
          denied_tools: [],
        },
        spawn_policy: { allowed: true, max_depth: 1, requires_lab_representative_approval: false },
        expected_outputs: ["reports/runtime-report.html", "workspace diff ref"],
        write_paths: ["reports/runtime-report.html"],
        lease_requirements: ["reports/runtime-report.html"],
        dependencies: ["runtime_planner"],
      }),
      runtimeUxMember("runtime_verifier", "Runtime verifier", "verification", ["verifier"], authoritativeInputs, {
        mission_prompt: "Verify the HTML report against document evidence, request rework once if claims are unclear, then accept only with evidence refs.",
        tool_policy: { allowed_tools: ["read", "grep", "seed_subplan_verify", "seed_report_check", "seed_team_send", "seed_team_ack", "seed_evidence_add", "seed_team_task_claim", "seed_team_task_update"], denied_tools: [] },
        evidence_contract: {
          required_refs: ["document extraction evidence", "workspace diff ref"],
          required_artifacts: ["verifier verdict"],
          oracle_scope: ["final report", "sub-plan verdicts", "workspace diff refs"],
        },
        verifier_obligations: ["Request rework before acceptance when evidence mapping is unclear."],
        expected_outputs: ["rework verdict", "accepted verdict"],
        write_paths: [],
        dependencies: ["runtime_implementer"],
      }),
    ],
  }
}

function runtimeUxMember(roleId, roleLabel, capabilityProfile, coverage, authoritativeInputs, overrides = {}) {
  return {
    role_id: roleId,
    role_label: roleLabel,
    coverage,
    capability_profile: capabilityProfile,
    mission_prompt: `${roleLabel}: complete your role in the runtime UX evaluation with durable evidence.`,
    tool_policy: { allowed_tools: ["read", "seed_evidence_add", "seed_team_send", "seed_team_ack"], denied_tools: [] },
    mcp_policy: { allowed_mcps: [], denied_mcps: [] },
    skill_policy: { allowed_skills: [], denied_skills: [], allow_user_skills: false },
    prompt_budget: { max_prompt_chars: 5000 },
    model_route: { model_profile: runtimeUxModelProfile(capabilityProfile), fallback_profiles: [] },
    spawn_policy: { allowed: false, max_depth: 0, requires_lab_representative_approval: true },
    launch_policy: { mode: "background", wait_for_dependencies: true },
    delivery_policy: { report_to: "lab-representative", expected_artifact_paths: [] },
    evidence_contract: { required_refs: [], required_artifacts: [], oracle_scope: [] },
    authoritative_inputs: authoritativeInputs,
    expected_state_version: 1,
    read_paths: authoritativeInputs,
    write_paths: [],
    evidence_paths: [],
    artifact_paths: [],
    expected_outputs: [`${roleLabel} output`],
    stop_condition: "Output is reported with evidence refs, or a blocker is recorded.",
    dependencies: [],
    artifact_inputs: [],
    evidence_inputs: authoritativeInputs,
    verifier_obligations: [],
    oracle_scope: [],
    semantic_conflict_policy: "exclusive",
    human_in_the_loop: { risk_tier: "low", escalation_triggers: [] },
    ...overrides,
  }
}

function runtimeUxModelProfile(capabilityProfile) {
  if (capabilityProfile === "planning") return "reasoning"
  if (capabilityProfile === "verification") return "review"
  return capabilityProfile
}

function paperReviewMember(roleId, roleLabel, capabilityProfile, coverage, authoritativeInputs, overrides = {}) {
  return {
    role_id: roleId,
    role_label: roleLabel,
    coverage,
    capability_profile: capabilityProfile,
    mission_prompt: `${roleLabel}: complete your part of the paper-review fixture with explicit evidence.`,
    tool_policy: { allowed_tools: ["seed_artifact_read", "seed_evidence_add", "seed_team_send", "seed_team_task_update"], denied_tools: [] },
    mcp_policy: { allowed_mcps: [], denied_mcps: [] },
    skill_policy: { allowed_skills: [], denied_skills: [], allow_user_skills: false },
    prompt_budget: { max_prompt_chars: 4000 },
    model_route: { model_profile: capabilityProfile === "verification" ? "review" : "research", fallback_profiles: [] },
    launch_policy: { mode: "background", wait_for_dependencies: true },
    delivery_policy: { report_to: "lab-representative", expected_artifact_paths: [] },
    evidence_contract: { required_refs: [], required_artifacts: [], oracle_scope: [] },
    authoritative_inputs: authoritativeInputs,
    expected_state_version: 1,
    read_paths: authoritativeInputs,
    write_paths: [`artifacts/${roleId}.md`],
    evidence_paths: [],
    artifact_paths: [],
    expected_outputs: [`${roleLabel} notes`],
    stop_condition: "Output is reported with evidence or a blocker.",
    dependencies: [],
    artifact_inputs: [],
    evidence_inputs: authoritativeInputs,
    verifier_obligations: [],
    oracle_scope: [],
    semantic_conflict_policy: "exclusive",
    human_in_the_loop: { risk_tier: "low", escalation_triggers: [] },
    ...overrides,
  }
}

function createMockOpenCodeClient({ directory, fixture, transcriptPath }) {
  const parentSessionId = "black-box-parent"
  let childCounter = 0
  const messages = []

  return {
    session: {
      async get(input) {
        await appendJsonl(transcriptPath, { kind: "opencode_sdk", action: "session.get", input: summarizeValue(input) })
        const id = input?.path?.id ?? parentSessionId
        return { data: { id, title: id === parentSessionId ? "Parent" : "Child", directory, agent: fixture.delegation?.target_agent ?? "lab-representative" } }
      },
      async create(input) {
        await appendJsonl(transcriptPath, { kind: "opencode_sdk", action: "session.create", input: summarizeValue(input) })
        childCounter += 1
        const childSessionId = `black-box-child-${fixture.id}-${childCounter}`
        const agent = input?.body?.agent ?? input?.agent ?? fixture.delegation?.target_agent ?? "lab-representative"
        return { data: { id: childSessionId, title: "Fixture child", parentID: parentSessionId, directory, agent } }
      },
      async prompt(input) {
        await appendJsonl(transcriptPath, { kind: "opencode_sdk", action: "session.prompt", input: summarizeValue(input) })
        const message = {
          info: {
            id: "black-box-assistant",
            role: "assistant",
            providerID: "fixture",
            modelID: "mock",
            tokens: { input: 12, output: 8 },
          },
          parts: [{ type: "text", text: fixture.delegation?.output ?? "paper-review fixture response" }],
        }
        messages.push(message)
        return { data: message }
      },
      async promptAsync(input) {
        await appendJsonl(transcriptPath, { kind: "opencode_sdk", action: "session.promptAsync", input: summarizeValue(input) })
        return { data: { queued: true } }
      },
      async messages(input) {
        await appendJsonl(transcriptPath, { kind: "opencode_sdk", action: "session.messages", input: summarizeValue(input) })
        return { data: messages }
      },
      async status(input) {
        await appendJsonl(transcriptPath, { kind: "opencode_sdk", action: "session.status", input: summarizeValue(input) })
        return { data: Object.fromEntries(Array.from({ length: childCounter }, (_value, index) => [`black-box-child-${fixture.id}-${index + 1}`, { type: "idle" }])) }
      },
    },
  }
}

async function loadBuiltModules(root) {
  const required = [
    "dist/src/index.js",
    "dist/src/config/index.js",
    "dist/src/tools/index.js",
  ]
  const missing = required.filter((file) => !existsSync(path.join(root, file)))
  if (missing.length > 0) {
    throw new Error(`Built dist files are missing: ${missing.join(", ")}. Run npm run build first.`)
  }
  const [plugin, config, tools] = await Promise.all(required.map((file) => import(pathToFileURL(path.join(root, file)))))
  return {
    createSeedAgentPlugin: plugin.createSeedAgentPlugin,
    mergeSeedConfig: config.mergeSeedConfig,
    createSeedTools: tools.createSeedTools,
  }
}

function toolContext(directory) {
  return {
    sessionID: "black-box-parent",
    messageID: "black-box-message",
    agent: "lab-representative",
    directory,
    worktree: directory,
    abort: new AbortController().signal,
    metadata() {},
    async ask() {},
  }
}

function parseToolJson(result) {
  const output = typeof result === "string" ? result : result.output
  assert.equal(typeof output, "string", "tool did not return string output")
  return JSON.parse(output)
}

function runCommonOracles(fixture, actual) {
  const { finalReport, events, latestActivity, artifactPaths, transcript } = actual
  for (const expected of fixture.oracle.final_report_contains) assert.match(finalReport, new RegExp(escapeRegex(expected)))
  for (const expected of fixture.oracle.trace_events) assert.ok(events.some((event) => event.type === expected), `missing trace event: ${expected}`)
  for (const expected of fixture.oracle.activity_contains) assert.match(latestActivity, new RegExp(escapeRegex(expected)))
  for (const expected of fixture.oracle.artifact_paths) assert.ok(artifactPaths.includes(expected), `missing artifact path: ${expected}`)
  for (const expected of fixture.oracle.transcript_contains) assert.match(transcript, new RegExp(escapeRegex(expected)))
}

async function writeFakePdfToText(root, extractedText) {
  const script = path.join(root, process.platform === "win32" ? "fake-pdftotext.cmd" : "fake-pdftotext.mjs")
  if (process.platform === "win32") {
    await writeFile(script, `@echo off\nif "%1"=="-v" (echo fake pdftotext poppler 0.0 & exit /b 0)\necho ${String(extractedText).replace(/[\r\n]+/g, " ")}\n`, "utf8")
    return script
  }
  await writeFile(
    script,
    `#!/usr/bin/env node
if (process.argv.includes("-v")) {
  console.error("fake pdftotext poppler 0.0");
  process.exit(0);
}
process.stdout.write(${JSON.stringify(String(extractedText))});
`,
    "utf8",
  )
  await chmod(script, 0o755)
  return script
}

async function listFilesIfExists(dir) {
  try {
    return await readdir(dir)
  } catch (error) {
    if (isEnoent(error)) return []
    throw error
  }
}

async function readJsonFileIfExists(file, fallback) {
  try {
    return JSON.parse(await readFile(file, "utf8"))
  } catch (error) {
    if (isEnoent(error)) return fallback
    throw error
  }
}

function assertNoSensitiveLeak(text, fixtureId) {
  assert.doesNotMatch(text, /sk-[A-Za-z0-9]{20,}/, `${fixtureId}: secret-looking OpenAI key leaked`)
  assert.doesNotMatch(text, /npm_[A-Za-z0-9]{20,}/, `${fixtureId}: npm token-looking value leaked`)
  assert.doesNotMatch(text, /\/home\/jiminhwang\/workspace\/seed-harness/, `${fixtureId}: local source checkout path leaked`)
}

function assertNoHiddenReasoning(text, fixtureId) {
  assert.doesNotMatch(text, /<thinking>|<\/thinking>|reasoning trace/i, `${fixtureId}: hidden reasoning-like text leaked`)
}

function assertBudgets(fixture, actual) {
  const budgets = fixture.budgets ?? {}
  if (budgets.max_agent_calls) assert.ok(actual.agentCalls.length <= budgets.max_agent_calls, `${fixture.id}: agent call budget exceeded`)
  if (budgets.max_model_calls) assert.ok(actual.modelCalls.length <= budgets.max_model_calls, `${fixture.id}: model call budget exceeded`)
  if (budgets.max_tool_calls) assert.ok(actual.toolCalls.length <= budgets.max_tool_calls, `${fixture.id}: tool call budget exceeded`)
}

async function createEvalCollector(options) {
  const outputDir = options.outputDir ?? process.env.SEED_BLACK_BOX_E2E_OUT
  if (!outputDir) return undefined
  await mkdir(outputDir, { recursive: true })
  const finalPath = path.join(outputDir, "black-box-results.json")
  const partialPath = path.join(outputDir, "black-box-partial.jsonl")
  const previous = existsSync(finalPath) ? JSON.parse(await readFile(finalPath, "utf8")) : undefined
  return {
    async record(summary) {
      await appendJsonl(partialPath, { recorded_at: new Date().toISOString(), summary })
    },
    async finalize(summaries) {
      const result = {
        schema_version: 1,
        generated_at: new Date().toISOString(),
        status: "passed",
        fixtures: summaries,
        comparison: previous
          ? {
              previous_generated_at: previous.generated_at,
              previous_fixture_count: previous.fixtures?.length ?? 0,
              current_fixture_count: summaries.length,
            }
          : undefined,
      }
      await writeFile(finalPath, `${JSON.stringify(result, null, 2)}\n`, "utf8")
    },
  }
}

async function readJsonl(file) {
  const text = await readFile(file, "utf8")
  return text
    .split("\n")
    .filter((line) => line.trim().length > 0)
    .map((line) => JSON.parse(line))
}

async function appendJsonl(file, value) {
  await mkdir(path.dirname(file), { recursive: true })
  await appendFile(file, `${JSON.stringify(value)}\n`, "utf8")
}

function labMeetingFeedbackOutput(sessionID, feedback) {
  const id = `feedback-${Date.now()}`
  return {
    messages: [
      {
        info: { id, role: "user", sessionID },
        parts: [
          {
            id: `${id}-part`,
            messageID: id,
            sessionID,
            type: "text",
            text: feedback,
          },
        ],
      },
    ],
  }
}

function summarizeValue(value) {
  return JSON.parse(JSON.stringify(value, (_key, current) => {
    if (typeof current === "string" && current.length > 600) return `${current.slice(0, 600)}...`
    return current
  }))
}

function textOfPart(part) {
  return part && typeof part === "object" && typeof part.text === "string" ? part.text : ""
}

function isEnoent(error) {
  return typeof error === "object" && error !== null && error.code === "ENOENT"
}

function escapeRegex(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}

if (isDirectRun()) {
  const fixturePaths = process.argv.slice(2).map((entry) => path.resolve(entry))
  runBlackBoxE2E({
    fixturePaths: fixturePaths.length > 0 ? fixturePaths : undefined,
    keep: process.env.SEED_BLACK_BOX_E2E_KEEP === "1",
  }).then((summaries) => {
    console.log(JSON.stringify({ status: "passed", fixtures: summaries }, null, 2))
  }).catch((error) => {
    console.error(error instanceof Error ? error.stack ?? error.message : String(error))
    process.exitCode = 1
  })
}

function isDirectRun() {
  return process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
}
