import path from "node:path";
import { access, appendFile, readFile, writeFile } from "node:fs/promises";
import { BrowserWindow, app, nativeImage } from "electron";
import { GatewayClient } from "@qjclaw/gateway-client";
import { RuntimeManager } from "@qjclaw/runtime-manager";
import type { AppConfig, RuntimeCloudFetchAction, RuntimeModePreference, SaveConfigInput, SystemSummary } from "@qjclaw/shared-types";
import { createMainWindow, loadMainWindowRenderer, resolveRendererEntry } from "./create-window.js";
import { registerDesktopIpc, type RegisteredDesktopIpc } from "./ipc.js";
import { AppConfigService } from "./services/app-config.js";
import { AuthClient, CreditClient, ModelConfigClient, OpenClawConfigClient, ProfileClient } from "./services/cloud-api.js";
import { DeviceIdentityService } from "./services/device-identity.js";
import { DiagnosticsService } from "./services/diagnostics.js";
import { DailyReportService } from "./services/daily-report-service.js";
import {
  loadLocalOpenClawGatewayConfig,
  resolveEffectiveGatewayToken,
  resolveEffectiveGatewayUrl,
  shouldUseLocalOpenClawGateway
} from "./services/openclaw-local-config.js";
import { PackagedBootstrapService } from "./services/packaged-bootstrap.js";
import { SecretManager } from "./services/secrets.js";
import { startSmokeCloudApiServer } from "./services/smoke-cloud-api.js";
import { RuntimeCloudSupervisor } from "./services/runtime-cloud-supervisor.js";
import { RuntimeSkillBridgeService } from "./services/runtime-skill-bridge.js";
import { resolveGenericSkillsRoot } from "./services/generic-skills-root.js";
import { SkillCatalogService } from "./services/skill-catalog.js";
import { SkillClient } from "./services/skill-client.js";
import { SkillStoreService } from "./services/skill-store.js";
import { ExpertCatalogService } from "./services/expert-catalog.js";
import { ProjectStoreService } from "./services/project-store.js";
import { ProjectBundleService } from "./services/project-bundle.js";
import { ProjectChatTargetResolverService } from "./services/project-chat-target-resolver.js";
import { ProjectContextService } from "./services/project-context.js";
import { ProjectExecutionRouter } from "./services/project-execution-router.js";
import { ProjectIntentRouterService } from "./services/project-intent-router.js";
import { ProjectSkillRouterService } from "./services/project-skill-router.js";
import { ProjectWorkspaceExecutorService } from "./services/project-workspace-executor.js";
import { TaskPanelService } from "./services/task-panel-service.js";
import { StartupLogger } from "./services/startup-logger.js";

interface RendererSmokeState {
  usingMockApi: boolean;
  viewMode?: "chat" | "experts" | "skills" | "settings";
  skillsPageCatalog?: Array<{ id: string; name?: string; zhName?: string; selectable?: boolean }>;
  gatewayStatus: {
    state?: string;
    version?: string;
    message?: string;
  } | null;
  gatewayHealth: {
    ok?: boolean;
    details?: string;
  } | null;
  runtimeStatus: {
    activeMode?: string;
    payloadState?: string;
    message?: string;
  } | null;
  runtimeTelemetry: {
    state?: string;
    queuedEventCount?: number;
    heartbeatSuccessCount?: number;
    configSyncSuccessCount?: number;
    totalAcceptedEventCount?: number;
  } | null;
  config: {
    gatewayUrl?: string;
    gatewayTokenConfigured?: boolean;
    authTokenConfigured?: boolean;
    cloudApiBaseUrl?: string;
    runtimeCloudApiBaseUrl?: string;
    runtimeMode?: string;
  } | null;
  authSession: {
    state?: string;
    message?: string;
  } | null;
  profile: {
    displayName?: string;
    organizationName?: string;
  } | null;
  credits: {
    balance?: number;
    status?: string;
  } | null;
  skills: Array<{ id: string; name: string }>;
  modelConfig: {
    image?: {
      baseUrl?: string;
      apiKeyConfigured?: boolean;
    };
    video?: {
      baseUrl?: string;
      apiKeyConfigured?: boolean;
    };
    copywriting?: {
      baseUrl?: string;
      apiKeyConfigured?: boolean;
      modelId?: string;
    };
  } | null;
  systemSummary: {
    isPackaged?: boolean;
    userDataPath?: string;
    logsPath?: string;
    appPath?: string;
  } | null;
  sessions: Array<{ id: string; title: string }>;
  messages: Array<{ id: string; role: string; content: string }>;
  logs: Array<{ ts: string; level: string; message: string }>;
  activeSessionId: string;
  streamSmoke: {
    phase?: string;
    prompt?: string;
    selectedSkillId?: string;
    requestId?: string;
    sessionId?: string;
    runId?: string;
    assistantMessageId?: string;
    startedEventCount?: number;
    deltaEventCount?: number;
    completedEventCount?: number;
    errorEventCount?: number;
    fallbackUsed?: boolean;
    renderedContent?: string;
    finalContent?: string;
    executionPolicySource?: string;
    executionPolicyModel?: string;
    latestStatusLabel?: string;
    statusLabels?: string[];
    lastError?: string;
  } | null;
  ui?: {
    currentProjectId?: string;
    homeIntentSuggestionVisible?: boolean;
    homeIntentSuggestionProjectId?: string;
    homeIntentSuggestionProjectName?: string;
    pendingHomeIntentPrompt?: string;
  };
  experts?: {
    standaloneIds?: string[];
    homeShortcutIds?: string[];
    standalonePromptAvailableIds?: string[];
  };
}

const APP_DISPLAY_NAME = "千匠问天";
const forcedUserDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim();
const forcedLogsPath = process.env.QJCLAW_LOGS_PATH?.trim();
const smokeOutputPathEnabled = process.env.QJCLAW_SMOKE_OUTPUT?.trim();
const singleInstanceSmokeMode = process.env.QJCLAW_SMOKE_SINGLE_INSTANCE === "1";
const singleInstanceSmokeReadyPath = process.env.QJCLAW_SMOKE_SINGLE_INSTANCE_READY_PATH?.trim();
const singleInstanceSmokeEventPath = process.env.QJCLAW_SMOKE_SINGLE_INSTANCE_EVENT_PATH?.trim();
const PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS = 45_000;
let smokeTestInFlight = false;
let mainWindow: BrowserWindow | null = null;
let mainWindowSmokeEnabled = false;
let desktopLifecycleReady = false;
let pendingMainWindowReveal = false;
let startupLoggerRef: StartupLogger | undefined;
let secondInstanceEventCount = 0;
let lastSecondInstanceEventSnapshot: WindowInventorySnapshot | null = null;
let mainWindowLoadState: MainWindowLoadState | null = null;

app.setName(APP_DISPLAY_NAME);

if (smokeOutputPathEnabled) {
  app.disableHardwareAcceleration();
  app.commandLine.appendSwitch("disable-gpu");
  app.commandLine.appendSwitch("disable-gpu-compositing");
  app.commandLine.appendSwitch("disable-software-rasterizer");
  app.commandLine.appendSwitch("disable-gpu-sandbox");
}

if (forcedUserDataPath) {
  app.setPath("userData", forcedUserDataPath);
  app.setPath("sessionData", path.join(forcedUserDataPath, "session-data"));
  app.setPath("temp", path.join(forcedUserDataPath, "temp"));
}

if (forcedLogsPath) {
  app.setAppLogsPath(forcedLogsPath);
}

function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function countStringMatches(source: string, pattern: string): number {
  if (!source || !pattern) {
    return 0;
  }

  let count = 0;
  let searchFrom = 0;
  while (searchFrom < source.length) {
    const nextIndex = source.indexOf(pattern, searchFrom);
    if (nextIndex === -1) {
      break;
    }
    count += 1;
    searchFrom = nextIndex + pattern.length;
  }
  return count;
}

async function countFilePatternMatches(filePath: string, pattern: string): Promise<number> {
  try {
    const content = await readFile(filePath, "utf8");
    return countStringMatches(content, pattern);
  } catch {
    return 0;
  }
}

async function firstExistingPath(paths: string[]): Promise<string | undefined> {
  for (const candidate of paths) {
    try {
      await access(candidate);
      return candidate;
    } catch {
      // Try the next candidate.
    }
  }

  return undefined;
}

async function resolveDevelopmentApplicationIconPath(systemSummary: SystemSummary): Promise<string | undefined> {
  if (systemSummary.isPackaged) {
    return undefined;
  }

  const projectDir = path.resolve(systemSummary.appPath);
  return firstExistingPath([
    path.join(projectDir, "build", "icon.png"),
    path.join(projectDir, "build", "icons", "brand-icon.png")
  ]);
}

async function configureDevelopmentDockIcon(systemSummary: SystemSummary): Promise<string | undefined> {
  const iconPath = await resolveDevelopmentApplicationIconPath(systemSummary);
  if (!iconPath || process.platform !== "darwin" || systemSummary.isPackaged || !app.dock) {
    return iconPath;
  }

  const icon = nativeImage.createFromPath(iconPath);
  if (!icon.isEmpty()) {
    app.dock.setIcon(icon);
  }

  return iconPath;
}

function snapshotMainWindowState(window: BrowserWindow | null): Record<string, unknown> {
  if (!window || window.isDestroyed()) {
    return {
      exists: false
    };
  }

  return {
    exists: true,
    id: window.id,
    visible: window.isVisible(),
    focused: window.isFocused(),
    minimized: window.isMinimized(),
    url: window.webContents.isDestroyed() ? undefined : window.webContents.getURL(),
    isLoadingMainFrame: window.webContents.isDestroyed() ? undefined : window.webContents.isLoadingMainFrame()
  };
}

interface MainWindowLoadState {
  status: "created" | "load-requested" | "load-resolved" | "did-start-loading" | "dom-ready" | "did-finish-load" | "did-fail-load" | "render-process-gone" | "web-contents-destroyed" | "window-closed" | "console-message" | "preload-error";
  capturedAt: string;
  url?: string;
  errorCode?: number;
  errorDescription?: string;
  reason?: string;
  message?: string;
}

interface WindowInventorySnapshot {
  reason: string;
  capturedAt: string;
  secondInstanceEventCount: number;
  windowCount: number;
  visibleWindowCount: number;
  focusedWindowCount: number;
  mainWindow: Record<string, unknown>;
  mainWindowLoadState: MainWindowLoadState | null;
}

interface SingleInstanceReadySnapshot extends WindowInventorySnapshot {
  runtimeManagerLogPath: string;
  initialLaunchCommandCount: number;
}

function snapshotWindowInventory(reason: string): WindowInventorySnapshot {
  const windows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed());
  return {
    reason,
    capturedAt: new Date().toISOString(),
    secondInstanceEventCount,
    windowCount: windows.length,
    visibleWindowCount: windows.filter((window) => window.isVisible()).length,
    focusedWindowCount: windows.filter((window) => window.isFocused()).length,
    mainWindow: snapshotMainWindowState(mainWindow),
    mainWindowLoadState
  };
}

async function writeSingleInstanceSmokeSignal(filePath: string | undefined, payload: unknown): Promise<void> {
  if (!filePath) {
    return;
  }

  await writeFile(filePath, JSON.stringify(payload, null, 2), "utf8").catch(() => undefined);
}

function focusMainWindow(window: BrowserWindow): BrowserWindow {
  if (window.isDestroyed()) {
    return window;
  }

  if (window.isMinimized()) {
    window.restore();
  }
  if (!window.isVisible()) {
    window.show();
  }
  window.focus();
  return window;
}

function updateTrackedMainWindowLoadState(window: BrowserWindow, nextState: Omit<MainWindowLoadState, "capturedAt">): void {
  if (mainWindow !== window) {
    return;
  }

  const currentUrl = !window.isDestroyed() && !window.webContents.isDestroyed()
    ? window.webContents.getURL()
    : undefined;
  mainWindowLoadState = {
    capturedAt: new Date().toISOString(),
    url: nextState.url ?? currentUrl,
    ...nextState
  };
  void startupLoggerRef?.info("bootstrap", "window.load-state", "Tracked main window load state updated.", {
    ...mainWindowLoadState
  });
  if (smokeOutputPathEnabled) {
    const line = "[" + new Date().toISOString() + "] window.load-state:" + JSON.stringify(mainWindowLoadState) + "\n";
    void appendFile(smokeOutputPathEnabled + ".trace.log", line, "utf8").catch(() => undefined);
  }
}

function attachMainWindow(window: BrowserWindow, smokeEnabled = mainWindowSmokeEnabled): BrowserWindow {
  mainWindowSmokeEnabled = smokeEnabled;
  mainWindow = window;
  updateTrackedMainWindowLoadState(window, {
    status: "created"
  });
  if (!window.webContents.isDestroyed()) {
    window.webContents.on("did-start-loading", () => {
      updateTrackedMainWindowLoadState(window, {
        status: "did-start-loading"
      });
    });
    window.webContents.on("dom-ready", () => {
      updateTrackedMainWindowLoadState(window, {
        status: "dom-ready"
      });
    });
    window.webContents.on("did-finish-load", () => {
      updateTrackedMainWindowLoadState(window, {
        status: "did-finish-load"
      });
    });
    window.webContents.on("did-fail-load", (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
      if (!isMainFrame) {
        return;
      }

      updateTrackedMainWindowLoadState(window, {
        status: "did-fail-load",
        url: validatedURL,
        errorCode,
        errorDescription
      });
    });
    window.webContents.on("render-process-gone", (_event, details) => {
      updateTrackedMainWindowLoadState(window, {
        status: "render-process-gone",
        reason: details.reason
      });
    });
    window.webContents.on("console-message", (_event, level, message) => {
      if (level >= 2) {
        updateTrackedMainWindowLoadState(window, {
          status: "console-message",
          message
        });
      }
    });
    window.webContents.on("preload-error", (_event, preloadPath, error) => {
      updateTrackedMainWindowLoadState(window, {
        status: "preload-error",
        errorDescription: preloadPath + ": " + error.message
      });
    });
    window.webContents.on("destroyed", () => {
      updateTrackedMainWindowLoadState(window, {
        status: "web-contents-destroyed"
      });
    });
  }
  window.once("closed", () => {
    updateTrackedMainWindowLoadState(window, {
      status: "window-closed"
    });
    if (mainWindow === window) {
      mainWindow = null;
    }
  });
  return window;
}

function createTrackedMainWindow(smokeEnabled = mainWindowSmokeEnabled): BrowserWindow {
  const window = attachMainWindow(createMainWindow(smokeEnabled), smokeEnabled);
  const rendererEntry = resolveRendererEntry();
  updateTrackedMainWindowLoadState(window, {
    status: "load-requested",
    url: rendererEntry
  });
  void loadMainWindowRenderer(window).then(() => {
    updateTrackedMainWindowLoadState(window, {
      status: "load-resolved",
      url: rendererEntry
    });
  }).catch((error) => {
    updateTrackedMainWindowLoadState(window, {
      status: "did-fail-load",
      url: rendererEntry,
      errorCode: 0,
      errorDescription: error instanceof Error ? error.message : String(error)
    });
  });
  if (pendingMainWindowReveal) {
    pendingMainWindowReveal = false;
    focusMainWindow(window);
  }
  return window;
}

function restoreOrCreateMainWindow(reason: string): BrowserWindow | null {
  if (mainWindow && !mainWindow.isDestroyed()) {
    return focusMainWindow(mainWindow);
  }

  if (!desktopLifecycleReady) {
    pendingMainWindowReveal = true;
    return null;
  }

  const window = createTrackedMainWindow(mainWindowSmokeEnabled);
  void startupLoggerRef?.info("bootstrap", "window.restore-or-create", "Restored or recreated the main window.", {
    reason
  });
  return focusMainWindow(window);
}

function withTimeout<T>(operation: Promise<T>, timeoutMs: number, message: string): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error(message));
    }, timeoutMs);
    operation.then(
      (value) => {
        clearTimeout(timer);
        resolve(value);
      },
      (error) => {
        clearTimeout(timer);
        reject(error);
      }
    );
  });
}

function buildSystemSummary(): SystemSummary {
  const userDataPath = process.env.QJCLAW_USER_DATA_PATH?.trim() || app.getPath("userData");
  const logsPath = process.env.QJCLAW_LOGS_PATH?.trim() || app.getPath("logs");

  return {
    appName: app.getName(),
    appVersion: app.getVersion(),
    isPackaged: app.isPackaged,
    platform: process.platform,
    arch: process.arch,
    appPath: app.getAppPath(),
    resourcesPath: process.resourcesPath,
    userDataPath,
    logsPath
  };
}

function resolveRequestedRuntimeMode(configMode: RuntimeModePreference): RuntimeModePreference {
  const override = process.env.QJCLAW_RUNTIME_MODE;
  return override === "bundled-runtime" || override === "external-gateway" ? override : configMode;
}

function buildManagedConfigFromEndpoint(input: {
  defaultConfig: Record<string, unknown>;
  providerKey: string;
  baseUrl: string;
  apiKey: string;
  modelId: string;
  modelLabel?: string;
  apiMode?: string;
}): Record<string, unknown> {
  const {
    defaultConfig,
    providerKey,
    baseUrl,
    apiKey,
    modelId,
    modelLabel = modelId,
    apiMode = "openai-completions"
  } = input;
  const nextConfig = structuredClone(defaultConfig);
  const modelsSection = (nextConfig.models && typeof nextConfig.models === "object" ? nextConfig.models : {}) as Record<string, unknown>;
  const providers = (modelsSection.providers && typeof modelsSection.providers === "object" ? modelsSection.providers : {}) as Record<string, unknown>;
  const existingProvider = (providers[providerKey] && typeof providers[providerKey] === "object" ? providers[providerKey] : {}) as Record<string, unknown>;
  const authSection = (nextConfig.auth && typeof nextConfig.auth === "object" ? nextConfig.auth : {}) as Record<string, unknown>;
  const authProfiles = (authSection.profiles && typeof authSection.profiles === "object" ? authSection.profiles : {}) as Record<string, unknown>;
  const agentsSection = (nextConfig.agents && typeof nextConfig.agents === "object" ? nextConfig.agents : {}) as Record<string, unknown>;
  const agentDefaults = (agentsSection.defaults && typeof agentsSection.defaults === "object" ? agentsSection.defaults : {}) as Record<string, unknown>;
  const modelDefaults = (agentDefaults.model && typeof agentDefaults.model === "object" ? agentDefaults.model : {}) as Record<string, unknown>;
  const modelAliases = (agentDefaults.models && typeof agentDefaults.models === "object" ? agentDefaults.models : {}) as Record<string, unknown>;

  authProfiles[providerKey + ":default"] = {
    provider: providerKey,
    mode: "api_key"
  };
  authSection.profiles = authProfiles;
  nextConfig.auth = authSection;

  providers[providerKey] = {
    ...existingProvider,
    baseUrl,
    apiKey,
    api: apiMode,
    models: [
      {
        id: modelId,
        name: modelLabel,
        reasoning: false,
        input: ["text"],
        cost: {
          input: 0,
          output: 0,
          cacheRead: 0,
          cacheWrite: 0
        },
        maxTokens: 2048
      }
    ]
  };
  modelsSection.mode = "merge";
  modelsSection.providers = providers;
  nextConfig.models = modelsSection;

  modelDefaults.primary = providerKey + "/" + modelId;
  modelDefaults.fallbacks = [];
  modelAliases[providerKey + "/" + modelId] = {
    alias: modelLabel
  };
  agentDefaults.model = modelDefaults;
  agentDefaults.models = modelAliases;
  agentsSection.defaults = agentDefaults;
  nextConfig.agents = agentsSection;

  return nextConfig;
}

function buildDirectProviderManagedConfig(defaultConfig: Record<string, unknown>, config: AppConfig, apiKey: string): Record<string, unknown> {
  return buildManagedConfigFromEndpoint({
    defaultConfig,
    providerKey: "direct-provider",
    baseUrl: config.baseUrl,
    apiKey,
    modelId: config.defaultModel || "gpt-5.4-mini",
    apiMode: config.provider === "anthropic" ? "anthropic-messages" : "openai-completions"
  });
}

function buildClientChatManagedConfig(defaultConfig: Record<string, unknown>, config: AppConfig, apiKey: string): Record<string, unknown> {
  return buildManagedConfigFromEndpoint({
    defaultConfig,
    providerKey: "client-chat-model",
    baseUrl: config.expertModelConfig.copywriting.baseUrl,
    apiKey,
    modelId: config.expertModelConfig.copywriting.modelId || "unknown-model"
  });
}

function hasConfiguredClientChatModel(config: AppConfig, apiKey?: string | null): apiKey is string {
  return Boolean(
    apiKey?.trim()
    && config.expertModelConfig.copywriting.baseUrl.trim()
    && (config.expertModelConfig.copywriting.modelId ?? "").trim()
  );
}

function resolveVendorRuntimeDir(systemSummary: SystemSummary): string {
  const smokeRuntimeDir = process.env.QJCLAW_SMOKE_RUNTIME_DIR;
  if (!systemSummary.isPackaged && smokeRuntimeDir?.trim()) {
    return path.resolve(smokeRuntimeDir);
  }

  if (systemSummary.isPackaged) {
    return path.join(systemSummary.resourcesPath, "vendor", "openclaw-runtime");
  }

  return path.resolve(systemSummary.appPath, "..", "..", "vendor", "openclaw-runtime");
}

function resolvePackagedBootstrapRoot(systemSummary: SystemSummary): string {
  if (systemSummary.isPackaged) {
    return path.join(systemSummary.resourcesPath, "bootstrap");
  }

  return path.resolve(systemSummary.appPath, "bootstrap");
}

async function waitForRendererSmokeState(window: BrowserWindow, timeoutMs = 20000): Promise<RendererSmokeState | null> {
  const started = Date.now();

  while (Date.now() - started < timeoutMs) {
    if (window.isDestroyed() || window.webContents.isDestroyed()) {
      throw new Error("Smoke test window was destroyed before renderer state became available.");
    }

    if (window.webContents.isLoadingMainFrame()) {
      await delay(250);
      continue;
    }

    let state: unknown = null;
    try {
      state = await Promise.race([
        window.webContents.executeJavaScript("window.__QJC_SMOKE__ ?? null"),
        delay(2000).then(() => null)
      ]);
    } catch {
      await delay(250);
      continue;
    }
    if (state && typeof state === "object" && "sessions" in state && "logs" in state) {
      return state as RendererSmokeState;
    }

    await delay(250);
  }

  return null;
}

function matchesExpectedSmokeModelConfig(state: RendererSmokeState | null | undefined): boolean {
  const modelConfig = state?.modelConfig;
  if (!modelConfig) {
    return false;
  }

  return Boolean(modelConfig.image?.apiKeyConfigured)
    && Boolean(modelConfig.video?.apiKeyConfigured)
    && Boolean(modelConfig.copywriting?.apiKeyConfigured)
    && String(modelConfig.copywriting?.baseUrl || "") === "https://dashscope.aliyuncs.com/compatible-mode/v1"
    && String(modelConfig.copywriting?.modelId || "") === "qwen3.6-plus";
}

async function waitForRendererSmokeBootstrap(
  window: BrowserWindow,
  timeoutMs = 20000,
  trace?: (message: string) => Promise<void>
): Promise<RendererSmokeState> {
  return await new Promise<RendererSmokeState>((resolve, reject) => {
    let settled = false;
    let timeout: NodeJS.Timeout | undefined;
    const cleanup = () => {
      if (timeout) {
        clearTimeout(timeout);
        timeout = undefined;
      }
      window.removeListener("closed", onClosed);
      if (!window.webContents.isDestroyed()) {
        window.webContents.removeListener("did-fail-load", onFailLoad);
        window.webContents.removeListener("render-process-gone", onRenderProcessGone);
        window.webContents.removeListener("destroyed", onWebContentsDestroyed);
      }
    };
    const finish = (state: RendererSmokeState) => {
      if (settled) {
        return;
      }
      settled = true;
      cleanup();
      resolve(state);
    };
    const fail = (message: string) => {
      if (settled) {
        return;
      }
      settled = true;
      cleanup();
      const loadState = JSON.stringify(mainWindowLoadState);
      const windowState = JSON.stringify(snapshotMainWindowState(window));
      reject(new Error(message + " lastLoadState=" + loadState + " window=" + windowState));
    };
    const onFailLoad = (_event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
      if (!isMainFrame) {
        return;
      }
      fail("Renderer main frame failed to load: " + errorDescription + " (" + errorCode + ") " + validatedURL);
    };
    const onRenderProcessGone = (_event: Electron.Event, details: Electron.RenderProcessGoneDetails) => {
      fail("Renderer process exited before smoke state became available: " + details.reason);
    };
    const onWebContentsDestroyed = () => {
      fail("Renderer webContents was destroyed before smoke state became available.");
    };
    const onClosed = () => {
      fail("Smoke test window was closed before renderer state became available.");
    };

    window.once("closed", onClosed);
    if (!window.webContents.isDestroyed()) {
      window.webContents.on("did-fail-load", onFailLoad);
      window.webContents.on("render-process-gone", onRenderProcessGone);
      window.webContents.on("destroyed", onWebContentsDestroyed);
    }

    timeout = setTimeout(() => {
      void trace?.("renderer-smoke-bootstrap:hard-timeout");
      fail("Renderer smoke bootstrap timed out before wait loop completed.");
    }, timeoutMs + 1000);

    void (async () => {
      try {
        await trace?.("renderer-smoke-bootstrap:wait-state-start");
        const state = await waitForRendererSmokeState(window, timeoutMs);
        if (!state) {
          await trace?.("renderer-smoke-bootstrap:state-missing");
          const rendererProbe = await Promise.race([
            window.webContents.executeJavaScript(`(() => ({
              readyState: document.readyState,
              smokeEnabled: Boolean(window.qjcSmokeEnabled),
              hasDesktopApi: Boolean(window.qjcDesktop),
              hasSmokeState: Boolean(window.__QJC_SMOKE__),
              rootChildCount: document.getElementById("root")?.childElementCount ?? null,
              bodyTextLength: document.body?.innerText?.length ?? null,
              locationHref: window.location.href
            }))()`),
            delay(2000).then(() => null)
          ]).catch(() => null);
          fail("Renderer smoke state was not published. probe=" + JSON.stringify(rendererProbe));
          return;
        }
        await trace?.("renderer-smoke-bootstrap:state-ready");
        finish(state);
      } catch (error) {
        await trace?.("renderer-smoke-bootstrap:error:" + (error instanceof Error ? error.message : String(error)));
        fail(error instanceof Error ? error.message : String(error));
      }
    })();
  });
}

async function waitForRendererStreamSmoke(window: BrowserWindow, timeoutMs = 40000): Promise<RendererSmokeState | null> {
  const started = Date.now();
  const acceptWorkspaceLaunch = process.env.QJCLAW_SMOKE_ACCEPT_WORKSPACE_LAUNCH === "1";

  while (Date.now() - started < timeoutMs) {
    const state = await waitForRendererSmokeState(window, 2000);
    const streamSmoke = state?.streamSmoke;
    if (streamSmoke && ["completed", "fallback", "error"].includes(String(streamSmoke.phase ?? ""))) {
      return state;
    }
    const statusLabels = Array.isArray(streamSmoke?.statusLabels)
      ? streamSmoke.statusLabels.map((value: string) => String(value || ""))
      : [];
    if (
      acceptWorkspaceLaunch
      && streamSmoke
      && String(streamSmoke.phase ?? "") !== "error"
      && statusLabels.some((label: string) => label.includes("Launching project workspace"))
    ) {
      return state;
    }

    await delay(250);
  }

  return null;
}

function resolveSmokeStreamTimeoutMs(): number {
  const raw = Number.parseInt(process.env.QJCLAW_SMOKE_STREAM_TIMEOUT_MS ?? "", 10);
  if (Number.isFinite(raw) && raw >= 10_000) {
    return raw;
  }
  return 40_000;
}

function resolveSmokeWaitForPaths(): string[] {
  const raw = process.env.QJCLAW_SMOKE_WAIT_FOR_PATHS ?? "";
  return raw
    .split(path.delimiter)
    .map((value) => value.trim())
    .filter((value) => value.length > 0);
}

function resolveSmokeWaitForPathsTimeoutMs(): number {
  const raw = Number.parseInt(process.env.QJCLAW_SMOKE_WAIT_FOR_PATHS_TIMEOUT_MS ?? "", 10);
  if (Number.isFinite(raw) && raw >= 1_000) {
    return raw;
  }
  return 120_000;
}

function resolveSmokeAttachments(): Array<{
  kind: "image" | "file";
  name: string;
  mimeType: string;
  localPath: string;
}> {
  const raw = process.env.QJCLAW_SMOKE_ATTACHMENTS_JSON ?? "";
  if (!raw.trim()) {
    return [];
  }

  try {
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) {
      return [];
    }

    return parsed.flatMap((attachment) => {
      if (!attachment || typeof attachment !== "object") {
        return [];
      }
      const typed = attachment as {
        kind?: unknown;
        name?: unknown;
        mimeType?: unknown;
        localPath?: unknown;
      };
      const kind = typed.kind === "image" || typed.kind === "file" ? typed.kind : null;
      const localPath = typeof typed.localPath === "string" ? typed.localPath.trim() : "";
      if (!kind || !localPath) {
        return [];
      }
      return [{
        kind,
        name: typeof typed.name === "string" && typed.name.trim() ? typed.name.trim() : path.basename(localPath),
        mimeType: typeof typed.mimeType === "string" && typed.mimeType.trim() ? typed.mimeType.trim() : "application/octet-stream",
        localPath
      }];
    });
  } catch {
    return [];
  }
}

function resolveSmokeSettingsConfig(): Pick<SaveConfigInput, "expertModelConfig" | "douyinRuntimeConfig"> | null {
  const raw = process.env.QJCLAW_SMOKE_SETTINGS_CONFIG_JSON ?? "";
  if (!raw.trim()) {
    return null;
  }

  try {
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== "object") {
      return null;
    }

    const input = parsed as {
      expertModelConfig?: {
        image?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
        video?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
        copywriting?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
        digitalHuman?: {
          volcAccessKey?: unknown;
          volcSecretKey?: unknown;
          qiniuAccessKey?: unknown;
          qiniuSecretKey?: unknown;
        };
      };
      image?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
      video?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
      copywriting?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
      digitalHuman?: {
        volcAccessKey?: unknown;
        volcSecretKey?: unknown;
        qiniuAccessKey?: unknown;
        qiniuSecretKey?: unknown;
      };
      douyinRuntimeConfig?: {
        videoAnalyzer?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
        replicationBrief?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown };
        vectcut?: { baseUrl?: unknown; fileBaseUrl?: unknown; apiKey?: unknown };
      };
    };
    const expertModelInput = input.expertModelConfig ?? input;

    type SmokeSettingsEntry = {
      baseUrl: string;
      apiKey?: string;
      modelId?: string;
    };
    type SmokeDouyinTextSettingsEntry = {
      baseUrl?: string;
      apiKey?: string;
      modelId?: string;
    };
    type SmokeVectCutSettingsEntry = {
      baseUrl?: string;
      fileBaseUrl?: string;
      apiKey?: string;
    };
    type SmokeDigitalHumanSettingsEntry = {
      volcAccessKey?: string;
      volcSecretKey?: string;
      qiniuAccessKey?: string;
      qiniuSecretKey?: string;
    };

    const normalizeEntry = (entry?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown }) => {
      if (!entry || typeof entry !== "object") {
        return undefined;
      }
      if (typeof entry.baseUrl !== "string") {
        return undefined;
      }

      const normalized: SmokeSettingsEntry = {
        baseUrl: entry.baseUrl
      };
      if (typeof entry.apiKey === "string") {
        normalized.apiKey = entry.apiKey;
      }
      if (typeof entry.modelId === "string") {
        normalized.modelId = entry.modelId;
      }
      return Object.keys(normalized).length ? normalized : undefined;
    };
    const normalizeDouyinTextEntry = (entry?: { baseUrl?: unknown; apiKey?: unknown; modelId?: unknown }) => {
      if (!entry || typeof entry !== "object") {
        return undefined;
      }

      const normalized: SmokeDouyinTextSettingsEntry = {};
      if (typeof entry.baseUrl === "string") {
        normalized.baseUrl = entry.baseUrl;
      }
      if (typeof entry.apiKey === "string") {
        normalized.apiKey = entry.apiKey;
      }
      if (typeof entry.modelId === "string") {
        normalized.modelId = entry.modelId;
      }
      return Object.keys(normalized).length ? normalized : undefined;
    };
    const normalizeVectCutEntry = (entry?: { baseUrl?: unknown; fileBaseUrl?: unknown; apiKey?: unknown }) => {
      if (!entry || typeof entry !== "object") {
        return undefined;
      }

      const normalized: SmokeVectCutSettingsEntry = {};
      if (typeof entry.baseUrl === "string") {
        normalized.baseUrl = entry.baseUrl;
      }
      if (typeof entry.fileBaseUrl === "string") {
        normalized.fileBaseUrl = entry.fileBaseUrl;
      }
      if (typeof entry.apiKey === "string") {
        normalized.apiKey = entry.apiKey;
      }
      return Object.keys(normalized).length ? normalized : undefined;
    };
    const normalizeDigitalHumanEntry = (entry?: {
      volcAccessKey?: unknown;
      volcSecretKey?: unknown;
      qiniuAccessKey?: unknown;
      qiniuSecretKey?: unknown;
    }) => {
      if (!entry || typeof entry !== "object") {
        return undefined;
      }

      const normalized: SmokeDigitalHumanSettingsEntry = {};
      if (typeof entry.volcAccessKey === "string") {
        normalized.volcAccessKey = entry.volcAccessKey;
      }
      if (typeof entry.volcSecretKey === "string") {
        normalized.volcSecretKey = entry.volcSecretKey;
      }
      if (typeof entry.qiniuAccessKey === "string") {
        normalized.qiniuAccessKey = entry.qiniuAccessKey;
      }
      if (typeof entry.qiniuSecretKey === "string") {
        normalized.qiniuSecretKey = entry.qiniuSecretKey;
      }
      return Object.keys(normalized).length ? normalized : undefined;
    };

    const resolved = {
      expertModelConfig: {
        image: normalizeEntry(expertModelInput.image),
        video: normalizeEntry(expertModelInput.video),
        copywriting: normalizeEntry(expertModelInput.copywriting),
        digitalHuman: normalizeDigitalHumanEntry(expertModelInput.digitalHuman)
      },
      douyinRuntimeConfig: {
        videoAnalyzer: normalizeDouyinTextEntry(input.douyinRuntimeConfig?.videoAnalyzer),
        replicationBrief: normalizeDouyinTextEntry(input.douyinRuntimeConfig?.replicationBrief),
        vectcut: normalizeVectCutEntry(input.douyinRuntimeConfig?.vectcut)
      }
    };

    return resolved.expertModelConfig.image
      || resolved.expertModelConfig.video
      || resolved.expertModelConfig.copywriting
      || resolved.expertModelConfig.digitalHuman
      || resolved.douyinRuntimeConfig.videoAnalyzer
      || resolved.douyinRuntimeConfig.replicationBrief
      || resolved.douyinRuntimeConfig.vectcut
      ? resolved
      : null;
  } catch {
    return null;
  }
}

async function waitForSmokePaths(pathsToCheck: string[], timeoutMs: number): Promise<void> {
  if (pathsToCheck.length === 0) {
    return;
  }

  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    const results = await Promise.all(pathsToCheck.map(async (targetPath) => {
      try {
        await access(targetPath);
        return true;
      } catch {
        return false;
      }
    }));
    if (results.every(Boolean)) {
      return;
    }
    await delay(1000);
  }

  const missingPaths = await Promise.all(pathsToCheck.map(async (targetPath) => {
    try {
      await access(targetPath);
      return null;
    } catch {
      return targetPath;
    }
  }));
  throw new Error("Workspace launch was accepted, but expected artifacts were not created in time: " + missingPaths.filter(Boolean).join(", "));
}

async function waitForWorkspaceVideoGeneration(
  userDataPath: string,
  projectId: string,
  timeoutMs: number
): Promise<void> {
  const outputRoot = path.join(userDataPath, "projects", projectId, "memory", "output");
  const latestProjectAliasPath = path.join(outputRoot, "_latest_project_path.txt");
  const started = Date.now();
  let lastStatus = "missing";

  while (Date.now() - started < timeoutMs) {
    try {
      await access(latestProjectAliasPath);
      const latestProjectPath = (await readFile(latestProjectAliasPath, "utf8")).trim();
      if (latestProjectPath) {
        const videoStatusPath = path.join(latestProjectPath, "video_generation_status.json");
        try {
          const videoStatusRaw = await readFile(videoStatusPath, "utf8");
          const videoStatus = JSON.parse(videoStatusRaw) as { status?: unknown; error?: unknown; message?: unknown };
          lastStatus = String(videoStatus.status ?? "").trim() || "unknown";
          if (lastStatus === "success") {
            const finalVideoPath = path.join(latestProjectPath, "latest_seedance_split.mp4");
            await access(finalVideoPath);
            return;
          }
          if (["error", "failed"].includes(lastStatus)) {
            const detail = String(videoStatus.error ?? videoStatus.message ?? "").trim();
            throw new Error("Workspace video generation failed" + (detail ? ": " + detail : "."));
          }
        } catch (error) {
          if (error instanceof Error && error.message.startsWith("Workspace video generation failed")) {
            throw error;
          }
        }
      }
    } catch {
      // Keep polling until the video artifacts are ready or timeout.
    }
    await delay(1000);
  }

  throw new Error("Workspace video generation did not finish in time. lastStatus=" + lastStatus);
}

async function waitForRendererTerminalStreamSmoke(window: BrowserWindow, timeoutMs = 40000): Promise<RendererSmokeState | null> {
  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    const state = await waitForRendererSmokeState(window, 2000);
    const streamSmoke = state?.streamSmoke;
    if (streamSmoke && ["completed", "fallback", "error"].includes(String(streamSmoke.phase ?? ""))) {
      return state;
    }
    await delay(250);
  }
  return null;
}

async function runSmokeTest(window: BrowserWindow, outputPath: string): Promise<void> {
  const result: Record<string, unknown> = {
    startedAt: new Date().toISOString()
  };
  const tracePath = outputPath + ".trace.log";
  const trace = async (message: string) => {
    const line = "[" + new Date().toISOString() + "] " + message + "\n";
    await appendFile(tracePath, line, "utf8").catch(() => undefined);
  };
  const smokeWaitForPaths = resolveSmokeWaitForPaths();
  const smokeWaitForPathsTimeoutMs = resolveSmokeWaitForPathsTimeoutMs();

  try {
    await trace("runSmokeTest:start");
    if (window.webContents.isLoadingMainFrame()) {
      await trace("runSmokeTest:renderer-loading");
    }

    await trace("runSmokeTest:loading-renderer-state");
    let initialState = await waitForRendererSmokeBootstrap(window, 20000, trace);

    const readyDeadline = Date.now() + 30000;
    while (
      Date.now() < readyDeadline &&
      (
        !initialState.config ||
        !initialState.gatewayStatus ||
        !initialState.systemSummary ||
        !initialState.runtimeStatus
      )
    ) {
      await delay(500);
      const nextState = await waitForRendererSmokeState(window, 2000);
      if (nextState) {
        initialState = nextState;
      }
    }

    result.initialState = initialState;
    await trace("runSmokeTest:initial-state-ready");

    const startupOnly = process.env.QJCLAW_SMOKE_STARTUP_ONLY === "1";
    if (startupOnly) {
      await trace("runSmokeTest:startup-only-begin");
      const startupOnlyResult = await window.webContents.executeJavaScript(`(async () => {
        const api = window.qjcDesktop;
        const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
        if (!api) {
          throw new Error("Renderer is using mock desktop API.");
        }

        const capture = async (label) => {
          const state = window.__QJC_SMOKE__;
          const workspace = await api.workspace.getSummary();
          return {
            label,
            capturedAt: new Date().toISOString(),
            workspace,
            ui: state?.ui ?? null,
            dom: {
              startupOverlayVisible: Boolean(document.querySelector(".startup-overlay")),
              bindEntryVisible: Boolean(document.querySelector(".bind-entry")),
              expertCardCount: document.querySelectorAll(".expert-card").length
            }
          };
        };

        const timeline = [];
        let warmupQueued = false;
        let lastSnapshot = await capture("initial");
        timeline.push(lastSnapshot);

        const deadline = Date.now() + 90000;
        while (Date.now() < deadline) {
          if (!warmupQueued) {
            await api.workspace.warmup().catch(() => undefined);
            warmupQueued = true;
          }

          await sleep(1000);
          lastSnapshot = await capture("poll");
          timeline.push(lastSnapshot);

          if (
            lastSnapshot.ui?.showBindEntry
            && !lastSnapshot.ui?.showStartupOverlay
            && lastSnapshot.dom.bindEntryVisible
            && !lastSnapshot.dom.startupOverlayVisible
          ) {
            return {
              timeline,
              finalSnapshot: lastSnapshot
            };
          }
        }

        throw new Error("Startup-only smoke did not reach bind-entry after shell warmup. lastState=" + JSON.stringify(lastSnapshot));
      })()`);
      await trace("runSmokeTest:startup-only-finished");
      result.startupOnlyResult = startupOnlyResult;
      result.finalState = await waitForRendererSmokeState(window, 5000);
      result.ok = true;
      await trace("runSmokeTest:startup-only-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }

    const prompt = process.env.QJCLAW_SMOKE_PROMPT?.trim() || `qjc smoke stream ${new Date().toISOString()}`;
    const preferredSkillId = process.env.QJCLAW_SMOKE_SKILL_ID?.trim();
    const requestedSmokeViewMode = process.env.QJCLAW_SMOKE_VIEW_MODE?.trim();
    const smokeViewMode = requestedSmokeViewMode === "experts"
      ? "experts"
      : requestedSmokeViewMode === "skills"
        ? "skills"
        : requestedSmokeViewMode === "settings"
          ? "settings"
          : "chat";
    const smokeProjectId = process.env.QJCLAW_SMOKE_PROJECT_ID?.trim() || "";
    const smokeExpertEntryId = process.env.QJCLAW_SMOKE_EXPERT_ENTRY_ID?.trim() || "";
    const smokeSendAfterExpertEntry = process.env.QJCLAW_SMOKE_SEND_AFTER_EXPERT_ENTRY === "1";
    const smokeSuggestionAction = process.env.QJCLAW_SMOKE_SUGGESTION_ACTION?.trim() || "";
    const smokeScenario = process.env.QJCLAW_SMOKE_SCENARIO?.trim() || "";
    const smokeAttachments = resolveSmokeAttachments();
    const smokeSettingsConfig = resolveSmokeSettingsConfig();
    await trace("runSmokeTest:before-send-script");
    const sendResult = await window.webContents.executeJavaScript(`(async () => {
      const api = window.qjcDesktop;
      const state = window.__QJC_SMOKE__;
      const actions = window.__QJC_SMOKE_ACTIONS__;
      if (!api) {
        throw new Error("Renderer is using mock desktop API.");
      }
      if (!actions) {
        throw new Error("Renderer smoke actions were not published.");
      }
      const smokeBaseUrl = ${JSON.stringify(process.env.QJCLAW_SMOKE_CLOUD_API_BASE_URL ?? "")};
      const smokeToken = ${JSON.stringify(process.env.QJCLAW_SMOKE_AUTH_TOKEN ?? "")};
      const smokeRuntimeApiKey = ${JSON.stringify(process.env.QJCLAW_SMOKE_RUNTIME_CLOUD_API_KEY ?? "smoke-runtime-api-key")};
      const preferredSkillId = ${JSON.stringify(process.env.QJCLAW_SMOKE_SKILL_ID?.trim() ?? "")};
      const smokeViewMode = ${JSON.stringify(smokeViewMode)};
      const smokeAttachments = ${JSON.stringify(smokeAttachments)};
      const smokeSuggestionAction = ${JSON.stringify(process.env.QJCLAW_SMOKE_SUGGESTION_ACTION?.trim() ?? "")};
      const smokeScenario = ${JSON.stringify(process.env.QJCLAW_SMOKE_SCENARIO?.trim() ?? "")};
      const smokeExpertEntryId = ${JSON.stringify(process.env.QJCLAW_SMOKE_EXPERT_ENTRY_ID?.trim() ?? "")};
      const smokeSendAfterExpertEntry = ${JSON.stringify(process.env.QJCLAW_SMOKE_SEND_AFTER_EXPERT_ENTRY === "1")};
      const requestedSmokeSettingsConfig = ${JSON.stringify(smokeSettingsConfig)};
      if (smokeBaseUrl) {
        const current = await api.config.load();
        await api.config.save({
          ...current,
          cloudApiBaseUrl: smokeBaseUrl,
          runtimeCloudApiBaseUrl: smokeBaseUrl,
          authToken: smokeToken || undefined,
          apiKey: smokeRuntimeApiKey || undefined,
          runtimeMode: current.runtimeMode
        });
        if (smokeToken) {
          await api.auth.signIn({ accessToken: smokeToken });
        }
      }
      const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
      const waitForGatewayReady = async () => {
        let status = null;
        let health = null;
        let lastError = null;
        for (let attempt = 0; attempt < 20; attempt += 1) {
          try {
            status = await api.gateway.status();
            if (status.state !== "connected") {
              status = await api.gateway.reconnect();
            }
            health = await api.gateway.health();
            if (status.state === "connected" && health.ok) {
              return { status, health, lastError };
            }
          } catch (error) {
            lastError = error instanceof Error ? error.message : String(error);
          }
          await sleep(750);
        }
        return {
          status: await api.gateway.status(),
          health: await api.gateway.health(),
          lastError
        };
      };
      const waitForGatewayStable = async () => {
        let stableSamples = 0;
        let lastSnapshot = null;
        const deadline = Date.now() + 120000;
        let warmupQueued = false;
        while (Date.now() < deadline) {
          let [status, workspace] = await Promise.all([
            api.gateway.status().catch(() => null),
            api.workspace.getSummary().catch(() => null)
          ]);
          if (status?.state !== "connected") {
            status = await api.gateway.reconnect().catch(() => status);
            workspace = await api.workspace.getSummary().catch(() => workspace);
          }
          lastSnapshot = {
            gatewayState: status?.state ?? "unknown",
            chatReady: Boolean(workspace?.chatReady),
            startupPhase: workspace?.startupPhase ?? "unknown",
            startupMessage: workspace?.startupMessage ?? ""
          };
          if (status?.state === "connected" && workspace?.chatReady) {
            stableSamples += 1;
            if (stableSamples >= 3) {
              return lastSnapshot;
            }
          } else {
            stableSamples = 0;
            if (!warmupQueued && workspace?.startupPhase === "error") {
              await api.workspace.warmup().catch(() => undefined);
              warmupQueued = true;
            }
          }
          await sleep(1000);
        }
        throw new Error("Gateway did not remain stable after settings save. lastState=" + JSON.stringify(lastSnapshot));
      };
      const waitForWorkspaceReady = async () => {
        let workspace = await api.workspace.getSummary();
        const deadline = Date.now() + 45000;
        let warmupQueued = false;
        while (Date.now() < deadline) {
          const chatReady = Boolean(workspace.chatReady);
          const projectReady = Boolean(
            chatReady
            && workspace.projectReady
            && workspace.currentProjectId
            && Array.isArray(workspace.projects)
            && workspace.projects.length > 0
          );
          const homeChatReady = chatReady && !projectReady;
          if (projectReady || homeChatReady) {
            return workspace;
          }
          if (!warmupQueued) {
            await api.workspace.warmup().catch(() => undefined);
            warmupQueued = true;
          }
          await sleep(1000);
          workspace = await api.workspace.getSummary();
        }
        throw new Error("Workspace did not become ready for smoke chat. lastState=" + JSON.stringify({
          chatReady: workspace.chatReady,
          projectReady: workspace.projectReady,
          currentProjectId: workspace.currentProjectId,
          projectCount: workspace.projectCount,
          sessionCount: Array.isArray(workspace.sessions) ? workspace.sessions.length : 0,
          startupPhase: workspace.startupPhase,
          startupMessage: workspace.startupMessage,
          chatStatusMessage: workspace.chatStatusMessage,
          runtimeCloudState: workspace.runtimeCloudState,
          runtimeState: workspace.runtimeState
        }));
      };
      const waitForSkillsPageReady = async () => {
        let workspace = await api.workspace.getSummary();
        let catalog = await api.skillCatalog.list().catch(() => []);
        const deadline = Date.now() + 45000;
        let warmupQueued = false;
        while (Date.now() < deadline) {
          if (Array.isArray(catalog) && catalog.length > 0) {
            return { workspace, catalog };
          }
          if (!warmupQueued) {
            await api.workspace.warmup().catch(() => undefined);
            warmupQueued = true;
          }
          await sleep(1000);
          workspace = await api.workspace.getSummary();
          catalog = await api.skillCatalog.list().catch(() => []);
        }
        throw new Error("Skills page did not become ready for smoke validation. lastState=" + JSON.stringify({
          skillCatalogCount: Array.isArray(catalog) ? catalog.length : 0,
          workspaceSkillCount: Array.isArray(workspace.skills) ? workspace.skills.length : 0,
          currentProjectId: workspace.currentProjectId,
          projectCount: workspace.projectCount,
          startupPhase: workspace.startupPhase,
          startupMessage: workspace.startupMessage,
          runtimeCloudState: workspace.runtimeCloudState,
          runtimeState: workspace.runtimeState
        }));
      };
      const waitForSettingsViewReady = async () => {
        const deadline = Date.now() + 20000;
        while (Date.now() < deadline) {
          const rendererState = window.__QJC_SMOKE__;
          if (rendererState?.viewMode === "settings" && rendererState.config && rendererState.modelConfig) {
            return rendererState;
          }
          await sleep(250);
        }
        throw new Error("Settings view did not become ready for smoke validation.");
      };
      const defaultSmokeSettingsConfig = {
        expertModelConfig: {
          image: {
            baseUrl: "https://image-smoke.example.com/v1",
            apiKey: "image-smoke-key"
          },
          video: {
            baseUrl: "https://video-smoke.example.com/v1",
            apiKey: "video-smoke-key"
          },
          copywriting: {
            baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
            apiKey: "copy-smoke-key",
            modelId: "qwen3.6-plus"
          }
        }
      };
      const skipSmokeSettingsSave = ${JSON.stringify(process.env.QJCLAW_SMOKE_SKIP_SETTINGS_SAVE === "1")};
      const smokeSettingsConfig = skipSmokeSettingsSave ? null : (requestedSmokeSettingsConfig || defaultSmokeSettingsConfig);
      const smokeExpertSettingsConfig = smokeSettingsConfig?.expertModelConfig
        ? {
            image: smokeSettingsConfig.expertModelConfig.image,
            video: smokeSettingsConfig.expertModelConfig.video,
            copywriting: smokeSettingsConfig.expertModelConfig.copywriting,
            digitalHuman: smokeSettingsConfig.expertModelConfig.digitalHuman
          }
        : null;
      const smokeDouyinSettingsConfig = smokeSettingsConfig?.douyinRuntimeConfig
        ? {
            videoAnalyzer: smokeSettingsConfig.douyinRuntimeConfig.videoAnalyzer,
            replicationBrief: smokeSettingsConfig.douyinRuntimeConfig.replicationBrief,
            vectcut: smokeSettingsConfig.douyinRuntimeConfig.vectcut
          }
        : null;
      const applySmokeSettingsConfig = async () => {
        if (!smokeExpertSettingsConfig && !smokeDouyinSettingsConfig) {
          return null;
        }
        await actions.navigateToView("settings");
        await waitForSettingsViewReady();
        return await actions.saveSettingsConfig({
          expertModelConfig: smokeExpertSettingsConfig ?? undefined,
          douyinRuntimeConfig: smokeDouyinSettingsConfig ?? undefined
        });
      };
      const runtimeCloudStatus = await api.runtimeCloud.getStatus();
      let runtimeCloudFetch = runtimeCloudStatus;
      let runtimeCloudFetchError = null;
      if (runtimeCloudStatus.apiKeyConfigured) {
        try {
          runtimeCloudFetch = await api.runtimeCloud.fetchConfig("init");
        } catch (error) {
          runtimeCloudFetchError = error instanceof Error ? error.message : String(error);
          if (!(runtimeCloudStatus && runtimeCloudStatus.state === "ready" && runtimeCloudStatus.config)) {
            throw error;
          }
        }
      }
      const runtimeStatus = await api.runtime.getStatus();
      const runtimeHealth = await api.runtime.health();
      const runtimeStartProbe = await api.runtime.start();
      const runtimeStatusAfterProbe = await api.runtime.getStatus();
      const runtimeHealthAfterProbe = await api.runtime.health();
      const runtimeLogs = await api.runtime.tailLogs(20);
      const runtimeTelemetryBeforeWait = await api.runtimeTelemetry.getStatus();
      const gatewayProbe = await waitForGatewayReady();
      const session = await api.auth.getSessionSummary();
      const profile = session.state === "authenticated" ? await api.profile.getSummary() : null;
      const credits = session.state === "authenticated" ? await api.credits.getSummary() : null;
      const skills = session.state === "authenticated" ? await api.skills.list() : [];
      const skillsPageState = smokeViewMode === "skills" ? await waitForSkillsPageReady() : null;
      const workspace = smokeViewMode === "settings"
        ? await api.workspace.getSummary()
        : skillsPageState?.workspace ?? await waitForWorkspaceReady();
      const readyWorkspaceSkills = Array.isArray(workspace.skills) ? workspace.skills.filter((skill) => skill.ready) : [];
      const readySkills = skills.filter((skill) => skill.ready);
      const selectedSkillId = preferredSkillId
        ? (readyWorkspaceSkills.find((skill) => skill.id === preferredSkillId)?.id
          ?? readySkills.find((skill) => skill.id === preferredSkillId)?.id)
        : undefined;
      const system = await api.system.getSummary();
      const settingsSave = await applySmokeSettingsConfig();
      if (settingsSave) {
        await api.runtime.start().catch(() => undefined);
        await waitForGatewayReady();
        await waitForGatewayStable();
      }
      if (smokeViewMode === "chat") {
        await waitForGatewayStable();
      }
      const runtimeStatusFinal = await api.runtime.getStatus();
      const runtimeHealthFinal = await api.runtime.health();
      const actionResult = smokeViewMode === "skills"
        ? await actions.navigateToView("skills")
        : smokeViewMode === "settings"
          ? {
            mode: "settings",
            sessionId: "",
            skillId: undefined,
            settingsSave
          }
          : smokeExpertEntryId
            ? await (async () => {
              const activated = await actions.activateExpertEntry(smokeExpertEntryId);
              if (smokeSendAfterExpertEntry) {
                const followup = await actions.sendConversationPrompt(${JSON.stringify(prompt)}, {
                  mode: activated.viewMode,
                  projectId: activated.viewMode === "experts" ? activated.currentProjectId : undefined,
                  skillId: selectedSkillId || undefined,
                  attachments: smokeAttachments.length ? smokeAttachments : undefined
                });
                return {
                  ...followup,
                  skillId: followup.skillId || selectedSkillId,
                  expertEntry: activated,
                  smokeExpertEntryAction: "activate-and-send",
                  settingsSave
                };
              }
              return {
                mode: activated.viewMode,
                sessionId: "",
                skillId: selectedSkillId,
                expertEntry: activated,
                smokeExpertEntryAction: "activate-only",
                settingsSave
              };
            })()
            : smokeSuggestionAction
              ? await (async () => {
                const suggestionState = await actions.resolveHomeIntentSuggestion(${JSON.stringify(prompt)});
                if (!suggestionState.visible) {
                  throw new Error("Renderer smoke did not surface a home intent suggestion for action " + smokeSuggestionAction + ".");
                }
                const waitForHomeIntentPending = async () => {
                  const deadline = Date.now() + 5000;
                  while (Date.now() < deadline) {
                    const state = window.__QJC_SMOKE__;
                    if (
                      state?.ui?.homeIntentSuggestionVisible
                      && state?.ui?.pendingHomeIntentPrompt === ${JSON.stringify(prompt)}
                    ) {
                      return window.__QJC_SMOKE_ACTIONS__ || actions;
                    }
                    await sleep(100);
                  }
                  throw new Error("Renderer smoke did not publish pending home intent state before action " + smokeSuggestionAction + ".");
                };
                const latestActions = await waitForHomeIntentPending();
                if (smokeSuggestionAction === "continue-home") {
                  const continued = await latestActions.continueHomeIntentSuggestion();
                  return {
                    mode: "chat",
                    sessionId: "",
                    skillId: selectedSkillId,
                    smokeScenario: "home-intent-suggestion",
                    homeIntentSuggestion: suggestionState,
                    homeIntentAction: smokeSuggestionAction,
                    homeIntentActionResult: continued,
                    settingsSave
                  };
                }
                if (smokeSuggestionAction === "switch-expert") {
                  const switched = await latestActions.switchHomeIntentSuggestion();
                  return {
                    mode: "chat",
                    sessionId: "",
                    skillId: selectedSkillId,
                    smokeScenario: "home-intent-suggestion",
                    homeIntentSuggestion: suggestionState,
                    homeIntentAction: smokeSuggestionAction,
                    homeIntentActionResult: switched,
                    settingsSave
                  };
                }
                if (smokeSuggestionAction === "dismiss") {
                  const dismissed = await latestActions.dismissHomeIntentSuggestion();
                  return {
                    mode: "chat",
                    sessionId: "",
                    skillId: selectedSkillId,
                    smokeScenario: "home-intent-suggestion",
                    homeIntentSuggestion: suggestionState,
                    homeIntentAction: smokeSuggestionAction,
                    homeIntentActionResult: dismissed,
                    homeIntentDismissed: true,
                    settingsSave
                  };
                }
                throw new Error("Unsupported smoke suggestion action: " + smokeSuggestionAction);
              })()
            : smokeScenario === "session-switch-stream"
              ? await (async () => {
                const scenario = await actions.runSessionSwitchStreamScenario(${JSON.stringify(prompt)});
                return {
                  ...scenario,
                  settingsSave
                };
              })()
              : await (async () => {
                const sent = await actions.sendConversationPrompt(${JSON.stringify(prompt)}, {
                  mode: ${JSON.stringify(smokeViewMode)},
                  projectId: ${JSON.stringify(smokeProjectId)},
                  skillId: selectedSkillId || undefined,
                  attachments: smokeAttachments.length ? smokeAttachments : undefined
                });
                return {
                  ...sent,
                  settingsSave
                };
              })();
      return {
        prompt: ${JSON.stringify(prompt)},
        smokeViewMode: ${JSON.stringify(smokeViewMode)},
        smokeProjectId: ${JSON.stringify(smokeProjectId)},
        smokeExpertEntryId,
        smokeSendAfterExpertEntry,
        smokeSuggestionAction,
        smokeScenario: actionResult.smokeScenario || smokeScenario,
        smokeAttachments,
        runtimeCloudStatus,
        runtimeCloudFetch,
        runtimeCloudFetchError,
        runtimeStatus,
        runtimeHealth,
        runtimeStartProbe,
        runtimeStatusAfterProbe,
        runtimeHealthAfterProbe,
        runtimeStatusFinal,
        runtimeHealthFinal,
        runtimeLogCount: runtimeLogs.length,
        runtimeTelemetryBeforeWait,
        session,
        profile,
        credits,
        skills,
        selectedSkillId: actionResult.skillId || selectedSkillId,
        initialSessionId: actionResult.sessionId,
        expertEntry: actionResult.expertEntry,
        smokeExpertEntryAction: actionResult.smokeExpertEntryAction,
        settingsSave: actionResult.settingsSave,
        homeIntentSuggestion: actionResult.homeIntentSuggestion,
        homeIntentAction: actionResult.homeIntentAction,
        homeIntentActionResult: actionResult.homeIntentActionResult,
        homeIntentDismissed: actionResult.homeIntentDismissed,
        sessionId: actionResult.sessionId,
        scenarioResult: actionResult.scenarioResult,
        system,
        health: gatewayProbe.health,
        status: gatewayProbe.status
      };
    })()`);

    await trace("runSmokeTest:send-script-finished");
    if (smokeViewMode === "settings") {
      let finalState: RendererSmokeState | null = null;
      const started = Date.now();
      while (Date.now() - started < 5000) {
        finalState = await waitForRendererSmokeState(window, 1000);
        if (finalState?.viewMode === "settings" && matchesExpectedSmokeModelConfig(finalState)) {
          break;
        }
        await delay(100);
      }
      if (!finalState || finalState.viewMode !== "settings") {
        throw new Error("Renderer smoke did not switch to settings view.");
      }
      if (!matchesExpectedSmokeModelConfig(finalState)) {
        throw new Error("Renderer smoke did not publish the saved settings model config in time.");
      }
      result.sendResult = sendResult;
      result.finalState = finalState;
      result.ok = true;
      await trace("runSmokeTest:settings-view-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }
    const streamState = smokeViewMode === "skills"
      ? await waitForRendererSmokeState(window, 5000)
      : sendResult.homeIntentDismissed
        || sendResult.smokeScenario === "home-intent-suggestion"
        || (sendResult.smokeExpertEntryId && sendResult.smokeExpertEntryAction !== "activate-and-send")
        ? await waitForRendererSmokeState(window, 5000)
        : await waitForRendererStreamSmoke(window, resolveSmokeStreamTimeoutMs());
    if (smokeViewMode === "skills") {
      const finalState = await waitForRendererSmokeState(window, 5000);
      const skillsPageCatalog = finalState?.skillsPageCatalog ?? streamState?.skillsPageCatalog ?? [];
      const workspaceSkills = finalState?.skills ?? streamState?.skills ?? [];
      if (!finalState || finalState.viewMode !== "skills") {
        throw new Error("Renderer smoke did not switch to skills view.");
      }
      if (!Array.isArray(skillsPageCatalog)) {
        throw new Error("Renderer smoke did not publish skillsPageCatalog.");
      }
      if (!Array.isArray(workspaceSkills)) {
        throw new Error("Renderer smoke did not publish workspace skills.");
      }
      result.sendResult = {
        ...sendResult,
        skillsPageCatalogCount: skillsPageCatalog.length,
        workspaceSkillCount: workspaceSkills.length,
        firstSkillsPageCatalogId: skillsPageCatalog[0]?.id ?? null
      };
      result.finalState = finalState;
      result.ok = true;
      await trace("runSmokeTest:skills-view-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }
    if (sendResult.smokeScenario === "session-switch-stream") {
      const finalState = await waitForRendererSmokeState(window, 5000);
      result.sendResult = {
        ...sendResult,
        streamSmoke: finalState?.streamSmoke ?? null
      };
      result.finalState = finalState;
      result.ok = true;
      await trace("runSmokeTest:session-switch-stream-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }
    if (sendResult.smokeScenario === "home-intent-suggestion") {
      const finalState = await (async () => {
        const started = Date.now();
        let latestState: RendererSmokeState | null = null;
        while (Date.now() - started < 5000) {
          latestState = await waitForRendererSmokeState(window, 1000);
          const streamSessionId = latestState?.streamSmoke?.sessionId;
          if (
            latestState
            && !latestState.ui?.homeIntentSuggestionVisible
            && !latestState.ui?.pendingHomeIntentPrompt
            && (latestState.activeSessionId || streamSessionId)
          ) {
            return latestState;
          }
          await delay(100);
        }
        return latestState;
      })();
      const postActionResult = await window.webContents.executeJavaScript(`(async () => {
        const state = window.__QJC_SMOKE__;
        const api = window.qjcDesktop;
        if (!api) {
          throw new Error("Renderer is using mock desktop API.");
        }
        const sessionId = state?.streamSmoke?.sessionId || state?.activeSessionId || "";
        const runtimeTelemetryAfterWait = await api.runtimeTelemetry.getStatus();
        const health = await api.gateway.health();
        const status = await api.gateway.status();
        return {
          runtimeTelemetryAfterWait,
          sessionId,
          currentProjectId: state?.ui?.currentProjectId,
          homeIntentSuggestionVisible: state?.ui?.homeIntentSuggestionVisible,
          homeIntentSuggestionProjectId: state?.ui?.homeIntentSuggestionProjectId,
          homeIntentSuggestionProjectName: state?.ui?.homeIntentSuggestionProjectName,
          pendingHomeIntentPrompt: state?.ui?.pendingHomeIntentPrompt,
          streamSmoke: state?.streamSmoke ?? null,
          health,
          status
        };
      })()`);
      result.sendResult = {
        ...sendResult,
        ...postActionResult,
        initialGatewayHealth: sendResult.health,
        initialGatewayStatus: sendResult.status,
        finalGatewayHealth: postActionResult.health,
        finalGatewayStatus: postActionResult.status
      };
      result.finalState = finalState;
      result.ok = true;
      await trace("runSmokeTest:home-intent-suggestion-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }
    if (sendResult.homeIntentDismissed) {
      const finalState = await waitForRendererSmokeState(window, 5000);
      result.sendResult = sendResult;
      result.finalState = finalState;
      result.ok = true;
      await trace("runSmokeTest:home-intent-dismiss-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }
    if (sendResult.smokeExpertEntryId && sendResult.smokeExpertEntryAction !== "activate-and-send") {
      const finalState = await waitForRendererSmokeState(window, 5000);
      const postExpertEntryResult = await window.webContents.executeJavaScript(`(async () => {
        const api = window.qjcDesktop;
        if (!api) {
          throw new Error("Renderer is using mock desktop API.");
        }
        const runtimeTelemetryAfterWait = await api.runtimeTelemetry.getStatus();
        const diagnostics = await api.diagnostics.exportSnapshot();
        const health = await api.gateway.health();
        const status = await api.gateway.status();
        return {
          runtimeTelemetryAfterWait,
          diagnostics,
          health,
          status
        };
      })()`);
      const combinedSendResult = {
        ...sendResult,
        ...postExpertEntryResult,
        initialGatewayHealth: sendResult.health,
        initialGatewayStatus: sendResult.status,
        finalGatewayHealth: postExpertEntryResult.health,
        finalGatewayStatus: postExpertEntryResult.status
      };
      const diagnosticsPath = typeof (combinedSendResult as { diagnostics?: { filePath?: string } }).diagnostics?.filePath === "string"
        ? (combinedSendResult as { diagnostics: { filePath: string } }).diagnostics.filePath
        : undefined;
      const diagnosticsSnapshot = diagnosticsPath
        ? JSON.parse(await readFile(diagnosticsPath, "utf8")) as Record<string, unknown>
        : null;
      result.sendResult = combinedSendResult;
      result.finalState = finalState;
      result.diagnosticsSnapshot = diagnosticsSnapshot;
      result.ok = true;
      await trace("runSmokeTest:expert-entry-success");
      result.finishedAt = new Date().toISOString();
      await trace("runSmokeTest:writing-output");
      await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
      await trace("runSmokeTest:output-written");
      app.quit();
      return;
    }
    if (!streamState?.streamSmoke) {
      result.sendResult = sendResult;
      result.finalState = await waitForRendererSmokeState(window, 5000);
      throw new Error("Renderer stream smoke did not reach a terminal state.");
    }

    await trace("runSmokeTest:stream-terminal:" + String(streamState.streamSmoke.phase ?? "unknown"));
    const workspaceLaunchAccepted = (
      process.env.QJCLAW_SMOKE_ACCEPT_WORKSPACE_LAUNCH === "1"
      && Array.isArray(streamState.streamSmoke.statusLabels)
      && streamState.streamSmoke.statusLabels.some((label: string) => String(label || "").includes("Launching project workspace"))
      && !["completed", "fallback", "error"].includes(String(streamState.streamSmoke.phase ?? ""))
    );
    if (workspaceLaunchAccepted) {
      await trace("runSmokeTest:workspace-launch-accepted");
      if (smokeWaitForPaths.length > 0) {
        await trace("runSmokeTest:artifact-wait-start:" + smokeWaitForPaths.length);
        await waitForSmokePaths(smokeWaitForPaths, smokeWaitForPathsTimeoutMs);
        await trace("runSmokeTest:artifact-wait-complete");
      }
      if (
        process.env.QJCLAW_SMOKE_WAIT_FOR_EXPECTED_STAGE === "video_generated"
        && process.env.QJCLAW_SMOKE_PROJECT_ID
      ) {
        await trace("runSmokeTest:video-artifact-wait-start");
        await waitForWorkspaceVideoGeneration(
          app.getPath("userData"),
          process.env.QJCLAW_SMOKE_PROJECT_ID,
          smokeWaitForPathsTimeoutMs
        );
        await trace("runSmokeTest:video-artifact-wait-complete");
      }
    }
    await delay(1500);
    const finalState = (
      process.env.QJCLAW_SMOKE_WAIT_FOR_EXPECTED_STAGE === "video_generated"
        ? await waitForRendererTerminalStreamSmoke(window, 30000)
        : null
    ) ?? await waitForRendererSmokeState(window, 5000);
    const streamSmoke = finalState?.streamSmoke ?? streamState.streamSmoke;
    await trace("runSmokeTest:before-post-stream-script");
    const postStreamResult = await window.webContents.executeJavaScript(`(async () => {
      const api = window.qjcDesktop;
      const state = window.__QJC_SMOKE__;
      if (!api) {
        throw new Error("Renderer is using mock desktop API.");
      }
      let sessions = state?.workspaceSummary?.sessions?.length
        ? state.workspaceSummary.sessions
        : await api.chat.listSessions().catch(() => []);
      if ((!Array.isArray(sessions) || sessions.length === 0) && Array.isArray(state?.sessions)) {
        sessions = state.sessions;
      }
      const sessionId = (state?.streamSmoke?.sessionId && sessions.some((session) => session.id === state.streamSmoke.sessionId)
        ? state.streamSmoke.sessionId
        : state?.activeSessionId
          || sessions.find((session) => session.id === state?.activeSessionId)?.id
          || sessions[0]?.id);
      if (!sessionId) {
        throw new Error("Renderer smoke state did not publish a project-scoped session.");
      }
      const runtimeTelemetryAfterWait = await api.runtimeTelemetry.getStatus();
      const messages = await api.chat.listMessages(sessionId);
      const chatMessages = messages.filter((message) => message.role === "assistant" || message.role === "user");
      const lastAssistantMessage = [...chatMessages].reverse().find((message) => message.role === "assistant") ?? null;
      const logs = await api.gateway.tailLogs(20);
      const diagnostics = await api.diagnostics.exportSnapshot();
      const health = await api.gateway.health();
      const status = await api.gateway.status();
      return {
        runtimeTelemetryAfterWait,
        sessionId,
        messageCount: chatMessages.length,
        rawMessageCount: messages.length,
        lastMessage: chatMessages.at(-1) ?? null,
        lastAssistantMessage,
        logCount: logs.length,
        diagnostics,
        health,
        status,
        currentProjectId: state?.ui?.currentProjectId,
        homeIntentSuggestionVisible: state?.ui?.homeIntentSuggestionVisible,
        homeIntentSuggestionProjectId: state?.ui?.homeIntentSuggestionProjectId,
        homeIntentSuggestionProjectName: state?.ui?.homeIntentSuggestionProjectName,
        pendingHomeIntentPrompt: state?.ui?.pendingHomeIntentPrompt
      };
    })()`);
    await trace("runSmokeTest:post-stream-script-finished");
    const combinedSendResult = {
      ...sendResult,
      ...postStreamResult,
      initialGatewayHealth: sendResult.health,
      initialGatewayStatus: sendResult.status,
      finalGatewayHealth: postStreamResult.health,
      finalGatewayStatus: postStreamResult.status,
      streamSmoke
    };
    const diagnosticsPath = typeof (combinedSendResult as { diagnostics?: { filePath?: string } }).diagnostics?.filePath === "string"
      ? (combinedSendResult as { diagnostics: { filePath: string } }).diagnostics.filePath
      : undefined;
    const diagnosticsSnapshot = diagnosticsPath
      ? JSON.parse(await readFile(diagnosticsPath, "utf8")) as Record<string, unknown>
      : null;

    result.sendResult = combinedSendResult;
    result.finalState = finalState;
    result.diagnosticsSnapshot = diagnosticsSnapshot;
    result.ok = true;
    await trace("runSmokeTest:success");
  } catch (error) {
    result.ok = false;
    result.error = error instanceof Error ? error.message : String(error);
    await trace("runSmokeTest:error:" + String(result.error));
  }

  result.finishedAt = new Date().toISOString();
  await trace("runSmokeTest:writing-output");
  await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
  await trace("runSmokeTest:output-written");
  app.quit();
}

async function runSingleInstanceSmoke(window: BrowserWindow, outputPath: string, systemSummary: SystemSummary): Promise<void> {
  const result: Record<string, unknown> = {
    startedAt: new Date().toISOString()
  };
  const tracePath = outputPath + ".trace.log";
  const trace = async (message: string) => {
    const line = "[" + new Date().toISOString() + "] " + message + "\n";
    await appendFile(tracePath, line, "utf8").catch(() => undefined);
  };
  const runtimeManagerLogPath = path.join(systemSummary.logsPath, "runtime-manager.log");
  const waitForInitialReadySnapshot = async (): Promise<{ readySnapshot: SingleInstanceReadySnapshot; initialLaunchCommandCount: number }> => {
    const deadline = Date.now() + 30_000;
    let runtimeLaunchObservedAt = 0;

    while (Date.now() < deadline) {
      if (window.isDestroyed()) {
        throw new Error("Primary instance window was destroyed before single-instance readiness.");
      }
      if (window.webContents.isDestroyed()) {
        throw new Error("Primary instance webContents was destroyed before single-instance readiness.");
      }

      if (mainWindowLoadState?.status === "did-fail-load") {
        throw new Error("Primary instance main window failed to load: " + JSON.stringify(mainWindowLoadState));
      }
      if (mainWindowLoadState?.status === "render-process-gone") {
        throw new Error("Primary instance renderer exited before single-instance readiness: " + JSON.stringify(mainWindowLoadState));
      }
      if (mainWindowLoadState?.status === "web-contents-destroyed" || mainWindowLoadState?.status === "window-closed") {
        throw new Error("Primary instance main window closed before single-instance readiness: " + JSON.stringify(mainWindowLoadState));
      }

      const initialLaunchCommandCount = await countFilePatternMatches(runtimeManagerLogPath, "Launching bundled runtime command");
      if (initialLaunchCommandCount >= 1 && runtimeLaunchObservedAt === 0) {
        runtimeLaunchObservedAt = Date.now();
      }

      const currentSnapshot = snapshotWindowInventory("ready-check");
      const mainWindowExists = currentSnapshot.windowCount === 1
        && currentSnapshot.visibleWindowCount >= 1
        && typeof currentSnapshot.mainWindow === "object"
        && currentSnapshot.mainWindow !== null
        && (currentSnapshot.mainWindow as { exists?: boolean }).exists === true;

      if (mainWindowExists && runtimeLaunchObservedAt > 0 && Date.now() - runtimeLaunchObservedAt >= 1000) {
        return {
          readySnapshot: {
            ...snapshotWindowInventory("ready"),
            runtimeManagerLogPath,
            initialLaunchCommandCount
          },
          initialLaunchCommandCount
        };
      }

      await delay(250);
    }

    throw new Error("Timed out waiting for primary instance readiness. lastWindowSnapshot="
      + JSON.stringify(snapshotWindowInventory("ready-timeout"))
      + " lastLoadState="
      + JSON.stringify(mainWindowLoadState));
  };

  try {
    await trace("runSingleInstanceSmoke:start");
    const { readySnapshot, initialLaunchCommandCount } = await waitForInitialReadySnapshot();
    result.initialWindowSnapshot = readySnapshot;
    result.initialRendererState = await waitForRendererSmokeState(window, 1000).catch(() => null);
    await trace("runSingleInstanceSmoke:primary-instance-ready");
    await writeSingleInstanceSmokeSignal(singleInstanceSmokeReadyPath, readySnapshot);
    await trace("runSingleInstanceSmoke:ready-signal-written");

    const deadline = Date.now() + 30_000;
    while (Date.now() < deadline && secondInstanceEventCount < 1) {
      await delay(250);
    }
    if (secondInstanceEventCount < 1) {
      throw new Error("Timed out waiting for second-instance activation.");
    }

    await trace("runSingleInstanceSmoke:second-instance-detected");
    await delay(1500);

    result.finalRendererState = await waitForRendererSmokeState(window, 1000).catch(() => null);
    result.singleInstance = {
      initialLaunchCommandCount,
      finalLaunchCommandCount: await countFilePatternMatches(runtimeManagerLogPath, "Launching bundled runtime command"),
      secondInstanceEventCount,
      lastSecondInstanceEventSnapshot,
      finalWindowSnapshot: snapshotWindowInventory("final"),
      runtimeManagerLogPath
    };
    result.ok = true;
    await trace("runSingleInstanceSmoke:success");
  } catch (error) {
    result.ok = false;
    result.error = error instanceof Error ? error.message : String(error);
    await trace("runSingleInstanceSmoke:error:" + String(result.error));
  }

  result.finishedAt = new Date().toISOString();
  await trace("runSingleInstanceSmoke:writing-output");
  await writeFile(outputPath, JSON.stringify(result, null, 2), "utf8");
  await trace("runSingleInstanceSmoke:output-written");
  app.quit();
}

async function bootstrap(): Promise<void> {
  await app.whenReady();

  const smokeOutputPath = process.env.QJCLAW_SMOKE_OUTPUT;
  const smokeEnabled = Boolean(smokeOutputPath);
  mainWindowSmokeEnabled = smokeEnabled;
  const smokeCloudBaseUrl = process.env.QJCLAW_SMOKE_CLOUD_API_BASE_URL?.trim();
  const smokeAuthToken = process.env.QJCLAW_SMOKE_AUTH_TOKEN?.trim();
  const smokeRuntimeApiKey = process.env.QJCLAW_SMOKE_RUNTIME_CLOUD_API_KEY ?? "smoke-runtime-api-key";
  const smokeCloudBootstrapEnabled = Boolean(smokeCloudBaseUrl && smokeAuthToken);
  let startupLogger: StartupLogger | undefined;
  const traceBootstrap = async (message: string, context?: Record<string, unknown>) => {
    await startupLogger?.info("bootstrap", "trace", message, context);
    if (!smokeOutputPath) {
      return;
    }
    const line = "[" + new Date().toISOString() + "] bootstrap:" + message + "\n";
    await appendFile(smokeOutputPath + ".trace.log", line, "utf8").catch(() => undefined);
  };
  const systemSummary = buildSystemSummary();
  startupLogger = new StartupLogger(systemSummary.logsPath);
  startupLoggerRef = startupLogger;
  await traceBootstrap("when-ready", { isPackaged: systemSummary.isPackaged, userDataPath: systemSummary.userDataPath, logsPath: systemSummary.logsPath, smokeEnabled, smokeCloudBootstrapEnabled });
  const developmentIconPath = await configureDevelopmentDockIcon(systemSummary);
  await traceBootstrap("development-icon-configured", { developmentIconPath });

  const configService = new AppConfigService(systemSummary.userDataPath);
  const config = await configService.load();
  const secretManager = new SecretManager(systemSummary.userDataPath);
  await secretManager.load();

  let stopSmokeCloudApiServer: (() => Promise<void>) | undefined;

  if (smokeEnabled || smokeCloudBootstrapEnabled) {
    await traceBootstrap("smoke-config-start");
    if (smokeCloudBaseUrl && smokeAuthToken) {
      stopSmokeCloudApiServer = await startSmokeCloudApiServer(smokeCloudBaseUrl, smokeAuthToken, smokeRuntimeApiKey);
    }
    if (smokeCloudBaseUrl && smokeAuthToken) {
      await configService.save({
        setupMode: config.setupMode,
        provider: config.provider,
        baseUrl: config.baseUrl,
        defaultModel: config.defaultModel,
        workspacePath: config.workspacePath,
        gatewayUrl: config.gatewayUrl,
        cloudApiBaseUrl: smokeCloudBaseUrl ?? config.cloudApiBaseUrl,
        runtimeCloudApiBaseUrl: smokeCloudBaseUrl ?? config.runtimeCloudApiBaseUrl,
        runtimeMode: config.runtimeMode,
        authToken: smokeAuthToken,
        gatewayToken: undefined,
        apiKey: smokeRuntimeApiKey
      });
      await secretManager.setAuthToken(smokeAuthToken);
      await secretManager.setApiKey(smokeRuntimeApiKey);
    }
  }

  const diagnosticsService = new DiagnosticsService(systemSummary.userDataPath, startupLogger);
  const deviceIdentityService = new DeviceIdentityService(systemSummary.userDataPath);
  await deviceIdentityService.load();
  await traceBootstrap("device-identity-loaded");
  await traceBootstrap("local-openclaw-config-start");
  const localOpenClawConfig = await loadLocalOpenClawGatewayConfig();
  await traceBootstrap("local-openclaw-config-done");
  const packagedBootstrapService = new PackagedBootstrapService(configService, {
    bootstrapRoot: resolvePackagedBootstrapRoot(systemSummary),
    startupLogger
  });
  const runtimeCloudClient = new OpenClawConfigClient(configService, secretManager, startupLogger, packagedBootstrapService);
  await traceBootstrap("runtime-cloud-hydrate-start");
  await runtimeCloudClient.hydrateCache();
  await traceBootstrap("runtime-cloud-hydrate-done");
  await traceBootstrap("skill-store-create-start");
  const skillStore = new SkillStoreService(systemSummary.userDataPath);
  await traceBootstrap("skill-store-create-done");
  await traceBootstrap("generic-skills-root-start");
  const genericSkillsRoot = resolveGenericSkillsRoot(systemSummary);
  await traceBootstrap("generic-skills-root-done", { genericSkillsRoot });
  await traceBootstrap("project-store-create-start");
  const projectStore = new ProjectStoreService(configService, { qSkillsRoot: genericSkillsRoot });
  await traceBootstrap("project-store-create-done");
  await traceBootstrap("project-store-initialize-start");
  await projectStore.initialize();
  await traceBootstrap("project-store-initialized");
  const projectBundleService = new ProjectBundleService(configService, projectStore, startupLogger);
  const syncProjectBundles = async (
    skills: Parameters<ProjectBundleService["syncRemoteBundles"]>[0],
    configVersion: string | undefined,
    action: RuntimeCloudFetchAction
  ) => {
    const timeoutMessage = `Project bundle sync timed out after ${Math.round(PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS / 1000)}s.`;
    console.info("[bundle-bootstrap]", "bundle.sync.start", {
      action,
      configVersion,
      skillCount: skills.length,
      timeoutMs: PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS
    });
    await traceBootstrap("project-bundle-sync:" + action + ":start:" + skills.length);
    try {
      await withTimeout(
        projectBundleService.syncRemoteBundles(skills, configVersion, action),
        PROJECT_BUNDLE_BOOTSTRAP_TIMEOUT_MS,
        timeoutMessage
      );
      console.info("[bundle-bootstrap]", "bundle.sync.done", {
        action,
        configVersion,
        skillCount: skills.length
      });
      await traceBootstrap("project-bundle-sync:" + action + ":done");
    } catch (error) {
      const rawMessage = error instanceof Error ? (error.stack ?? error.message) : String(error);
      const userMessage = error instanceof Error ? error.message : String(error);
      const timedOut = userMessage === timeoutMessage;
      if (!timedOut) {
        projectBundleService.setSyncError(userMessage);
      }
      const logContext = {
        action,
        configVersion,
        skillIds: skills.map((skill) => skill.skillId),
        error: rawMessage,
        timedOut
      };
      if (timedOut) {
        console.warn("[bundle-bootstrap]", "bundle.sync.timeout", logContext);
        await traceBootstrap("project-bundle-sync:" + action + ":timeout:" + rawMessage.replace(/\r?\n/g, " | "));
        return;
      }
      console.error("[bundle-bootstrap]", "bundle.sync.error", {
        ...logContext
      });
      await traceBootstrap("project-bundle-sync:" + action + ":error:" + rawMessage.replace(/\r?\n/g, " | "));
    }
  };
  const projectContextService = new ProjectContextService(projectStore);
  const projectIntentRouter = new ProjectIntentRouterService(projectStore);
  const projectSkillRouter = new ProjectSkillRouterService(projectStore);
  const projectChatTargetResolver = new ProjectChatTargetResolverService(projectStore, projectIntentRouter);
  const projectExecutionRouter = new ProjectExecutionRouter(systemSummary);
  let lastRemoteSkillSyncKey = "";
  runtimeCloudClient.onPayloadUpdated(async ({ config: payloadConfig, skills }) => {
    const remoteSkillSyncKey = JSON.stringify(skills.map((skill) => ({
      skillId: skill.skillId,
      fileName: skill.fileName ?? null,
      fileSize: typeof skill.fileSize === "number" ? skill.fileSize : null,
      downloadUrl: skill.downloadUrl ?? null
    })));
    if (remoteSkillSyncKey === lastRemoteSkillSyncKey) {
      return;
    }
    lastRemoteSkillSyncKey = remoteSkillSyncKey;
    await skillStore.reconcile(skills, payloadConfig.configVersion);
    await syncProjectBundles(skills, payloadConfig.configVersion, "sync");
  });
  const cachedRuntimeCloudStatus = await runtimeCloudClient.getStatus();
  const cachedRuntimeCloudConfig = cachedRuntimeCloudStatus.config;

  const runtimeManager = new RuntimeManager({
    vendorRuntimeDir: resolveVendorRuntimeDir(systemSummary),
    runtimeDataDir: path.join(systemSummary.userDataPath, "runtime"),
    logFilePath: path.join(systemSummary.logsPath, "runtime-manager.log"),
    requestedMode: resolveRequestedRuntimeMode(config.runtimeMode),
    managedConfigResolver: async ({ defaultConfig }) => {
      const latestConfig = await configService.load();
      const apiKey = await secretManager.getApiKey();
      if (latestConfig.setupMode === "direct-provider") {
        if (!apiKey) {
          throw new Error("Direct provider API Key is not configured.");
        }
        return buildDirectProviderManagedConfig(defaultConfig, latestConfig, apiKey);
      }
      if (!apiKey) {
        return defaultConfig;
      }
      const chatModelApiKey = await secretManager.getCopywritingModelApiKey();
      if (!hasConfiguredClientChatModel(latestConfig, chatModelApiKey)) {
        return defaultConfig;
      }
      return buildClientChatManagedConfig(defaultConfig, latestConfig, chatModelApiKey);
    },
    strictBundledRuntime: systemSummary.isPackaged
  });
  await traceBootstrap("runtime-configure-start");
  await runtimeManager.configure();
  await traceBootstrap("runtime-configure-done");
  const projectWorkspaceExecutor = new ProjectWorkspaceExecutorService(runtimeManager);
  const taskPanelService = new TaskPanelService(systemSummary.userDataPath);
  const runtimeStatus = await runtimeManager.status();
  const runtimeGatewayConnection = await runtimeManager.getGatewayConnection();
  if (systemSummary.isPackaged && runtimeStatus.payloadState !== "ready") {
    throw new Error(`Packaged app bundled runtime is not ready: ${runtimeStatus.payloadState}`);
  }

  const shouldUseBundledRuntimeGateway = config.runtimeMode !== "external-gateway"
    && runtimeStatus.selectedMode === "bundled-runtime"
    && typeof runtimeGatewayConnection.url === "string";
  const gatewayClient = new GatewayClient({
    url: shouldUseBundledRuntimeGateway
      ? runtimeGatewayConnection.url!
      : resolveEffectiveGatewayUrl(
        config.gatewayUrl,
        shouldUseLocalOpenClawGateway(systemSummary.isPackaged, config.runtimeMode) ? localOpenClawConfig?.gatewayUrl : undefined
      ),
    token: shouldUseBundledRuntimeGateway
      ? runtimeGatewayConnection.token
      : resolveEffectiveGatewayToken(
        await secretManager.getGatewayToken(),
        shouldUseLocalOpenClawGateway(systemSummary.isPackaged, config.runtimeMode) ? localOpenClawConfig?.gatewayToken : undefined
      ),
    deviceToken: await secretManager.getDeviceToken(),
    deviceIdentity: deviceIdentityService,
    onDeviceToken: async (deviceToken) => {
      await secretManager.setDeviceToken(deviceToken);
    }
  });

  const runtimeSkillBridge = new RuntimeSkillBridgeService(skillStore, projectStore, runtimeManager);
  await runtimeSkillBridge.clearManagedSkills().catch(() => undefined);

  const authClient = new AuthClient(configService, secretManager);
  const profileClient = new ProfileClient(configService, secretManager);
  const creditClient = new CreditClient(configService, secretManager);
  const skillClient = new SkillClient(skillStore);
  const expertCatalogService = new ExpertCatalogService(systemSummary);
  const skillCatalogService = new SkillCatalogService({
    systemSummary,
    projectStore,
    qSkillsRoot: genericSkillsRoot
  });
  const modelConfigClient = new ModelConfigClient(configService, secretManager);
  const dailyReportService = new DailyReportService({
    userDataPath: systemSummary.userDataPath,
    configService,
    secretManager
  });
  await dailyReportService.start();
  const runtimeCloudSupervisor = new RuntimeCloudSupervisor({
    appVersion: app.getVersion(),
    configService,
    runtimeCloudClient,
    runtimeManager,
    secretManager
  });
    runtimeCloudSupervisor.onActivity((event) => {
    dailyReportService.handleActivity(event);
  });

  await traceBootstrap("register-ipc");
  const desktopApi: RegisteredDesktopIpc = registerDesktopIpc({
    appVersion: app.getVersion(),
    configService,
    diagnosticsService,
    gatewayClient,
    runtimeManager,
    secretManager,
    authClient,
    profileClient,
    creditClient,
    skillClient,
    expertCatalogService,
    skillCatalogService,
    skillStore,
    modelConfigClient,
    runtimeCloudClient,
    runtimeCloudSupervisor,
    dailyReportService,
    runtimeSkillBridge,
    projectStore,
    projectBundleService,
    projectContextService,
    projectChatTargetResolver,
    projectSkillRouter,
    projectExecutionRouter,
    projectWorkspaceExecutor,
    taskPanelService,
    startupLogger: startupLogger!,
    systemSummary,
    localOpenClawConfig
  });

  let beforeQuitHandled = false;
  app.on("before-quit", (event) => {
    if (beforeQuitHandled) {
      return;
    }

    beforeQuitHandled = true;
    event.preventDefault();
    void (async () => {
      await runtimeCloudSupervisor.stop("app-before-quit");
      await dailyReportService.stop();
      await runtimeManager.stop();
      await runtimeSkillBridge.clearManagedSkills().catch(() => undefined);
      if (stopSmokeCloudApiServer) {
        await stopSmokeCloudApiServer();
        stopSmokeCloudApiServer = undefined;
      }
      app.quit();
    })();
  });

  desktopLifecycleReady = true;
  await traceBootstrap("create-window");
  const window = smokeEnabled
    ? createTrackedMainWindow(smokeEnabled)
    : restoreOrCreateMainWindow("bootstrap") ?? createTrackedMainWindow(smokeEnabled);
  await traceBootstrap("window-created");

  if (cachedRuntimeCloudConfig) {
    void (async () => {
      const cachedRemoteSkills = runtimeCloudClient.getRemoteSkillAssets();
      lastRemoteSkillSyncKey = JSON.stringify(cachedRemoteSkills.map((skill) => ({
        skillId: skill.skillId,
        fileName: skill.fileName ?? null,
        fileSize: typeof skill.fileSize === "number" ? skill.fileSize : null,
        downloadUrl: skill.downloadUrl ?? null
      })));
      await skillStore.reconcile(cachedRemoteSkills, cachedRuntimeCloudConfig.configVersion).catch(() => undefined);
      await syncProjectBundles(cachedRemoteSkills, cachedRuntimeCloudConfig.configVersion, "init");
    })().catch((error) => {
      console.error("Cached runtime cloud bootstrap sync skipped:", error instanceof Error ? error.message : String(error));
    });
  }

  void (async () => {
    await traceBootstrap("bootstrap-warmup-scheduled");
    await desktopApi.__internal.queueWorkspaceWarmup("bootstrap", { action: "init" });
  })().catch((error) => {
    console.error("Bundled runtime bootstrap skipped:", error instanceof Error ? error.message : String(error));
  });

  if (smokeEnabled && smokeOutputPath) {
    await traceBootstrap("run-smoke-test-start");
    smokeTestInFlight = true;
    const smokeTask = singleInstanceSmokeMode
      ? runSingleInstanceSmoke(window, smokeOutputPath, systemSummary)
      : runSmokeTest(window, smokeOutputPath);
    void smokeTask.finally(() => {
      smokeTestInFlight = false;
    });
  }

  app.on("activate", () => {
    restoreOrCreateMainWindow("activate");
  });
}

app.on("window-all-closed", () => {
  if (process.platform !== "darwin" && !smokeTestInFlight) {
    app.quit();
  }
});

const hasSingleInstanceLock = smokeOutputPathEnabled || app.requestSingleInstanceLock();

if (!hasSingleInstanceLock) {
  app.exit(0);
} else {
  app.on("second-instance", () => {
    secondInstanceEventCount += 1;
    const handle = async () => {
      restoreOrCreateMainWindow("second-instance");
      const snapshot = snapshotWindowInventory("second-instance");
      lastSecondInstanceEventSnapshot = snapshot;
      await writeSingleInstanceSmokeSignal(singleInstanceSmokeEventPath, snapshot);
      await startupLoggerRef?.info("bootstrap", "single-instance.second-instance", "Second app instance redirected to the existing main window.", {
        secondInstanceEventCount,
        windowCount: snapshot.windowCount,
        visibleWindowCount: snapshot.visibleWindowCount,
        focusedWindowCount: snapshot.focusedWindowCount
      });
    };

    if (app.isReady()) {
      void handle();
      return;
    }

    void app.whenReady().then(handle);
  });

  void bootstrap().catch(async (error) => {
    const smokeOutputPath = process.env.QJCLAW_SMOKE_OUTPUT;
    const message = error instanceof Error ? error.message : String(error);
    if (smokeOutputPath) {
      await writeFile(smokeOutputPath, JSON.stringify({ ok: false, stage: "bootstrap", error: message, finishedAt: new Date().toISOString() }, null, 2), "utf8");
    } else {
      console.error(message);
    }
    app.quit();
  });
}


