param(
  [int]$GatewayPort = 18889,
  [string]$GatewayToken = 'qjc-bundled-runtime-token',
  [int]$SmokePort = 4318,
  [string]$SmokeToken = 'smoke-token',
  [string]$BaseOutputDir,
  [int]$TimeoutSeconds = 180,
  [switch]$SkipMaterializeRuntime
)

$ErrorActionPreference = 'Stop'

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 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
  }
}

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 smoke coverage."
  Write-Utf8File (Join-Path $projectRoot 'AGENTS.md') "# $ProjectName`n`nThis is a passive fixture expert used for desktop UI smoke coverage."
}

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if (-not $BaseOutputDir) {
  $BaseOutputDir = Join-Path $repoRoot '.tmp\xhs-expert-cloud-bundle-smoke'
}
$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-cloud-bundle.zip'
$bundleFileName = 'xhs-expert-cloud-bundle.zip'
$bundleProjectId = 'xhs'
$bundleProjectName = 'Xiaohongshu Automation'
$bundleSkillId = 'xhs-project-bundle'
$bundleConfigVersion = '2026-04-03T12:00:00.000Z'
$expectedBundleSourceUrl = "http://127.0.0.1:$SmokePort/downloads/$bundleFileName"
$expertPrompt = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('5Y+R6YCB5LiA5Liq56+u55CD5oqA5ben5biW5a2Q'))
$expectedExpertIds = @('browser-expert-smoke', 'douyin-expert-smoke', 'xhs')
$electronSmokeScript = Join-Path $repoRoot 'build\scripts\electron-smoke.ps1'
$attachmentFixturePath = Join-Path $BaseOutputDir 'fixtures\smoke-image.png'
$expectedAttachmentDir = Join-Path $userDataPath 'projects\xhs\inputs\images\main'
$attachmentFixtureName = 'smoke-image.png'
$attachmentPayload = @(
  @{
    kind = 'image'
    name = $attachmentFixtureName
    mimeType = 'image/png'
    localPath = $attachmentFixturePath
  }
)
$smokeSettingsConfig = [ordered]@{
  image = [ordered]@{
    baseUrl = 'https://ark.cn-beijing.volces.com/api/v3/images/generations'
    apiKey = 'image-smoke-key'
    modelId = 'doubao-seedream-5-0-260128'
  }
  video = [ordered]@{
    baseUrl = 'https://ark.cn-beijing.volces.com/api/v3'
    apiKey = 'video-smoke-key'
    modelId = 'doubao-seedance-2-0-260128'
  }
  copywriting = [ordered]@{
    baseUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
    apiKey = 'copy-smoke-key'
    modelId = 'qwen3.5-plus'
  }
}
$xhsSourceCandidates = @(
  (Join-Path $repoRoot 'workspace\xhs'),
  (Join-Path $repoRoot '.tmp\real-api-bundle-check-2\bundle-src\xhs'),
  (Join-Path $repoRoot '.tmp\xhs-expert-live-run\bundle-src\xhs')
)
$xhsSourceRoot = $xhsSourceCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1

if (Test-Path $BaseOutputDir) {
  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
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)

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 smoke validation.'
$env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersion
$env:QJCLAW_SMOKE_ATTACHMENTS_JSON = (ConvertTo-Json -InputObject @($attachmentPayload) -Depth 5 -Compress)
$env:QJCLAW_SMOKE_SETTINGS_CONFIG_JSON = ($smokeSettingsConfig | ConvertTo-Json -Depth 10 -Compress)
$xhsImageProviderConfig = Get-XhsVolcesImageProviderConfig
$env:QJCLAW_EXTRA_MODEL_PROVIDERS_JSON = ($xhsImageProviderConfig.providers | ConvertTo-Json -Depth 10 -Compress)
$env:QJCLAW_XHS_SMOKE_MODE = '1'
$env:QJCLAW_SMOKE_ACCEPT_WORKSPACE_LAUNCH = '1'
$env:QJCLAW_DISABLE_LOCAL_OPENCLAW_GATEWAY = '1'

try {
  Invoke-ElectronSmokeWithRetry -ScriptPath $electronSmokeScript -Label 'xhs expert cloud-bundle smoke' -ArgumentList @(
    '-SmokeOutput', $smokeOutput,
    '-SmokePort', $SmokePort,
    '-SmokeToken', $SmokeToken,
    '-UserDataPath', $userDataPath,
    '-LogsPath', $logsPath,
    '-RuntimeMode', 'bundled-runtime',
    '-ExpectBundledRuntime',
    '-ExpectWorkspaceEntry',
    '-PreserveUserData',
    '-WorkspaceProjectId', $bundleProjectId,
    '-WorkspaceProjectName', $bundleProjectName,
    '-WorkspaceMarkerFile', 'AGENTS.md',
    '-SmokePrompt', $expertPrompt,
    '-SmokeViewMode', 'experts',
    '-SmokeProjectId', $bundleProjectId,
    '-TimeoutSeconds', $TimeoutSeconds
  )

  $summary = & node -e @"
const fs = require('fs');
const path = require('path');
const [smokeOutput, userDataPath, expectedBundleSourceUrl, expectedBundleConfigVersion, expectedBundleFileName, expectedBundleSkillId, expectedPrompt, expectedExpertIdsCsv, expectedImageBaseUrl, expectedImageApiKey, expectedImageModelId, expectedCopyBaseUrl, expectedCopyApiKey, expectedCopyModelId] = process.argv.slice(1);
const result = JSON.parse(fs.readFileSync(smokeOutput, 'utf8'));
if (!result.ok) {
  throw new Error(result.error || 'Smoke failed.');
}
function parseProjectEnv(filePath) {
  const parsed = {};
  const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
  for (const line of lines) {
    if (!line || line.startsWith('#')) {
      continue;
    }
    const separatorIndex = line.indexOf('=');
    if (separatorIndex <= 0) {
      continue;
    }
    const key = line.slice(0, separatorIndex).trim();
    const rawValue = line.slice(separatorIndex + 1).trim();
    if (!key) {
      continue;
    }
    try {
      parsed[key] = JSON.parse(rawValue);
    } catch {
      parsed[key] = rawValue;
    }
  }
  return parsed;
}
function sanitizeAttachmentFileComponent(value) {
  const trimmed = String(value || '').trim();
  const sanitized = trimmed.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '');
  return sanitized || 'attachment';
}
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 settingsSave = sendResult.settingsSave || {};
const savedModelConfig = settingsSave.expertModelConfig || {};
const expertProjectIds = Array.isArray(finalState.expertProjectIds)
  ? finalState.expertProjectIds.map((value) => String(value || '')).sort()
  : [];
const bundleManifestPath = path.join(userDataPath, 'manifests', 'project-bundles.json');
const projectEnvPath = path.join(userDataPath, 'projects', 'xhs', 'memory', 'project.env');
if (!fs.existsSync(bundleManifestPath)) {
  throw new Error('Bundle manifest was not produced: ' + bundleManifestPath);
}
if (!fs.existsSync(projectEnvPath)) {
  throw new Error('project.env was not materialized: ' + projectEnvPath);
}
const projectEnv = parseProjectEnv(projectEnvPath);
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('Smoke did not remain on experts view.');
}
if (String(sendResult.smokeViewMode || '') !== 'experts') {
  throw new Error('Smoke request was not issued from experts mode.');
}
if (String(sendResult.smokeProjectId || '') !== 'xhs') {
  throw new Error('Smoke request did not target the xhs expert.');
}
if (String(sendResult.prompt || '') !== expectedPrompt) {
  throw new Error('Smoke prompt mismatch.');
}
if (String(savedModelConfig.image && savedModelConfig.image.baseUrl || '') !== 'https://ark.cn-beijing.volces.com/api/v3/images/generations') {
  throw new Error('Saved image baseUrl mismatch: ' + String(savedModelConfig.image && savedModelConfig.image.baseUrl || ''));
}
if (String(savedModelConfig.image && savedModelConfig.image.modelId || '') !== 'doubao-seedream-5-0-260128') {
  throw new Error('Saved image modelId mismatch: ' + String(savedModelConfig.image && savedModelConfig.image.modelId || ''));
}
if (String(savedModelConfig.copywriting && savedModelConfig.copywriting.baseUrl || '') !== 'https://dashscope.aliyuncs.com/compatible-mode/v1') {
  throw new Error('Saved copywriting baseUrl mismatch: ' + String(savedModelConfig.copywriting && savedModelConfig.copywriting.baseUrl || ''));
}
if (String(savedModelConfig.copywriting && savedModelConfig.copywriting.modelId || '') !== 'qwen3.5-plus') {
  throw new Error('Saved copywriting modelId mismatch: ' + String(savedModelConfig.copywriting && savedModelConfig.copywriting.modelId || ''));
}
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('Expert smoke 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('Expert smoke stream did not complete: ' + String(streamSmoke.phase || ''));
}
if (String(sendResult.selectedSkillId || streamSmoke.selectedSkillId || '')) {
  throw new Error('Expert smoke 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('Expert smoke still routed through a skill: ' + JSON.stringify(statusLabels));
}
if (!workspaceLaunchAccepted) {
  throw new Error('Expert smoke 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 attachmentSessionId = String(sendResult.sessionId || '');
const sessionSlug = sanitizeAttachmentFileComponent(attachmentSessionId.replace(/[:]/g, '-'));
const expectedAttachmentRelativePath = 'inputs/images/main/' + sessionSlug + '-01.png';
const expectedAttachmentPath = path.join(userDataPath, 'projects', 'xhs', ...expectedAttachmentRelativePath.split('/'));
if (!fs.existsSync(expectedAttachmentPath)) {
  throw new Error('Materialized attachment file was not found: ' + expectedAttachmentPath);
}
const attachmentStat = fs.statSync(expectedAttachmentPath);
if (!attachmentStat.isFile() || attachmentStat.size < 1) {
  throw new Error('Materialized attachment file is empty: ' + expectedAttachmentPath);
}
if (String(projectEnv.QWEN_BASE_URL || '') !== 'https://dashscope.aliyuncs.com/compatible-mode/v1') {
  throw new Error('project.env QWEN_BASE_URL mismatch: ' + String(projectEnv.QWEN_BASE_URL || ''));
}
if (String(projectEnv.QWEN_API_KEY || '') !== expectedCopyApiKey) {
  throw new Error('project.env QWEN_API_KEY mismatch.');
}
if (String(projectEnv.QWEN_MODEL || '') !== 'qwen3.5-plus') {
  throw new Error('project.env QWEN_MODEL mismatch: ' + String(projectEnv.QWEN_MODEL || ''));
}
if (String(projectEnv.QWEN_VISION_MODEL || '') !== 'qwen3.5-plus') {
  throw new Error('project.env QWEN_VISION_MODEL mismatch: ' + String(projectEnv.QWEN_VISION_MODEL || ''));
}
if (String(projectEnv.XHS_IMAGE_PROVIDER || '') !== 'env:xhsImage') {
  throw new Error('project.env XHS_IMAGE_PROVIDER mismatch: ' + String(projectEnv.XHS_IMAGE_PROVIDER || ''));
}
if (String(projectEnv.XHS_IMAGE_BASE_URL || '') !== 'https://ark.cn-beijing.volces.com/api/v3/images/generations') {
  throw new Error('project.env XHS_IMAGE_BASE_URL mismatch: ' + String(projectEnv.XHS_IMAGE_BASE_URL || ''));
}
if (String(projectEnv.XHS_IMAGE_API_KEY || '') !== expectedImageApiKey) {
  throw new Error('project.env XHS_IMAGE_API_KEY mismatch.');
}
if (String(projectEnv.XHS_IMAGE_MODEL || '') !== expectedImageModelId) {
  throw new Error('project.env XHS_IMAGE_MODEL mismatch: ' + String(projectEnv.XHS_IMAGE_MODEL || ''));
}
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,
  selectedSkillId: sendResult.selectedSkillId || streamSmoke.selectedSkillId || null,
  attachmentCount: smokeAttachments.length,
  attachmentPath: expectedAttachmentPath,
  attachmentRelativePath: expectedAttachmentRelativePath,
  projectEnvPath,
  savedImageModelId: savedModelConfig.image && savedModelConfig.image.modelId || null,
  savedCopywritingModelId: savedModelConfig.copywriting && savedModelConfig.copywriting.modelId || null,
  statusLabels,
  bundleManifestPath
}, null, 2));
"@ $smokeOutput $userDataPath $expectedBundleSourceUrl $bundleConfigVersion $bundleFileName $bundleSkillId $expertPrompt ($expectedExpertIds -join ',') $smokeSettingsConfig.image.baseUrl $smokeSettingsConfig.image.apiKey $smokeSettingsConfig.image.modelId $smokeSettingsConfig.copywriting.baseUrl $smokeSettingsConfig.copywriting.apiKey $smokeSettingsConfig.copywriting.modelId
  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_SETTINGS_CONFIG_JSON -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_EXTRA_MODEL_PROVIDERS_JSON -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_XHS_SMOKE_MODE -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_ACCEPT_WORKSPACE_LAUNCH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_DISABLE_LOCAL_OPENCLAW_GATEWAY -ErrorAction SilentlyContinue
}
