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

$ErrorActionPreference = 'Stop'

if (-not $PSBoundParameters.ContainsKey('Prompt') -or [string]::IsNullOrWhiteSpace($Prompt)) {
  $Prompt = [System.Text.Encoding]::UTF8.GetString(
    [System.Convert]::FromBase64String('5biu5oiR5YGa5LiA56+H5YWz5LqO6Ziy5pmS5Za36Zu+55qE5bCP57qi5Lmm5Zu+5paH56yU6K6w5paH5qGI77yM5Y+X5LyX5piv6YCa5Yuk5aWz55Sf77yM6aOO5qC85bm85a6e55So77yM5YGa5oiQ5Zu+5paH56yU6K6w')
  )
}

function Write-Utf8File {
  param([string]$filePath, [string]$content)
  $encoding = New-Object System.Text.UTF8Encoding $false
  [System.IO.File]::WriteAllText($filePath, $content, $encoding)
}

function Get-XhsVolcesImageProviderConfig {
  $modelId = 'doubao-seedream-5-0-260128'
  return [ordered]@{
    providerName = 'volces'
    modelId = $modelId
    providers = [ordered]@{
      volces = [ordered]@{
        baseUrl = 'https://ark.cn-beijing.volces.com/api/v3/images/generations'
        apiKey = 'c434d7a4-ac38-48c4-9d50-ce0e3c9aaf6b'
        api = 'openai-completions'
        models = @(
          [ordered]@{
            id = $modelId
            name = 'volces-gen-image'
            reasoning = $false
            input = @('text', 'image')
            cost = [ordered]@{
              input = 0
              output = 0
              cacheRead = 0
              cacheWrite = 0
            }
            contextWindow = 128000
            maxTokens = 81920
          }
        )
      }
    }
  }
}

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 Install-XhsNodeDependencies {
  param([string]$ProjectRoot)

  $packageJsonPath = Join-Path $ProjectRoot 'package.json'
  $packageLockPath = Join-Path $ProjectRoot 'package-lock.json'
  if (-not (Test-Path $packageJsonPath) -or -not (Test-Path $packageLockPath)) {
    throw "XHS bundle staging directory is missing package.json or package-lock.json: $ProjectRoot"
  }

  Write-Host "Installing XHS Node dependencies in staging directory: $ProjectRoot"
  Push-Location $ProjectRoot
  try {
    npm ci --omit=dev --no-audit --fund=false
    if ($LASTEXITCODE -ne 0) {
      throw "npm ci failed while preparing XHS bundle staging directory."
    }
  } finally {
    Pop-Location
  }
}

function Assert-XhsBundleNodeDependencies {
  param([string]$ZipPath)

  Add-Type -AssemblyName System.IO.Compression.FileSystem
  $archive = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
  try {
    $entries = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($entry in $archive.Entries) {
      [void]$entries.Add($entry.FullName.Replace('\', '/'))
    }
    foreach ($requiredEntry in @(
      'xhs/node_modules/crypto-js/package.json',
      'xhs/node_modules/jsdom/package.json'
    )) {
      if (-not $entries.Contains($requiredEntry)) {
        throw "XHS bundle zip is missing required Node dependency entry: $requiredEntry"
      }
    }
  } finally {
    $archive.Dispose()
  }
}

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

  $memoryRoot = Join-Path $ProjectRoot 'memory'
  foreach ($relativePath in @(
    'openclaw_runs',
    'generated_images',
    'xhs_profile',
    'project.env'
  )) {
    $fullPath = Join-Path $memoryRoot $relativePath
    if (Test-Path $fullPath) {
      Remove-Item -LiteralPath $fullPath -Recurse -Force -ErrorAction SilentlyContinue
    }
  }
}

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\xhs-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 'xhs-expert-live-run.zip'
$bundleFileName = 'xhs-expert-live-run.zip'
$bundleProjectId = 'xhs'
$bundleProjectName = 'xiaohongshu-writer'
$bundleSkillId = 'xhs-project-bundle'
$bundleConfigVersion = '2026-04-13T18:30:00.000Z'
$expectedBundleSourceUrl = "http://127.0.0.1:$SmokePort/downloads/$bundleFileName"
$expectedExpertIds = @('browser-expert-smoke', 'douyin-expert-smoke', 'xhs')
$electronSmokeScript = Join-Path $repoRoot 'build\scripts\electron-smoke.ps1'
$attachmentFixturePath = Join-Path $BaseOutputDir 'fixtures\portrait.png'
$attachmentFixtureName = 'portrait.png'
$attachmentPayload = @(
  @{
    kind = 'image'
    name = $attachmentFixtureName
    mimeType = 'image/png'
    localPath = $attachmentFixturePath
  }
)
$latestNotePath = Join-Path $userDataPath 'projects\xhs\memory\openclaw_runs\xhs_last_note.json'
$xhsProfilePath = Join-Path $userDataPath 'projects\xhs\memory\xhs_profile'
$waitPaths = @($latestNotePath, $xhsProfilePath)
$xhsSourceCandidates = @(
  (Join-Path $repoRoot 'workspace\xhs')
)
$xhsSourceRoot = $xhsSourceCandidates | 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 $xhsSourceRoot) {
  throw "XHS workspace source was not found in any expected location: $($xhsSourceCandidates -join ', ')"
}

Write-Base64File -FilePath $attachmentFixturePath -Base64 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aKJwAAAAASUVORK5CYII='
$stagedXhsRoot = Join-Path $bundleSourceRoot 'xhs'
Copy-ProjectBundleSource -SourceRoot $xhsSourceRoot -DestinationRoot $stagedXhsRoot
Install-XhsNodeDependencies -ProjectRoot $stagedXhsRoot
Reset-XhsLiveRunBundleState -ProjectRoot $stagedXhsRoot
if (Test-Path $bundleZipPath) {
  Remove-Item $bundleZipPath -Force
}
Compress-Archive -Path (Join-Path $bundleSourceRoot 'xhs') -DestinationPath $bundleZipPath -Force
Assert-XhsBundleNodeDependencies -ZipPath $bundleZipPath

$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 'douyin-expert-smoke' -ProjectName 'Douyin Expert Fixture' -Platform 'douyin' -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 = 'XHS Project Bundle'
$env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION = 'Zip-backed Xiaohongshu project bundle for expert-page live-run validation.'
$env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersion
$env:QJCLAW_SMOKE_ATTACHMENTS_JSON = (ConvertTo-Json -InputObject @($attachmentPayload) -Depth 5 -Compress)
$xhsImageProviderConfig = Get-XhsVolcesImageProviderConfig
$env:QJCLAW_EXTRA_MODEL_PROVIDERS_JSON = ($xhsImageProviderConfig.providers | ConvertTo-Json -Depth 10 -Compress)
$env:XHS_IMAGE_PROVIDER = $xhsImageProviderConfig.providerName
$env:XHS_IMAGE_MODEL = $xhsImageProviderConfig.modelId
$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_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 'xhs expert live run' -ArgumentList $electronSmokeArguments -MaxAttempts 1

  $summary = & node -e @"
const fs = require('fs');
const path = require('path');
const [smokeOutput, userDataPath, expectedBundleSourceUrl, expectedBundleConfigVersion, expectedBundleFileName, expectedBundleSkillId, expectedPrompt, expectedExpertIdsCsv] = process.argv.slice(1);
const result = JSON.parse(fs.readFileSync(smokeOutput, 'utf8'));
if (!result.ok) {
  throw new Error(result.error || 'Live run failed.');
}
const expectedExpertIds = expectedExpertIdsCsv.split(',').map((value) => value.trim()).filter(Boolean).sort();
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 bundleManifestPath = path.join(userDataPath, 'manifests', 'project-bundles.json');
if (!fs.existsSync(bundleManifestPath)) {
  throw new Error('Bundle manifest was not produced: ' + bundleManifestPath);
}
const bundleManifest = JSON.parse(fs.readFileSync(bundleManifestPath, 'utf8'));
const manifestRecord = bundleManifest.xhs;
if (!manifestRecord || typeof manifestRecord !== 'object') {
  throw new Error('Bundle manifest does not contain xhs.');
}
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 || '') !== 'xhs') {
  throw new Error('Live run request did not target the xhs expert.');
}
if (String(sendResult.prompt || '') !== expectedPrompt) {
  throw new Error('Live run prompt mismatch.');
}
if (String(workspaceSummary.currentProjectId || '') !== 'xhs') {
  throw new Error('Final active project was not xhs: ' + String(workspaceSummary.currentProjectId || ''));
}
if (expertProjectIds.length !== expectedExpertIds.length || expertProjectIds.some((value, index) => value !== expectedExpertIds[index])) {
  throw new Error('Expert list did not expose all expected projects. 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:xhs:')) {
  throw new Error('Live run session did not bind to xhs: ' + String(sendResult.sessionId || ''));
}
const statusLabels = Array.isArray(streamSmoke.statusLabels)
  ? streamSmoke.statusLabels.map((value) => String(value || ''))
  : [];
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));
}
const smokeAttachments = Array.isArray(sendResult.smokeAttachments) ? sendResult.smokeAttachments : [];
if (smokeAttachments.length !== 1) {
  throw new Error('Expected exactly one smoke attachment. actual=' + smokeAttachments.length);
}
const assistantContent = String((sendResult.lastAssistantMessage && sendResult.lastAssistantMessage.content) || streamSmoke.finalContent || streamSmoke.renderedContent || '');
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.');
}
const workspaceAttachmentRelativePath = assistantAttachmentMatch[0].trim();
const workspaceAttachmentPath = path.join(userDataPath, 'projects', 'xhs', ...workspaceAttachmentRelativePath.split('/'));
if (!fs.existsSync(workspaceAttachmentPath)) {
  throw new Error('Workspace attachment file was not found: ' + workspaceAttachmentPath);
}
const workspaceAttachmentStat = fs.statSync(workspaceAttachmentPath);
if (!workspaceAttachmentStat.isFile() || workspaceAttachmentStat.size < 1) {
  throw new Error('Workspace attachment file is empty: ' + workspaceAttachmentPath);
}
const projectRoot = path.join(userDataPath, 'projects', 'xhs');
const latestNotePath = path.join(projectRoot, 'memory', 'openclaw_runs', 'xhs_last_note.json');
const generatedRoot = path.join(projectRoot, 'memory', 'generated_images');
const xhsProfilePath = path.join(projectRoot, 'memory', 'xhs_profile');
if (!fs.existsSync(latestNotePath)) {
  throw new Error('Live run did not write xhs_last_note.json.');
}
if (!fs.existsSync(generatedRoot) || !fs.statSync(generatedRoot).isDirectory()) {
  throw new Error('Live run did not create memory/generated_images.');
}
if (!fs.existsSync(xhsProfilePath) || !fs.statSync(xhsProfilePath).isDirectory()) {
  throw new Error('Live run did not create memory/xhs_profile.');
}
const latestNote = JSON.parse(fs.readFileSync(latestNotePath, 'utf8'));
if (!latestNote || typeof latestNote !== 'object') {
  throw new Error('xhs_last_note.json is not an object.');
}
const title = String(latestNote.title || '').trim();
const draft = String(latestNote.draft || '').trim();
const tags = Array.isArray(latestNote.tags) ? latestNote.tags.map((value) => String(value || '').trim()).filter(Boolean) : [];
const images = Array.isArray(latestNote.images) ? latestNote.images.map((value) => String(value || '').trim()).filter(Boolean) : [];
const noteMarkdownPath = String(latestNote.note_markdown_path || '').trim();
const noteTextPath = String(latestNote.note_text_path || '').trim();
/*
function looksLikePlaceholder(value) {
  const text = String(value || '').trim().replace(/^["'“”‘’]+|["'“”‘’]+$/g, '');
  if (!text) {
    return true;
  }
  const compact = text.replace(/\s+/g, '').toLowerCase();
  if (['...', '…', '。。。', 'placeholder', 'todo', 'tbd', '示例', '样例', '待补充', '待填写'].includes(compact)) {
    return true;
  }
  return /^[.。…·_-]{2,}$/.test(text) || /^(真实标题|真实正文|标签[a-z0-9一二三四五六七八九十]*)$/i.test(text);
}
*/
function looksLikePlaceholder(value) {
  const text = String(value || '').trim().replace(/^["'\s]+|["'\s]+$/g, '');
  if (!text) {
    return true;
  }
  const compact = text.replace(/\s+/g, '').toLowerCase();
  if (['...', 'placeholder', 'todo', 'tbd', 'example', 'sample', 'draft', 'title'].includes(compact)) {
    return true;
  }
  return /^[._-]{2,}$/.test(text);
}
if (!title) {
  throw new Error('xhs_last_note.json is missing title.');
}
if (!draft) {
  throw new Error('xhs_last_note.json is missing draft.');
}
if (looksLikePlaceholder(title)) {
  throw new Error('xhs_last_note.json title is still placeholder text: ' + title);
}
if (looksLikePlaceholder(draft) || draft.replace(/\s+/g, '').length < 60) {
  throw new Error('xhs_last_note.json draft is still placeholder text or too short.');
}
if (tags.length < 3 || tags.some((value) => looksLikePlaceholder(value))) {
  throw new Error('xhs_last_note.json tags are still placeholder text: ' + JSON.stringify(tags));
}
if (images.length < 1) {
  throw new Error('xhs_last_note.json does not contain generated images.');
}
if (!noteMarkdownPath) {
  throw new Error('xhs_last_note.json is missing note_markdown_path.');
}
if (!noteTextPath) {
  throw new Error('xhs_last_note.json is missing note_text_path.');
}
if (!fs.existsSync(noteMarkdownPath) || !fs.statSync(noteMarkdownPath).isFile()) {
  throw new Error('Markdown note file is missing: ' + noteMarkdownPath);
}
if (!fs.existsSync(noteTextPath) || !fs.statSync(noteTextPath).isFile()) {
  throw new Error('Text note file is missing: ' + noteTextPath);
}
const normalizedGeneratedRoot = path.normalize(generatedRoot + path.sep);
const resolvedImages = images.map((imagePath) => {
  const resolvedPath = path.isAbsolute(imagePath) ? imagePath : path.join(projectRoot, imagePath);
  const normalizedPath = path.normalize(resolvedPath);
  if (!normalizedPath.startsWith(normalizedGeneratedRoot)) {
    throw new Error('Generated image escaped memory/generated_images: ' + normalizedPath);
  }
  if (!fs.existsSync(normalizedPath)) {
    throw new Error('Generated image file is missing: ' + normalizedPath);
  }
  const imageStat = fs.statSync(normalizedPath);
  if (!imageStat.isFile() || imageStat.size < 1) {
    throw new Error('Generated image file is empty: ' + normalizedPath);
  }
  return normalizedPath;
});
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,
  latestNotePath,
  noteMarkdownPath,
  noteTextPath,
  generatedImageCount: resolvedImages.length,
  generatedImages: resolvedImages,
  workspaceAttachmentPath,
  workspaceAttachmentRelativePath,
  xhsProfilePath,
  statusLabels,
  bundleManifestPath
}, null, 2));
"@ $smokeOutput $userDataPath $expectedBundleSourceUrl $bundleConfigVersion $bundleFileName $bundleSkillId $Prompt ($expectedExpertIds -join ',')
  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_EXTRA_MODEL_PROVIDERS_JSON -ErrorAction SilentlyContinue
  Remove-Item Env:XHS_IMAGE_PROVIDER -ErrorAction SilentlyContinue
  Remove-Item Env:XHS_IMAGE_MODEL -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_DISABLE_LOCAL_OPENCLAW_GATEWAY -ErrorAction SilentlyContinue
}
