param(
  [int]$GatewayPort = 18889,
  [string]$GatewayToken = 'qjc-bundled-runtime-token',
  [int]$SmokePort = 4318,
  [string]$SmokeToken = 'smoke-token',
  [string]$BaseOutputDir,
  [int]$TimeoutSeconds = 900,
  [int]$StreamTimeoutSeconds = 720,
  [string]$Prompt,
  [string]$PromptBase64,
  [string]$ExpectedVideoEngine,
  [string]$ExpectedStage,
  [switch]$SkipMaterializeRuntime,
  [switch]$UseExistingCloudConfig,
  [string]$EmployeeApiKey,
  [string]$RuntimeCloudApiBaseUrl = 'https://spb-bp1wv2oe0hvfvi98.supabase.opentrust.net/functions/v1'
)

$ErrorActionPreference = 'Stop'
$script:Utf8NoBom = New-Object System.Text.UTF8Encoding $false

if ($PSBoundParameters.ContainsKey('PromptBase64') -and -not [string]::IsNullOrWhiteSpace($PromptBase64)) {
  $Prompt = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($PromptBase64))
}
elseif (-not $PSBoundParameters.ContainsKey('Prompt') -or [string]::IsNullOrWhiteSpace($Prompt)) {
  $Prompt = [System.Text.Encoding]::UTF8.GetString(
    [System.Convert]::FromBase64String('5biu5oiR5YGa5LiA5Liq5YWz5LqO6Ziy5pmS5Za36Zu+55qE5oqW6Z+z6KeG6aKR5paH5qGI77yM5Y+X5LyX5piv6YCa5Yuk5aWz55Sf77yM5YGa5oiQ5pWw5a2X5Lq65Y+j5pKt77yM6aOO5qC857K+6Ie05bm/5ZGK5oSf77yM5pe26ZW/MTXnp5LjgII=')
  )
}

function Resolve-ExpectedVideoEngine {
  param(
    [string]$PromptText,
    [string]$ExplicitValue
  )

  if (-not [string]::IsNullOrWhiteSpace($ExplicitValue)) {
    return $ExplicitValue.Trim().ToLowerInvariant()
  }

  return 'auto'
}

function Resolve-ExpectedStage {
  param(
    [string]$PromptText,
    [string]$ExplicitValue
  )

  if (-not [string]::IsNullOrWhiteSpace($ExplicitValue)) {
    return $ExplicitValue.Trim()
  }

  return 'preview_ready'
}

$ExpectedVideoEngine = Resolve-ExpectedVideoEngine -PromptText $Prompt -ExplicitValue $ExpectedVideoEngine
$ExpectedStage = Resolve-ExpectedStage -PromptText $Prompt -ExplicitValue $ExpectedStage
$shouldAttachImage = $ExpectedVideoEngine -eq 'omnihuman'

function Write-Utf8File {
  param([string]$filePath, [string]$content)
  [System.IO.File]::WriteAllText($filePath, $content, $script:Utf8NoBom)
}

function Write-Base64File {
  param(
    [string]$FilePath,
    [string]$Base64
  )

  $parent = Split-Path -Parent $FilePath
  if ($parent) {
    New-Item -ItemType Directory -Force -Path $parent | Out-Null
  }
  [System.IO.File]::WriteAllBytes($FilePath, [System.Convert]::FromBase64String($Base64))
}

function Copy-ProjectBundleSource {
  param(
    [string]$SourceRoot,
    [string]$DestinationRoot
  )

  $projectJsonPath = Join-Path $SourceRoot 'project.json'
  $excludePaths = @()
  if (Test-Path $projectJsonPath) {
    $projectConfig = Get-Content -Path $projectJsonPath -Raw | ConvertFrom-Json
    $configuredExcludes = $projectConfig.bundlePackaging.excludePaths
    if ($configuredExcludes) {
      $excludePaths = @($configuredExcludes | ForEach-Object { [string]$_ })
    }
  }
  $excludeSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
  foreach ($entry in $excludePaths) {
    if ($entry) {
      [void]$excludeSet.Add($entry)
    }
  }

  New-Item -ItemType Directory -Force -Path $DestinationRoot | Out-Null
  foreach ($entry in Get-ChildItem -LiteralPath $SourceRoot -Force) {
    if ($excludeSet.Contains($entry.Name)) {
      continue
    }
    if ($entry.PSIsContainer -and $entry.Name -match '^tmp[a-z0-9]{6,}$') {
      Write-Warning "Skipping transient temp directory from bundle source: $($entry.FullName)"
      continue
    }
    try {
      Copy-Item -LiteralPath $entry.FullName -Destination (Join-Path $DestinationRoot $entry.Name) -Recurse -Force
    }
    catch [System.UnauthorizedAccessException] {
      Write-Warning "Skipping inaccessible bundle source entry: $($entry.FullName)"
      continue
    }
  }
}

function Reset-DouyinLiveRunBundleState {
  param(
    [string]$ProjectRoot
  )

  $outputRoot = Join-Path $ProjectRoot 'memory\output'
  if (Test-Path $outputRoot) {
    Remove-Item -LiteralPath $outputRoot -Recurse -Force -ErrorAction SilentlyContinue
  }
  $pendingRoot = Join-Path $ProjectRoot 'memory\output\pending_intake'
  if (Test-Path $pendingRoot) {
    Remove-Item -LiteralPath $pendingRoot -Recurse -Force -ErrorAction SilentlyContinue
  }
  New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
}

function New-ExpertFixtureProject {
  param(
    [string]$ProjectsRoot,
    [string]$ProjectId,
    [string]$ProjectName,
    [string]$Platform,
    [string]$Description,
    [string]$UpdatedAt
  )

  $projectRoot = Join-Path $ProjectsRoot $ProjectId
  New-Item -ItemType Directory -Force -Path $projectRoot, (Join-Path $projectRoot 'memory') | Out-Null

  $projectPayload = [ordered]@{
    id = $ProjectId
    name = $ProjectName
    description = $Description
    platform = $Platform
    ready = $true
    updatedAt = $UpdatedAt
    boundSkillIds = @()
    workspaceEntryEnabled = $true
  }
  Write-Utf8File (Join-Path $projectRoot 'project.json') ($projectPayload | ConvertTo-Json -Depth 8)
  Write-Utf8File (Join-Path $projectRoot 'README.md') "# $ProjectName`n`nFixture expert project for UI live-run coverage."
  Write-Utf8File (Join-Path $projectRoot 'AGENTS.md') "# $ProjectName`n`nThis is a passive fixture expert used for desktop UI live-run coverage."
}

function Initialize-LiveRunCloudConfig {
  param(
    [string]$UserDataPath,
    [string]$EmployeeApiKey,
    [string]$RuntimeCloudApiBaseUrl
  )

  if ([string]::IsNullOrWhiteSpace($EmployeeApiKey)) {
    return
  }

  $configRoot = Join-Path $UserDataPath 'config'
  New-Item -ItemType Directory -Force -Path $configRoot | Out-Null

  $appConfigPath = Join-Path $configRoot 'app-config.json'
  $secretsPath = Join-Path $configRoot 'secrets.dev.json'

  $appConfig = [ordered]@{
    setupMode = 'employee-key'
    provider = 'openai'
    baseUrl = 'https://api.openai.com/v1'
    apiKeyConfigured = $true
    gatewayTokenConfigured = $false
    authTokenConfigured = $false
    defaultModel = 'gpt-5.4-mini'
    workspacePath = $UserDataPath
    gatewayUrl = 'ws://127.0.0.1:18789'
    cloudApiBaseUrl = ''
    runtimeCloudApiBaseUrl = $RuntimeCloudApiBaseUrl
    runtimeMode = 'bundled-runtime'
  }
  Write-Utf8File $appConfigPath ($appConfig | ConvertTo-Json -Depth 8)

  $secretPayload = [ordered]@{
    note = 'Development fallback only. Replace this file-based secret store with keytar before shipping.'
    apiKey = $EmployeeApiKey
  }
  Write-Utf8File $secretsPath ($secretPayload | ConvertTo-Json -Depth 6)
}

function Invoke-ElectronSmokeWithRetry {
  param(
    [string]$ScriptPath,
    [string]$Label,
    [string[]]$ArgumentList,
    [int]$MaxAttempts = 2
  )

  for ($attempt = 1; $attempt -le $MaxAttempts; $attempt += 1) {
    powershell -ExecutionPolicy Bypass -File $ScriptPath @ArgumentList
    if ($LASTEXITCODE -eq 0) {
      return
    }
    if ($attempt -ge $MaxAttempts) {
      exit $LASTEXITCODE
    }
    Write-Warning "$Label failed on attempt $attempt. Retrying..."
    Start-Sleep -Seconds 2
  }
}

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if (-not $BaseOutputDir) {
  $BaseOutputDir = Join-Path $repoRoot '.tmp\douyin-expert-live-run'
}
$BaseOutputDir = [System.IO.Path]::GetFullPath($BaseOutputDir)
$userDataPath = Join-Path $BaseOutputDir 'user-data'
$logsPath = Join-Path $BaseOutputDir 'logs'
$smokeOutput = Join-Path $BaseOutputDir 'result.json'
$bundleSourceRoot = Join-Path $BaseOutputDir 'bundle-src'
$bundleZipPath = Join-Path $BaseOutputDir 'douyin-expert-live-run.zip'
$bundleFileName = 'douyin-expert-live-run.zip'
$bundleProjectId = 'douyin'
$bundleProjectName = 'Douyin Automation'
$bundleSkillId = 'douyin-project-bundle'
$bundleConfigVersion = '2026-04-13T16:00:00.000Z'
$expectedBundleSourceUrl = "http://127.0.0.1:$SmokePort/downloads/$bundleFileName"
$expectedExpertIds = @('browser-expert-smoke', 'douyin', 'xhs-expert-smoke')
$electronSmokeScript = Join-Path $repoRoot 'build\scripts\electron-smoke.ps1'
$attachmentFixturePath = Join-Path $BaseOutputDir 'fixtures\portrait.png'
$expectedAttachmentDir = Join-Path $userDataPath 'projects\douyin\inputs\images\main'
$attachmentFixtureName = 'portrait.png'
$attachmentPayload = @()
if ($shouldAttachImage) {
  $attachmentPayload = @(
    @{
      kind = 'image'
      name = $attachmentFixtureName
      mimeType = 'image/png'
      localPath = $attachmentFixturePath
    }
  )
}
$latestProjectAliasPath = Join-Path $userDataPath 'projects\douyin\memory\output\_latest_project_path.txt'
$latestSummaryAliasPath = Join-Path $userDataPath 'projects\douyin\memory\output\_latest_workflow_summary.json'
$waitPaths = @($latestProjectAliasPath, $latestSummaryAliasPath)
$douyinSourceCandidates = @(
  (Join-Path $repoRoot 'workspace\douyin')
)
$douyinSourceRoot = $douyinSourceCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1

if (Test-Path $BaseOutputDir) {
  if ($UseExistingCloudConfig) {
    Remove-Item $bundleSourceRoot -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item $logsPath -Recurse -Force -ErrorAction SilentlyContinue
    Remove-Item $smokeOutput -Force -ErrorAction SilentlyContinue
    Remove-Item $bundleZipPath -Force -ErrorAction SilentlyContinue
    Remove-Item $attachmentFixturePath -Force -ErrorAction SilentlyContinue
  }
  else {
    Remove-Item $BaseOutputDir -Recurse -Force -ErrorAction SilentlyContinue
  }
}
New-Item -ItemType Directory -Force -Path $BaseOutputDir, $bundleSourceRoot, $userDataPath, $logsPath | Out-Null

if (-not $douyinSourceRoot) {
  throw "Douyin workspace source was not found in any expected location: $($douyinSourceCandidates -join ', ')"
}

if ($shouldAttachImage) {
  Write-Base64File -FilePath $attachmentFixturePath -Base64 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVHhe7dAxAQAwDITA+tf4XlIDOIDhFkbetjNrAEWTBlA0aQBFkwZQNGkARZMGUDRpAEWTBlA0aQBFkwZQNGkARZMGUDRpAEWTBlA0aQBFkwZQNGkARZMGUDRpAEWTBlA0aQBFkwZQNGkARZMGUDRpAEWTBlA0aQBFkwZQNGkARZMGUDRpAEUT+YDdB6OSM1gxG4BEAAAAAElFTkSuQmCC'
}
Copy-ProjectBundleSource -SourceRoot $douyinSourceRoot -DestinationRoot (Join-Path $bundleSourceRoot 'douyin')
Reset-DouyinLiveRunBundleState -ProjectRoot (Join-Path $bundleSourceRoot 'douyin')
if (Test-Path $bundleZipPath) {
  Remove-Item $bundleZipPath -Force
}
Compress-Archive -Path (Join-Path $bundleSourceRoot 'douyin') -DestinationPath $bundleZipPath -Force

$projectsRoot = Join-Path $userDataPath 'projects'
$manifestsRoot = Join-Path $userDataPath 'manifests'
New-Item -ItemType Directory -Force -Path $projectsRoot, $manifestsRoot | Out-Null
New-ExpertFixtureProject -ProjectsRoot $projectsRoot -ProjectId 'xhs-expert-smoke' -ProjectName 'XHS Expert Fixture' -Platform 'xiaohongshu' -Description 'Fixture project that keeps the experts rail above two items.' -UpdatedAt '2026-04-03T00:00:00.000Z'
New-ExpertFixtureProject -ProjectsRoot $projectsRoot -ProjectId 'browser-expert-smoke' -ProjectName 'Browser Expert Fixture' -Platform 'browser' -Description 'Fixture project that keeps the experts rail above two items.' -UpdatedAt '2026-04-03T00:01:00.000Z'
Write-Utf8File (Join-Path $manifestsRoot 'active-project.json') (@{ projectId = 'browser-expert-smoke' } | ConvertTo-Json -Depth 3)
Initialize-LiveRunCloudConfig -UserDataPath $userDataPath -EmployeeApiKey $EmployeeApiKey -RuntimeCloudApiBaseUrl $RuntimeCloudApiBaseUrl

if (-not $SkipMaterializeRuntime) {
  Write-Host "Materializing bundled runtime payload on port $GatewayPort"
  powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\materialize-runtime-payload.ps1') -GatewayPort $GatewayPort -GatewayToken $GatewayToken
  if ($LASTEXITCODE -ne 0) {
    exit $LASTEXITCODE
  }
}

$env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH = $bundleZipPath
$env:QJCLAW_SMOKE_BUNDLE_FILE_NAME = $bundleFileName
$env:QJCLAW_SMOKE_BUNDLE_SKILL_ID = $bundleSkillId
$env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE = 'Douyin Project Bundle'
$env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION = 'Zip-backed Douyin project bundle for expert-page live-run validation.'
$env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersion
if ($attachmentPayload.Count -gt 0) {
  $env:QJCLAW_SMOKE_ATTACHMENTS_JSON = (ConvertTo-Json -InputObject @($attachmentPayload) -Depth 5 -Compress)
}
else {
  Remove-Item Env:QJCLAW_SMOKE_ATTACHMENTS_JSON -ErrorAction SilentlyContinue
}
$env:QJCLAW_SMOKE_STREAM_TIMEOUT_MS = ([int]$StreamTimeoutSeconds * 1000).ToString()
$env:QJCLAW_SMOKE_ACCEPT_WORKSPACE_LAUNCH = '1'
$env:QJCLAW_SMOKE_WAIT_FOR_PATHS = [string]::Join([System.IO.Path]::PathSeparator, $waitPaths)
$env:QJCLAW_SMOKE_WAIT_FOR_PATHS_TIMEOUT_MS = ([int]$StreamTimeoutSeconds * 1000).ToString()
$env:QJCLAW_SMOKE_WAIT_FOR_EXPECTED_STAGE = $ExpectedStage
$env:QJCLAW_DISABLE_LOCAL_OPENCLAW_GATEWAY = '1'

try {
  $electronSmokeArguments = @(
    '-SmokeOutput', $smokeOutput,
    '-SmokePort', $SmokePort,
    '-SmokeToken', $SmokeToken,
    '-UserDataPath', $userDataPath,
    '-LogsPath', $logsPath,
    '-RuntimeMode', 'bundled-runtime',
    '-ExpectBundledRuntime',
    '-ExpectWorkspaceEntry',
    '-PreserveUserData',
    '-WorkspaceProjectId', $bundleProjectId,
    '-WorkspaceProjectName', $bundleProjectName,
    '-WorkspaceMarkerFile', 'AGENTS.md',
    '-SmokePrompt', $Prompt,
    '-SmokeViewMode', 'experts',
    '-SmokeProjectId', $bundleProjectId,
    '-TimeoutSeconds', $TimeoutSeconds
  )
  if ($UseExistingCloudConfig) {
    $electronSmokeArguments += '-UseExistingCloudConfig'
  }

  Invoke-ElectronSmokeWithRetry -ScriptPath $electronSmokeScript -Label 'douyin expert live run' -ArgumentList $electronSmokeArguments

  $summary = & node -e @"
const fs = require('fs');
const path = require('path');

const [
  smokeOutput,
  userDataPath,
  expectedBundleSourceUrl,
  expectedBundleConfigVersion,
  expectedBundleFileName,
  expectedBundleSkillId,
  expectedPrompt,
  expectedExpertIdsCsv,
  expectedVideoEngine,
  expectedStage,
  expectAttachmentFlag
] = process.argv.slice(1);

const result = JSON.parse(fs.readFileSync(smokeOutput, 'utf8'));
if (!result.ok) {
  throw new Error(result.error || 'Live run failed.');
}

const finalState = result.finalState || {};
const sendResult = result.sendResult || {};
const streamSmoke = sendResult.streamSmoke || {};
const workspaceSummary = finalState.workspaceSummary || {};
const nonHomeProjects = Array.isArray(workspaceSummary.projects)
  ? workspaceSummary.projects.filter((project) => !project.isBuiltinHome)
  : [];
const expertProjectIds = Array.isArray(finalState.expertProjectIds)
  ? finalState.expertProjectIds.map((value) => String(value || '')).sort()
  : [];
const expectedExpertIds = expectedExpertIdsCsv.split(',').map((value) => value.trim()).filter(Boolean).sort();
const expectAttachment = String(expectAttachmentFlag || '') === '1';
const assistantContent = String(
  (sendResult.lastAssistantMessage && sendResult.lastAssistantMessage.content)
  || streamSmoke.finalContent
  || streamSmoke.renderedContent
  || ''
);
const statusLabels = Array.isArray(streamSmoke.statusLabels)
  ? streamSmoke.statusLabels.map((value) => String(value || ''))
  : [];

function requireFile(filePath, message) {
  if (!fs.existsSync(filePath)) {
    throw new Error(message + ': ' + filePath);
  }
  return filePath;
}

function requirePathInside(rootPath, candidatePath, label) {
  const normalizedRoot = path.normalize(rootPath + path.sep);
  const normalizedCandidate = path.normalize(String(candidatePath || ''));
  if (!normalizedCandidate.startsWith(normalizedRoot)) {
    throw new Error(label + ' escaped expected root: ' + normalizedCandidate);
  }
  return normalizedCandidate;
}

const bundleManifestPath = path.join(userDataPath, 'manifests', 'project-bundles.json');
requireFile(bundleManifestPath, 'Bundle manifest was not produced');
const bundleManifest = JSON.parse(fs.readFileSync(bundleManifestPath, 'utf8'));
const manifestRecord = bundleManifest.douyin;
if (!manifestRecord || typeof manifestRecord !== 'object') {
  throw new Error('Bundle manifest does not contain douyin.');
}
if (String(manifestRecord.sourceUrl || '') !== expectedBundleSourceUrl) {
  throw new Error('Unexpected bundle sourceUrl: ' + String(manifestRecord.sourceUrl || ''));
}
if (String(manifestRecord.configVersion || '') !== expectedBundleConfigVersion) {
  throw new Error('Unexpected bundle configVersion: ' + String(manifestRecord.configVersion || ''));
}
if (String(manifestRecord.fileName || '') !== expectedBundleFileName) {
  throw new Error('Unexpected bundle fileName: ' + String(manifestRecord.fileName || ''));
}
if (String(manifestRecord.sourceSkillId || '') !== expectedBundleSkillId) {
  throw new Error('Unexpected bundle sourceSkillId: ' + String(manifestRecord.sourceSkillId || ''));
}

if (String(finalState.viewMode || '') !== 'experts') {
  throw new Error('Live run did not remain on experts view.');
}
if (String(sendResult.smokeViewMode || '') !== 'experts') {
  throw new Error('Live run request was not issued from experts mode.');
}
if (String(sendResult.smokeProjectId || '') !== 'douyin') {
  throw new Error('Live run request did not target the douyin expert.');
}
if (String(sendResult.prompt || '') !== expectedPrompt) {
  throw new Error('Live run prompt mismatch.');
}
if (String(workspaceSummary.currentProjectId || '') !== 'douyin') {
  throw new Error('Final active project was not douyin: ' + String(workspaceSummary.currentProjectId || ''));
}
if (expertProjectIds.length !== expectedExpertIds.length || expertProjectIds.some((value, index) => value !== expectedExpertIds[index])) {
  throw new Error('Expert list mismatch. actual=' + JSON.stringify(expertProjectIds) + ' expected=' + JSON.stringify(expectedExpertIds));
}
if (nonHomeProjects.length < 3) {
  throw new Error('Workspace summary did not expose at least three non-home projects.');
}
if (!String(sendResult.sessionId || '').startsWith('project:douyin:')) {
  throw new Error('Live run session did not bind to douyin: ' + String(sendResult.sessionId || ''));
}

const workspaceLaunchAccepted = statusLabels.some((label) => label.includes('Launching project workspace'));
if (String(streamSmoke.phase || '') !== 'completed' && !workspaceLaunchAccepted) {
  throw new Error('Live run stream did not complete: ' + String(streamSmoke.phase || ''));
}
if (String(sendResult.selectedSkillId || streamSmoke.selectedSkillId || '')) {
  throw new Error('Live run unexpectedly selected a skill instead of workspace-entry: ' + String(sendResult.selectedSkillId || streamSmoke.selectedSkillId || ''));
}
if (statusLabels.some((label) => label.includes('Routing to skill'))) {
  throw new Error('Live run still routed through a skill: ' + JSON.stringify(statusLabels));
}
if (!workspaceLaunchAccepted) {
  throw new Error('Live run did not expose a workspace-entry launch status: ' + JSON.stringify(statusLabels));
}
if (!assistantContent.includes(String(expectedStage || ''))) {
  throw new Error('Assistant content did not report expected stage: ' + String(expectedStage || ''));
}
for (const requiredHint of ['video_request.json', 'storyboard.md', '_latest_workflow_summary.json']) {
  if (!assistantContent.includes(requiredHint)) {
    throw new Error('Assistant content did not expose required artifact hint: ' + requiredHint);
  }
}

const outputRoot = path.join(userDataPath, 'projects', 'douyin', 'memory', 'output');
const latestProjectAliasPath = path.join(outputRoot, '_latest_project_path.txt');
const latestSummaryAliasPath = path.join(outputRoot, '_latest_workflow_summary.json');
requireFile(latestProjectAliasPath, 'Live run did not write _latest_project_path.txt');
requireFile(latestSummaryAliasPath, 'Live run did not write _latest_workflow_summary.json');

const latestProjectPath = String(fs.readFileSync(latestProjectAliasPath, 'utf8')).trim();
if (!latestProjectPath) {
  throw new Error('Live run wrote an empty latest project path.');
}
const normalizedLatestProjectPath = requirePathInside(outputRoot, latestProjectPath, 'Latest project path');
if (!fs.existsSync(normalizedLatestProjectPath) || !fs.statSync(normalizedLatestProjectPath).isDirectory()) {
  throw new Error('Latest project path is not a directory: ' + normalizedLatestProjectPath);
}

const workflowSummaryPath = requireFile(path.join(normalizedLatestProjectPath, 'workflow_summary.json'), 'Missing workflow summary');
const videoRequestPath = requireFile(path.join(normalizedLatestProjectPath, 'video_request.json'), 'Missing video request');
const workflowSummary = JSON.parse(fs.readFileSync(workflowSummaryPath, 'utf8'));
const latestWorkflowSummary = JSON.parse(fs.readFileSync(latestSummaryAliasPath, 'utf8'));
if (String(workflowSummary.stage || '') !== String(expectedStage || '')) {
  throw new Error('Expected workflow summary stage ' + String(expectedStage || '') + ', received: ' + String(workflowSummary.stage || ''));
}
if (String(latestWorkflowSummary.stage || '') !== String(expectedStage || '')) {
  throw new Error('Latest workflow summary alias did not reach expected stage ' + String(expectedStage || '') + '.');
}

const startedAtValue = Date.parse(String(result.startedAt || ''));
const summaryTimestampValue = Date.parse(String(workflowSummary.timestamp || latestWorkflowSummary.timestamp || ''));
if (Number.isFinite(startedAtValue) && Number.isFinite(summaryTimestampValue) && summaryTimestampValue + 1000 < startedAtValue) {
  throw new Error('Workflow summary timestamp predates smoke start. startedAt=' + String(result.startedAt || '') + ' summary=' + String(workflowSummary.timestamp || latestWorkflowSummary.timestamp || ''));
}

const videoRequest = JSON.parse(fs.readFileSync(videoRequestPath, 'utf8'));
const selectedVideoEngine = String(videoRequest.selected_video_engine || '');
if (String(expectedVideoEngine || '') !== 'auto' && selectedVideoEngine !== String(expectedVideoEngine || '')) {
  throw new Error('Unexpected selected_video_engine. actual=' + selectedVideoEngine + ' expected=' + String(expectedVideoEngine || ''));
}

let finalVideoPath = null;
let videoStatusPath = null;
let videoSummaryPath = null;
if (String(expectedStage || '') === 'video_generated') {
  videoStatusPath = requireFile(path.join(normalizedLatestProjectPath, 'video_generation_status.json'), 'Missing video generation status');
  const videoStatus = JSON.parse(fs.readFileSync(videoStatusPath, 'utf8'));
  if (String(videoStatus.status || '') !== 'success') {
    throw new Error('Video generation status is not success: ' + JSON.stringify(videoStatus));
  }
  if (selectedVideoEngine === 'seedance') {
    videoSummaryPath = requireFile(path.join(normalizedLatestProjectPath, 'latest_seedance_split_summary.json'), 'Missing seedance split summary');
    const splitSummary = JSON.parse(fs.readFileSync(videoSummaryPath, 'utf8'));
    finalVideoPath = requirePathInside(normalizedLatestProjectPath, String(splitSummary.latest_project_video || splitSummary.final_video_path || ''), 'latest seedance split video');
    requireFile(finalVideoPath, 'Missing final generated video');
  }
}

const commonArtifacts = [
  ['storyboard_file', videoRequest.storyboard_file],
  ['storyboard_markdown_file', videoRequest.storyboard_markdown_file],
  ['copywriter_text_file', videoRequest.copywriter_text_file],
  ['storyboard_text_file', videoRequest.storyboard_text_file],
  ['script_file', videoRequest.script_file],
  ['structured_script_file', videoRequest.structured_script_file]
];
const engineSpecificArtifacts = [];
if (selectedVideoEngine === 'omnihuman') {
  engineSpecificArtifacts.push(['omnihuman_prompt_file', videoRequest.omnihuman_prompt_file]);
}
if (selectedVideoEngine === 'seedance') {
  engineSpecificArtifacts.push(['seedance_prompt_file', videoRequest.seedance_prompt_file]);
}
const validatedArtifacts = [];
for (const [label, artifactPathValue] of commonArtifacts.concat(engineSpecificArtifacts)) {
  const artifactPath = requirePathInside(normalizedLatestProjectPath, String(artifactPathValue || ''), label);
  requireFile(artifactPath, 'Missing artifact for ' + label);
  validatedArtifacts.push({ label, path: artifactPath });
}

const smokeAttachments = Array.isArray(sendResult.smokeAttachments) ? sendResult.smokeAttachments : [];
let workspaceAttachmentPath = null;
let workspaceAttachmentRelativePath = null;
let projectMainImagePath = null;
if (expectAttachment) {
  if (smokeAttachments.length !== 1) {
    throw new Error('Expected exactly one smoke attachment. actual=' + smokeAttachments.length);
  }
  if (!assistantContent.includes('Project attachments:')) {
    throw new Error('Assistant content did not echo project attachments.');
  }
  const assistantAttachmentMatch = assistantContent.match(/inputs\/images\/main\/[^\r\n]+/);
  if (!assistantAttachmentMatch) {
    throw new Error('Assistant content did not expose a materialized attachment path.');
  }
  workspaceAttachmentRelativePath = assistantAttachmentMatch[0].trim();
  workspaceAttachmentPath = path.join(userDataPath, 'projects', 'douyin', ...workspaceAttachmentRelativePath.split('/'));
  requireFile(workspaceAttachmentPath, 'Workspace attachment file was not found');
  const workspaceAttachmentStat = fs.statSync(workspaceAttachmentPath);
  if (!workspaceAttachmentStat.isFile() || workspaceAttachmentStat.size < 1) {
    throw new Error('Workspace attachment file is empty: ' + workspaceAttachmentPath);
  }
  if (!assistantContent.includes(workspaceAttachmentRelativePath)) {
    throw new Error('Assistant content did not reference the materialized workspace attachment path: ' + workspaceAttachmentRelativePath);
  }
  const projectInputs = videoRequest.project_inputs || {};
  projectMainImagePath = requirePathInside(normalizedLatestProjectPath, String(projectInputs.main_image || ''), 'project_inputs.main_image');
  requireFile(projectMainImagePath, 'project_inputs.main_image was not created');
  const latestProjectImagePathHint = path.join(path.dirname(projectMainImagePath), '_latest_path.txt');
  requireFile(latestProjectImagePathHint, 'Project inputs main image directory is missing _latest_path.txt');
  const latestProjectImagePath = path.normalize(String(fs.readFileSync(latestProjectImagePathHint, 'utf8')).trim());
  if (latestProjectImagePath !== projectMainImagePath) {
    throw new Error('Project inputs latest image pointer mismatch. actual=' + latestProjectImagePath + ' expected=' + projectMainImagePath);
  }
} else if (smokeAttachments.length !== 0) {
  throw new Error('Did not expect smoke attachments. actual=' + smokeAttachments.length);
}

console.log(JSON.stringify({
  ok: true,
  smokeOutput,
  prompt: sendResult.prompt || null,
  currentProjectId: workspaceSummary.currentProjectId || null,
  currentProjectName: workspaceSummary.currentProjectName || null,
  expertProjectIds,
  nonHomeProjectCount: nonHomeProjects.length,
  executionPolicySource: streamSmoke.executionPolicySource || null,
  sessionId: sendResult.sessionId || null,
  latestProjectPath: normalizedLatestProjectPath,
  workflowSummaryPath,
  selectedVideoEngine,
  expectedVideoEngine,
  expectedStage,
  finalVideoPath,
  videoStatusPath,
  videoSummaryPath,
  smokeAttachmentCount: smokeAttachments.length,
  workspaceAttachmentPath,
  workspaceAttachmentRelativePath,
  projectMainImagePath,
  artifactCount: validatedArtifacts.length,
  statusLabels,
  bundleManifestPath
}, null, 2));
"@ $smokeOutput $userDataPath $expectedBundleSourceUrl $bundleConfigVersion $bundleFileName $bundleSkillId $Prompt ($expectedExpertIds -join ',') $ExpectedVideoEngine $ExpectedStage ($(if ($shouldAttachImage) { '1' } else { '0' }))
  if ($LASTEXITCODE -ne 0) {
    exit $LASTEXITCODE
  }
  Write-Output $summary
}
finally {
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_FILE_NAME -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_ID -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_ATTACHMENTS_JSON -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_STREAM_TIMEOUT_MS -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_ACCEPT_WORKSPACE_LAUNCH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_WAIT_FOR_PATHS -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_WAIT_FOR_PATHS_TIMEOUT_MS -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_WAIT_FOR_EXPECTED_STAGE -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_DISABLE_LOCAL_OPENCLAW_GATEWAY -ErrorAction SilentlyContinue
}
