// SPEC: PJ2026-01040111 long-running Workbench observation.
// Responsibility: Runner command queue, authentication, navigation, control page recovery, and prompt submission source fragment.

export function nodeWebObserveRunnerControlSource(): string {
  return String.raw`async function drainOneCommand() {
  const entries = (await readdir(dirs.commandsPending).catch(() => [])).filter((name) => name.endsWith(".json")).sort();
  const name = entries[0];
  if (!name) return;
  const pending = path.join(dirs.commandsPending, name);
  const processing = path.join(dirs.commandsProcessing, name);
  await rename(pending, processing).catch(() => null);
  const raw = await readFile(processing, "utf8");
  const command = JSON.parse(raw);
  const id = safeId(command.id || name.replace(/[.]json$/u, ""));
  command.id = id;
  const stopCommandSampler = startCommandActiveSampler(command);
  try {
    const result = await processCommand(command);
    const done = { ok: true, commandId: id, type: command.type, completedAt: new Date().toISOString(), result: sanitize(result) };
    await writeFile(path.join(dirs.commandsDone, id + ".json"), JSON.stringify(done, null, 2) + "\n", { mode: 0o600 });
    await appendJsonl(files.control, controlRecord(command, "completed", done.result));
    await unlink(processing).catch(() => {});
  } catch (error) {
    const failureSample = await samplePage("command-failed", { refreshObserver: false, screenshot: false })
      .then(() => ({ ok: true, sampleSeq, valuesRedacted: true }))
      .catch((sampleError) => ({ ok: false, error: errorSummary(sampleError), valuesRedacted: true }));
    const failed = { ok: false, commandId: id, type: command.type, failedAt: new Date().toISOString(), error: errorSummary(error), failureSample };
    await writeFile(path.join(dirs.commandsFailed, id + ".json"), JSON.stringify(failed, null, 2) + "\n", { mode: 0o600 });
    await appendJsonl(files.control, controlRecord(command, "failed", { error: failed.error, failureSample }));
    await unlink(processing).catch(() => {});
  } finally {
    stopCommandSampler();
    activeCommandId = null;
    activeCommandType = null;
    await writeHeartbeat({ status: terminalStatus });
  }
}

function startCommandActiveSampler(command) {
  const intervalMs = Math.max(1000, Number(sampleIntervalMs) || 5000);
  const heartbeatIntervalMs = Math.min(5000, intervalMs);
  let stopped = false;
  let timer = null;
  let heartbeatTimer = null;
  let inFlight = false;
  const heartbeat = () => {
    if (stopped) return;
    void writeHeartbeat({ status: terminalStatus, activeCommandId: command.id, activeCommandType: command.type, commandActive: true })
      .catch((error) => appendJsonl(files.errors, eventRecord("command-active-heartbeat-error", { commandId: command.id, commandType: command.type, error: errorSummary(error) })));
  };
  const schedule = () => {
    if (stopped) return;
    timer = setTimeout(tick, intervalMs);
    if (timer && typeof timer.unref === "function") timer.unref();
  };
  const tick = () => {
    if (stopped) return;
    if (inFlight) {
      schedule();
      return;
    }
    inFlight = true;
    samplePage("command-active", { refreshObserver: false, screenshot: false })
      .catch((error) => appendJsonl(files.errors, eventRecord("command-active-sample-error", { commandId: command.id, commandType: command.type, error: errorSummary(error) })))
      .finally(() => {
        inFlight = false;
        schedule();
      });
  };
  heartbeatTimer = setInterval(heartbeat, heartbeatIntervalMs);
  if (heartbeatTimer && typeof heartbeatTimer.unref === "function") heartbeatTimer.unref();
  schedule();
  return () => {
    stopped = true;
    if (timer) clearTimeout(timer);
    if (heartbeatTimer) clearInterval(heartbeatTimer);
  };
}

async function processCommand(command) {
  commandSeq += 1;
  activeCommandId = command.id;
  activeCommandType = command.type;
  await writeHeartbeat({ status: "running", activeCommandId, activeCommandType });
  await appendJsonl(files.control, controlRecord(command, "started", commandInputSummary(command)));
  switch (command.type) {
    case "login": return authenticate(context);
    case "loginAccount": return withObserverSync(await loginAccount(command), "loginAccount");
    case "logout": return withObserverSync(await logoutAccount(command), "logout");
    case "listSessions": return withObserverSync(await listSessions(command), "listSessions");
    case "switchSessions": return withObserverSync(await switchSessions(command), "switchSessions");
    case "preflight": return preflightSummary();
    case "goto": return withObserverSync(await gotoTarget(command.path || command.url || targetPath), "goto");
    case "newSession": return withObserverSync(await createSessionFromUi(), "newSession");
    case "sendPrompt": return withObserverSync(await sendPrompt(String(command.text || ""), {
      provider: command.provider,
      expectedAction: "turn",
      responsePath: "/v1/agent/chat",
      alternateResponsePaths: ["/v1/agent/chat/steer"],
      noActiveReason: "send-no-turn-composer",
      throwOnActionMismatch: true,
      expectedActionWaitMs: command.expectedActionWaitMs,
    }), "sendPrompt");
    case "validateRealtimeFanout": return validateRealtimeFanout(command);
    case "validateExistingSessionRefresh": return validateExistingSessionRefresh(command);
    case "validateWorkbenchKafkaDebugReplay": return withObserverSync(await validateWorkbenchKafkaDebugReplay(command), "validateWorkbenchKafkaDebugReplay");
    case "validateWorkbenchTraceReadability": return withObserverSync(await validateWorkbenchTraceReadability(command), "validateWorkbenchTraceReadability");
    case "steer": return withObserverSync(await sendPrompt(String(command.text || ""), { expectedAction: "steer", responsePath: "/v1/agent/chat/steer", noActiveReason: "steer-no-active-turn", expectedActionWaitMs: command.expectedActionWaitMs }), "steer");
    case "cancel": return withObserverSync(await cancelRunningTurn(), "cancel");
    case "selectProvider": return withObserverSync(await selectProvider(String(command.provider || command.value || command.text || "")), "selectProvider");
    case "clickSession": return withObserverSync(await clickSession(String(command.sessionId || command.value || "")), "clickSession");
    case "refreshCurrentSession": return withObserverSync(await refreshCurrentSession(command), "refreshCurrentSession");
    case "switchAwayAndBack": return withObserverSync(await switchAwayAndBack(command), "switchAwayAndBack");
    case "assertSessionInvariant": return withObserverSync(await assertSessionInvariant(command), "assertSessionInvariant");
    case "gotoProjectMdtodo": return withObserverSync(await gotoProjectMdtodo(), "gotoProjectMdtodo");
    case "openMdtodoSourceConfig": return openMdtodoSourceConfig(command);
    case "closeMdtodoSourceConfig": return closeMdtodoSourceConfig(command);
    case "configureMdtodoHwpodSource": return configureMdtodoHwpodSource(command);
    case "probeMdtodoSource": return probeMdtodoSource(command);
    case "reindexMdtodoSource": return reindexMdtodoSource(command);
    case "selectProjectSource": return selectProjectSource(command);
    case "selectMdtodoSource": return selectMdtodoSource(command);
    case "selectMdtodoFile": return selectMdtodoFile(command);
    case "selectMdtodoTask": return selectMdtodoTask(command);
    case "expandMdtodoTask": return expandMdtodoTask(command);
    case "openMdtodoReportPreview": return openMdtodoReportPreview(command);
    case "toggleMdtodoReportFullscreen": return toggleMdtodoReportFullscreen(command);
    case "editMdtodoTaskInline": return editMdtodoTaskInline(command);
    case "editMdtodoTaskTitle": return editMdtodoTaskTitle(command);
    case "editMdtodoTaskBody": return editMdtodoTaskBody(command);
    case "toggleMdtodoTaskStatus": return toggleMdtodoTaskStatus(command);
    case "addMdtodoRootTask": return addMdtodoRootTask(command);
    case "addMdtodoSubTask": return addMdtodoSubTask(command);
    case "continueMdtodoTask": return continueMdtodoTask(command);
    case "deleteMdtodoTask": return deleteMdtodoTask(command);
    case "launchWorkbenchFromTask": return withObserverSync(await launchWorkbenchFromTask(command), "launchWorkbenchFromTask");
    case "launchWorkbenchFromMdtodo": return withObserverSync(await launchWorkbenchFromMdtodo(command), "launchWorkbenchFromMdtodo");
    case "performanceCapture": return capturePerformanceProfile(command);
    case "screenshot": return captureCommandScreenshot(command);
    case "mark": return { mark: truncate(command.label || command.text || "mark", 200), currentUrl: currentPageUrl(), pageId };
    case "stop": stopping = true; return { stopping: true, currentUrl: currentPageUrl(), pageId };
    default: throw new Error("unsupported observer command type: " + command.type);
  }
}

async function withObserverSync(result, reason) {
  return { ...result, observer: await syncObserverPageToControlSession(reason, result?.sessionId ?? null) };
}

async function syncObserverPageToControlSession(reason, explicitSessionId = null, options = {}) {
  if (!observerPage || observerPage.isClosed()) return { ok: false, reason, pageRole: "observer", pageId: observerPageId, failureKind: "observer-page-unavailable" };
  const forceRefresh = options?.forceRefresh === true;
  const navigationTimeoutMs = Number.isFinite(Number(options.navigationTimeoutMs)) ? Math.max(1, Number(options.navigationTimeoutMs)) : 45000;
  const readinessTimeoutMs = Number.isFinite(Number(options.readinessTimeoutMs)) ? Math.max(1, Number(options.readinessTimeoutMs)) : 15000;
  const hydrationTimeoutMs = Number.isFinite(Number(options.hydrationTimeoutMs)) ? Math.max(1, Number(options.hydrationTimeoutMs)) : 15000;
  const shortCircuitReadinessTimeoutMs = Number.isFinite(Number(options.shortCircuitReadinessTimeoutMs)) ? Math.max(1, Number(options.shortCircuitReadinessTimeoutMs)) : 1000;
  const shortCircuitHydrationTimeoutMs = Number.isFinite(Number(options.shortCircuitHydrationTimeoutMs)) ? Math.max(1, Number(options.shortCircuitHydrationTimeoutMs)) : 1000;
  const snapshot = await workbenchSessionSnapshot();
  const sessionId = explicitSessionId || snapshot?.activeSessionId || snapshot?.routeSessionId || routeSessionIdFromUrl(currentPageUrl());
  const target = sessionId ? "/workbench/sessions/" + encodeURIComponent(sessionId) : targetPath;
  const targetUrl = new URL(target, baseUrl).toString();
  const beforeUrl = pageUrl(observerPage);
  const beforeSessionId = routeSessionIdFromUrl(beforeUrl);
  const attempts = [];
  if (sessionId && beforeSessionId === sessionId && !forceRefresh) {
    const current = await observerSessionReadiness(targetUrl, sessionId, { readinessTimeoutMs: shortCircuitReadinessTimeoutMs, hydrationTimeoutMs: shortCircuitHydrationTimeoutMs });
    if (current.ok) return { ok: true, reason, changed: false, observerRoundTrip: false, sessionId, beforeUrl, afterUrl: beforeUrl, pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, readiness: current.readiness, hydration: current.hydration, valuesRedacted: true };
    attempts.push({ attempt: 0, ok: false, shortCircuitRejected: true, failureKind: current.failureKind, readiness: current.readiness, hydration: current.hydration, beforeUrl, afterUrl: pageUrl(observerPage), valuesRedacted: true });
  }
  const maxAttempts = Number.isFinite(Number(options?.maxAttempts)) ? Math.max(1, Number(options.maxAttempts)) : 2;
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const attemptBeforeUrl = pageUrl(observerPage);
    observerPageEpoch += 1;
    let status = null;
    let statusText = null;
    const response = await observerPage.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: navigationTimeoutMs }).catch((error) => ({ observerGotoError: errorSummary(error) }));
    if (response?.observerGotoError) {
      attempts.push({ attempt, ok: false, failureKind: navigationFailureKind(response.observerGotoError?.message || response.observerGotoError?.name || "observer-navigation-error"), beforeUrl: attemptBeforeUrl, afterUrl: pageUrl(observerPage), error: response.observerGotoError, valuesRedacted: true });
      if (attempt < maxAttempts && isRetryableNavigationError(response.observerGotoError?.message || response.observerGotoError?.name || "")) {
        await recreateObserverPageForNavigation("observer-goto-error", attempt).catch((resetError) => appendJsonl(files.errors, eventRecord("observer-page-reset-error", { commandId: activeCommandId, attempt, error: errorSummary(resetError) })));
        continue;
      }
      lastObserverRefreshAtMs = Date.now();
      return { ok: false, reason, changed: false, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, error: response.observerGotoError, attempts, valuesRedacted: true };
    }
    status = typeof response?.status === "function" ? response.status() : null;
    statusText = typeof response?.statusText === "function" ? response.statusText() : null;
    const readiness = await waitForTargetPageReady(observerPage, targetUrl, { timeoutMs: readinessTimeoutMs });
    if (!readiness.ok) {
      attempts.push({ attempt, ok: false, failureKind: readiness.reason || "observer-target-not-ready", beforeUrl: attemptBeforeUrl, afterUrl: pageUrl(observerPage), httpStatus: status, statusText, readiness, valuesRedacted: true });
      if (attempt < maxAttempts && observerReadinessRetryable(readiness)) {
        await recreateObserverPageForNavigation(readiness.reason || "observer-target-not-ready", attempt).catch((resetError) => appendJsonl(files.errors, eventRecord("observer-page-reset-error", { commandId: activeCommandId, attempt, error: errorSummary(resetError) })));
        continue;
      }
      lastObserverRefreshAtMs = Date.now();
      return { ok: false, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, httpStatus: status, statusText, readiness, hydration: null, attempts, failureKind: readiness.reason || "observer-target-not-ready", valuesRedacted: true };
    }
    if (!isWorkbenchPathname(safeUrlPath(targetUrl) || "")) {
      lastObserverRefreshAtMs = Date.now();
      attempts.push({ attempt, ok: true, beforeUrl: attemptBeforeUrl, afterUrl: pageUrl(observerPage), httpStatus: status, statusText, readiness, valuesRedacted: true });
      return { ok: true, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, httpStatus: status, statusText, readiness, hydration: null, attempts, valuesRedacted: true };
    }
    const hydration = await waitForWorkbenchSessionHydrated(observerPage, sessionId, { timeoutMs: hydrationTimeoutMs });
    attempts.push({ attempt, ok: hydration.ok === true, failureKind: hydration.ok === true ? null : hydration.reason || "observer-session-hydration-failed", beforeUrl: attemptBeforeUrl, afterUrl: pageUrl(observerPage), httpStatus: status, statusText, readiness, hydration, valuesRedacted: true });
    if (hydration.ok === true) {
      lastObserverRefreshAtMs = Date.now();
      return { ok: true, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, httpStatus: status, statusText, readiness, hydration, attempts, failureKind: null, valuesRedacted: true };
    }
    if (attempt < maxAttempts && observerHydrationRetryable(hydration)) {
      await recreateObserverPageForNavigation(hydration.reason || "observer-session-hydration-failed", attempt).catch((resetError) => appendJsonl(files.errors, eventRecord("observer-page-reset-error", { commandId: activeCommandId, attempt, error: errorSummary(resetError) })));
      continue;
    }
    lastObserverRefreshAtMs = Date.now();
    return { ok: false, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, httpStatus: status, statusText, readiness, hydration, attempts, failureKind: hydration.reason || "observer-session-hydration-failed", valuesRedacted: true };
  }
  lastObserverRefreshAtMs = Date.now();
  return { ok: false, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, attempts, failureKind: "observer-sync-retry-exhausted", valuesRedacted: true };
}

async function observerSessionReadiness(targetUrl, sessionId, options = {}) {
  const readiness = await waitForTargetPageReady(observerPage, targetUrl, { timeoutMs: options.readinessTimeoutMs ?? 1000 });
  if (!readiness.ok) return { ok: false, failureKind: readiness.reason || "observer-target-not-ready", readiness, hydration: null, valuesRedacted: true };
  if (!isWorkbenchPathname(safeUrlPath(targetUrl) || "")) return { ok: true, readiness, hydration: null, valuesRedacted: true };
  const hydration = await waitForWorkbenchSessionHydrated(observerPage, sessionId, { timeoutMs: options.hydrationTimeoutMs ?? 1000 });
  return { ok: hydration.ok === true, failureKind: hydration.ok === true ? null : hydration.reason || "observer-session-hydration-failed", readiness, hydration, valuesRedacted: true };
}

function observerReadinessRetryable(readiness) {
  const reason = String(readiness?.reason || "");
  const snapshot = readiness?.snapshot || {};
  return /workbench-app-not-ready|observer-target-not-ready/iu.test(reason)
    || snapshot.workbenchShellVisible === false
    || snapshot.sessionRailPresent === false && snapshot.commandInputPresent === false;
}

function observerHydrationRetryable(hydration) {
  const reason = String(hydration?.reason || "");
  return /observer-session-hydration-timeout|observer-session-hydration-failed|active-session-not-hydrated|route-session-not-hydrated/iu.test(reason);
}

async function recreateObserverPageForNavigation(reason, attempt) {
  const before = pageUrl(observerPage);
  if (observerPage && !observerPage.isClosed()) await observerPage.close().catch(() => {});
  observerPage = await context.newPage();
  attachPassiveListeners(observerPage, "observer", observerPageId);
  await appendJsonl(files.control, eventRecord("observer-page-recreated", { reason, attempt, beforeUrl: before, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, pageEpoch: observerPageEpoch, valuesRedacted: true }));
}

async function waitForWorkbenchSessionHydrated(targetPage, sessionId, options = {}) {
  const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Number(options.timeoutMs) : 15000;
  const started = Date.now();
  const deadline = started + Math.max(1, timeoutMs);
  let last = null;
  while (Date.now() <= deadline) {
    last = await workbenchSessionSnapshot(targetPage);
    const expected = String(sessionId || "").trim();
    const observedPath = safeUrlPath(last?.url || pageUrl(targetPage));
    const routeOk = expected ? last?.routeSessionId === expected : isWorkbenchPathname(observedPath || "");
    const activeOk = expected ? last?.activeSessionId === expected : isWorkbenchPathname(observedPath || "");
    if (routeOk && activeOk) return { ok: true, durationMs: Date.now() - started, snapshot: last, valuesRedacted: true };
    await targetPage.waitForTimeout(250).catch(() => {});
  }
  return { ok: false, durationMs: Date.now() - started, snapshot: last, reason: "observer-session-hydration-timeout", expectedSessionId: sessionId || null, valuesRedacted: true };
}

async function maybeRefreshObserverPage(reason) {
  if (!observerPage || observerPage.isClosed()) return null;
  if (!observerRefreshIntervalMs || observerRefreshIntervalMs <= 0) return null;
  if (Date.now() - lastObserverRefreshAtMs < observerRefreshIntervalMs) return null;
  const result = await syncObserverPageToControlSession("observer-periodic-refresh", null, { forceRefresh: true });
  await appendJsonl(files.control, eventRecord("observer-periodic-refresh", {
    pageRole: "observer",
    pageId: observerPageId,
    reason,
    intervalMs: observerRefreshIntervalMs,
    result,
    valuesRedacted: true
  }));
  return result;
}

async function runControlCommand(command, fn) {
  activeCommandId = command.id;
  commandSeq += 1;
  const beforeUrl = currentPageUrl();
  const started = Date.now();
  await appendJsonl(files.control, controlRecord(command, "started", { beforeUrl, input: commandInputSummary(command) }));
  try {
    const result = await fn();
    await appendJsonl(files.control, controlRecord(command, "completed", { beforeUrl, afterUrl: currentPageUrl(), durationMs: Date.now() - started, result: sanitize(result) }));
    return result;
  } catch (error) {
    let failurePageProvenance = null;
    let failureScreenshot = null;
    try {
      failurePageProvenance = compactPageProvenance(await refreshPageProvenance("command-failed", null));
    } catch (captureError) {
      await appendJsonl(files.errors, eventRecord("failure-provenance-error", { commandId: command.id, pageRole: "control", pageId, error: errorSummary(captureError) }));
    }
    try {
      failureScreenshot = await captureScreenshot("command-failed", "jpeg");
    } catch (captureError) {
      await appendJsonl(files.errors, eventRecord("failure-screenshot-error", { commandId: command.id, pageRole: "control", pageId, error: errorSummary(captureError) }));
    }
    await appendJsonl(files.control, controlRecord(command, "failed", { beforeUrl, afterUrl: currentPageUrl(), durationMs: Date.now() - started, error: errorSummary(error), failurePageProvenance, failureScreenshot }));
    throw error;
  } finally {
    activeCommandId = null;
  }
}

async function authenticate(browserContext) {
  const loginUrl = new URL("/auth/login", baseUrl).toString();
  const attempts = [];
  const maxAttempts = authLoginMaxAttempts;
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const retryDelayMs = authRetryDelayMs(attempt, maxAttempts);
    const retryLabel = attempt + "/" + maxAttempts;
    await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", retryAttempt: attempt, retryMaxAttempts: maxAttempts, lastRetryLabel: retryLabel, retryDelayMs: 0, retryExhausted: false, valuesRedacted: true } }).catch(() => {});
    try {
      const response = await pageAuthLogin(browserContext, loginUrl);
      const cookieState = await readAuthCookieState(browserContext);
      const retryable = isRetryableAuthStatus(response.status);
      const item = {
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel,
        retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
        requestTimeoutMs: authLoginRequestTimeoutMs,
        method: "api",
        status: response.status,
        statusText: response.statusText,
        retryable,
        cookiePresent: cookieState.cookiePresent,
        cookieNames: cookieState.cookieNames,
        valuesRedacted: true,
      };
      attempts.push(item);
      await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: item.retryLabel, retryAttempt: item.retryAttempt, retryMaxAttempts: item.retryMaxAttempts, retryDelayMs: item.retryDelayMs, lastStatus: item.status, lastStatusText: item.statusText, retryable: item.retryable, cookiePresent: item.cookiePresent, retryExhausted: false, valuesRedacted: true } }).catch(() => {});
      if (response.ok && cookieState.cookiePresent) {
        return {
          ok: true,
          method: "api",
          loginPath: new URL(loginUrl).pathname,
          status: response.status,
          statusText: response.statusText,
          cookiePresent: true,
          cookieNames: cookieState.cookieNames,
          attempts,
          retryCount: attempt - 1,
          retryMaxAttempts: maxAttempts,
          lastRetryLabel: attempt + "/" + maxAttempts,
          retryExhausted: false,
          retryable: false,
          valuesRedacted: true,
        };
      }
      if (!retryable) break;
    } catch (error) {
      const retryable = isRetryableAuthError(error);
      attempts.push({
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel,
        retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
        requestTimeoutMs: authLoginRequestTimeoutMs,
        method: "api",
        status: 0,
        statusText: "request-error",
        retryable,
        error: error && error.message ? truncate(error.message, 500) : truncate(String(error), 500),
        cookiePresent: false,
        cookieNames: [],
        valuesRedacted: true,
      });
      const item = attempts[attempts.length - 1] || null;
      await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: item?.retryLabel || retryLabel, retryAttempt: attempt, retryMaxAttempts: maxAttempts, retryDelayMs: item?.retryDelayMs ?? 0, lastStatus: item?.status ?? 0, lastStatusText: item?.statusText ?? "request-error", retryable, cookiePresent: false, retryExhausted: false, lastError: item?.error || null, valuesRedacted: true } }).catch(() => {});
      if (!retryable) break;
    }
    if (attempt < maxAttempts && attempts[attempts.length - 1]?.retryable === true) await interruptibleRunnerSleep(retryDelayMs);
  }
  const cookieState = await readAuthCookieState(browserContext);
  const last = attempts[attempts.length - 1] || null;
  const retryable = attempts.some((attempt) => attempt && attempt.retryable === true);
  const failure = {
    ok: false,
    method: "api",
    loginPath: new URL(loginUrl).pathname,
    status: typeof last?.status === "number" ? last.status : 0,
    statusText: typeof last?.statusText === "string" ? last.statusText : "api-login-failed",
    cookiePresent: cookieState.cookiePresent,
    cookieNames: cookieState.cookieNames,
    attempts,
    retryCount: Math.max(0, attempts.length - 1),
    retryMaxAttempts: maxAttempts,
    lastRetryLabel: last?.retryLabel || null,
    retryExhausted: retryable && attempts.length >= maxAttempts,
    retryable,
    lastError: last?.error || null,
    valuesRedacted: true,
  };
  await writeHeartbeat({ status: terminalStatus, auth: { phase: "api-login", lastRetryLabel: failure.lastRetryLabel, retryAttempt: attempts.length, retryMaxAttempts: maxAttempts, retryDelayMs: 0, lastStatus: failure.status, lastStatusText: failure.statusText, retryable: failure.retryable, cookiePresent: failure.cookiePresent, retryExhausted: failure.retryExhausted, lastError: failure.lastError, valuesRedacted: true } }).catch(() => {});
  const error = new Error(authFailureMessage(failure));
  error.webProbeAuth = failure;
  throw error;
}

async function pageAuthLogin(browserContext, loginUrl, credential = { username, password }) {
  if (!browserContext?.request) throw new Error("auth browser context request is not ready");
  const response = await browserContext.request.post(loginUrl, {
    data: { username: credential.username, password: credential.password },
    headers: { accept: "application/json", "content-type": "application/json" },
    timeout: authLoginRequestTimeoutMs,
  });
  const result = {
    ok: response.ok(),
    status: response.status(),
    statusText: response.statusText() || "",
  };
  if (typeof response.dispose === "function") await response.dispose().catch(() => {});
  return result;
}

async function loginAccount(command) {
  const accountId = requiredAccountId(command, ["accountId", "account", "value", "text"]);
  const credential = credentialForAccount(accountId);
  const loginUrl = new URL("/auth/login", baseUrl).toString();
  const before = await accountSessionSnapshot();
  const attempts = [];
  let response = null;
  let cookieState = null;
  const maxAttempts = authLoginMaxAttempts;
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const retryDelayMs = authRetryDelayMs(attempt, maxAttempts);
    const retryLabel = attempt + "/" + maxAttempts;
    try {
      response = await pageAuthLogin(context, loginUrl, credential);
      cookieState = await readAuthCookieState(context);
      const retryable = isRetryableAuthStatus(response.status);
      attempts.push({
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel,
        retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
        requestTimeoutMs: authLoginRequestTimeoutMs,
        method: "api",
        status: response.status,
        statusText: response.statusText,
        retryable,
        cookiePresent: cookieState.cookiePresent,
        cookieNames: cookieState.cookieNames,
        credentialSource: credential.source,
        valuesRedacted: true,
      });
      if (response.ok && cookieState.cookiePresent) break;
      if (!retryable) break;
    } catch (error) {
      const retryable = isRetryableAuthError(error);
      attempts.push({
        attempt,
        retryAttempt: attempt,
        retryMaxAttempts: maxAttempts,
        retryLabel,
        retryDelayMs: retryable && attempt < maxAttempts ? retryDelayMs : 0,
        requestTimeoutMs: authLoginRequestTimeoutMs,
        method: "api",
        status: 0,
        statusText: "request-error",
        retryable,
        error: error && error.message ? truncate(error.message, 500) : truncate(String(error), 500),
        cookiePresent: false,
        cookieNames: [],
        credentialSource: credential.source,
        valuesRedacted: true,
      });
      response = { ok: false, status: 0, statusText: "request-error" };
      cookieState = await readAuthCookieState(context).catch(() => ({ cookiePresent: false, cookieNames: [] }));
      if (!retryable) break;
    }
    if (attempt < maxAttempts && attempts[attempts.length - 1]?.retryable === true) await interruptibleRunnerSleep(retryDelayMs);
  }
  response = response ?? { ok: false, status: 0, statusText: "api-login-failed" };
  cookieState = cookieState ?? await readAuthCookieState(context);
  if (!response.ok || !cookieState.cookiePresent) {
    const error = new Error("loginAccount failed for accountId=" + accountId + " status=" + response.status + " " + (response.statusText || ""));
    const retryable = attempts.some((item) => item && item.retryable === true);
    error.details = {
      accountId,
      status: response.status,
      statusText: response.statusText,
      cookiePresent: cookieState.cookiePresent,
      credentialSource: credential.source,
      attempts,
      retryCount: Math.max(0, attempts.length - 1),
      retryMaxAttempts: maxAttempts,
      lastRetryLabel: attempts[attempts.length - 1]?.retryLabel || null,
      retryExhausted: retryable && attempts.length >= maxAttempts,
      retryable,
      valuesRedacted: true,
    };
    throw error;
  }
  const target = isWorkbenchPathname(safeUrlPath(currentPageUrl()) || "") ? safeUrlPath(currentPageUrl()) : targetPath;
  const navigation = await gotoTarget(target || targetPath);
  const after = await accountSessionSnapshot();
  return { ok: true, type: "loginAccount", accountId, credentialSource: credential.source, before, after, navigation, cookiePresent: cookieState.cookiePresent, cookieNames: cookieState.cookieNames, valuesRedacted: true };
}

async function logoutAccount(command = {}) {
  const accountId = commandValue(command, ["accountId", "account", "value", "text"]) || null;
  const before = await accountSessionSnapshot();
  const logoutUrl = new URL("/logout", baseUrl).toString();
  const response = await page.evaluate(async (input) => {
    const res = await fetch(input.logoutUrl, { method: "POST", headers: { accept: "application/json" }, credentials: "include" });
    return { ok: res.ok, status: res.status, statusText: res.statusText || "" };
  }, { logoutUrl });
  await context.clearCookies().catch(() => {});
  const cookieState = await readAuthCookieState(context);
  const afterUrl = await page.goto(new URL("/auth/login", baseUrl).toString(), { waitUntil: "domcontentloaded", timeout: 15000 }).then(() => currentPageUrl()).catch(() => currentPageUrl());
  const result = { ok: response.ok || response.status === 401 || !cookieState.cookiePresent, type: "logout", accountId, status: response.status, statusText: response.statusText, before, after: { url: afterUrl, cookiePresent: cookieState.cookiePresent, cookieNames: cookieState.cookieNames, valuesRedacted: true }, valuesRedacted: true };
  if (!result.ok) {
    const error = new Error("logout failed status=" + response.status + " " + (response.statusText || ""));
    error.details = result;
    throw error;
  }
  return result;
}

async function listSessions(command = {}) {
  const accountId = commandValue(command, ["accountId", "account", "value", "text"]) || null;
  if (!isWorkbenchPathname(safeUrlPath(currentPageUrl()) || "")) await gotoTarget(targetPath);
  const snapshot = await workbenchSessionSnapshot();
  const sessions = await page.evaluate(() => {
    const seen = new Set();
    const rows = [];
    for (const element of Array.from(document.querySelectorAll("[data-session-id], .session-tab, a[href*='/workbench/sessions/']"))) {
      const sessionId = element.getAttribute("data-session-id") || (element.getAttribute("href") || "").match(/\/workbench\/sessions\/([^/?#]+)/)?.[1] || "";
      if (!sessionId || seen.has(sessionId)) continue;
      seen.add(sessionId);
      rows.push({
        sessionId,
        active: element.getAttribute("data-active") === "true" || element.getAttribute("aria-selected") === "true",
        status: element.getAttribute("data-status") || null,
        conversationId: element.getAttribute("data-conversation-id") || null,
      });
    }
    return rows.slice(0, 50);
  }).catch(() => []);
  return { ok: true, type: "listSessions", accountId, sessionCount: sessions.length, activeSessionId: snapshot?.activeSessionId || snapshot?.routeSessionId || null, sessions, snapshot, valuesRedacted: true };
}

async function switchSessions(command) {
  const fromAccountId = commandValue(command, ["fromAccountId", "fromAccount", "accountId"]);
  const toAccountId = requiredAccountId(command, ["toAccountId", "toAccount", "value", "text"]);
  const before = await accountSessionSnapshot();
  if (fromAccountId) {
    const beforeAccount = before.accountId || null;
    await appendJsonl(files.control, eventRecord("switchSessions-from-account", { fromAccountId, observedAccountId: beforeAccount, valuesRedacted: true }));
  }
  const logout = await logoutAccount({ ...command, accountId: fromAccountId || null });
  const login = await loginAccount({ ...command, accountId: toAccountId });
  const sessions = await listSessions({ ...command, accountId: toAccountId });
  return { ok: login.ok === true && sessions.ok === true, type: "switchSessions", fromAccountId: fromAccountId || null, toAccountId, before, logout, login, sessions, valuesRedacted: true };
}

async function accountSessionSnapshot() {
  const cookieState = await readAuthCookieState(context);
  const workbench = await workbenchSessionSnapshot().catch(() => null);
  return {
    url: currentPageUrl(),
    path: safeUrlPath(currentPageUrl()),
    cookiePresent: cookieState.cookiePresent,
    cookieNames: cookieState.cookieNames,
    activeSessionId: workbench?.activeSessionId || null,
    routeSessionId: workbench?.routeSessionId || null,
    tabCount: workbench?.tabCount ?? null,
    messageCount: workbench?.messageCount ?? null,
    valuesRedacted: true,
  };
}

function requiredAccountId(command, keys) {
  const accountId = commandValue(command, keys);
  if (!isSafeAccountId(accountId)) throw new Error(command.type + " requires --account-id using lowercase account id");
  return accountId;
}

function credentialForAccount(accountId) {
  if (accountId === "bootstrap-admin" || accountId === "admin") {
    if (!password) throw new Error("loginAccount accountId=" + accountId + " missing HWLAB_WEB_PASS");
    return { username, password, source: "HWLAB_WEB_USER/HWLAB_WEB_PASS", valuesRedacted: true };
  }
  const env = accountCredentialEnvCandidates(accountId);
  for (const jsonKey of env.jsonKeys) {
    const raw = process.env[jsonKey];
    if (!raw) continue;
    const parsed = parseCredentialJson(raw);
    if (parsed !== null) return { ...parsed, source: jsonKey, valuesRedacted: true };
  }
  for (const pair of env.pairs) {
    const user = process.env[pair.userKey];
    const pass = process.env[pair.passKey];
    if (user && pass) return { username: user, password: pass, source: pair.userKey + "/" + pair.passKey, valuesRedacted: true };
  }
  throw new Error("loginAccount missing credential material for accountId=" + accountId + "; expected one of " + [...env.jsonKeys, ...env.pairs.flatMap((item) => [item.userKey, item.passKey])].join(","));
}

function accountCredentialEnvCandidates(accountId) {
  const segment = accountId.toUpperCase().replace(/[^A-Z0-9]+/gu, "_").replace(/^_+|_+$/gu, "");
  return {
    jsonKeys: [
      "HWLAB_WEB_" + segment + "_JSON",
      "HWLAB_WEB_ACCOUNT_" + segment + "_JSON",
    ],
    pairs: [
      { userKey: "HWLAB_WEB_" + segment + "_USER", passKey: "HWLAB_WEB_" + segment + "_PASS" },
      { userKey: "HWLAB_WEB_ACCOUNT_" + segment + "_USER", passKey: "HWLAB_WEB_ACCOUNT_" + segment + "_PASS" },
    ],
  };
}

function parseCredentialJson(raw) {
  try {
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
    const user = typeof parsed.username === "string" ? parsed.username : typeof parsed.user === "string" ? parsed.user : typeof parsed.email === "string" ? parsed.email : "";
    const pass = typeof parsed.password === "string" ? parsed.password : typeof parsed.pass === "string" ? parsed.pass : "";
    if (!user || !pass) return null;
    return { username: user, password: pass, valuesRedacted: true };
  } catch {
    return null;
  }
}

function isSafeAccountId(value) {
  return /^[a-z0-9][a-z0-9-]{1,80}$/u.test(String(value || ""));
}

function publicAuth(value) {
  if (!value) return null;
  return {
    ok: value.ok === true,
    method: value.method,
    status: value.status,
    cookiePresent: value.cookiePresent === true,
    cookieNames: value.cookieNames || [],
    retryCount: value.retryCount ?? null,
    retryMaxAttempts: value.retryMaxAttempts ?? null,
    lastRetryLabel: value.lastRetryLabel ?? null,
    retryExhausted: value.retryExhausted === true,
    valuesRedacted: true
  };
}

async function readAuthCookieState(browserContext) {
  const cookies = await browserContext.cookies(baseUrl);
  const cookieNames = cookies.map((cookie) => cookie.name).sort();
  return {
    cookiePresent: cookieNames.includes("hwlab_session") || cookieNames.some((name) => /session|auth|token/iu.test(name)),
    cookieNames: cookieNames.filter((name) => /session|auth|token/iu.test(name)),
  };
}

function isRetryableAuthStatus(status) {
  return status === 0 || status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
}

function isRetryableAuthError(error) {
  const message = error && error.message ? String(error.message) : String(error || "");
  return /AbortError|EAI_AGAIN|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ECONNABORTED|socket hang up|ERR_NETWORK_CHANGED|fetch failed|failed to fetch|network|timeout|aborted/iu.test(message);
}

function authRetryDelayMs(attempt, maxAttempts) {
  return attempt < maxAttempts ? Math.min(authLoginMaxDelayMs, authLoginInitialDelayMs * (2 ** (attempt - 1))) : 0;
}

function authFailureMessage(failure) {
  const last = Array.isArray(failure.attempts) && failure.attempts.length > 0 ? failure.attempts[failure.attempts.length - 1] : null;
  const retry = failure.lastRetryLabel ? " retry=" + failure.lastRetryLabel : "";
  const exhausted = failure.retryExhausted ? " exhausted=true" : "";
  const status = last ? " status=" + (last.status ?? "-") + " " + (last.statusText ?? "") : "";
  const error = last?.error ? " error=" + truncate(last.error, 160) : "";
  return ("auth login failed:" + retry + exhausted + status + error).trim();
}

function proxyConfigFromEnv(targetBaseUrl) {
  if (browserProxyMode === "direct") return null;
  let target;
  try {
    target = new URL(targetBaseUrl);
  } catch {
    return null;
  }
  const noProxy = process.env.NO_PROXY || process.env.no_proxy || "";
  if (noProxyMatches(target.hostname, noProxy)) return null;
  const raw = target.protocol === "https:"
    ? process.env.HTTPS_PROXY || process.env.https_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTP_PROXY || process.env.http_proxy || ""
    : process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy || process.env.HTTPS_PROXY || process.env.https_proxy || "";
  if (!raw) return null;
  return { server: raw };
}

function chromiumLaunchOptionsForProxy(proxy) {
  const baseArgs = chromiumLowResourceArgs();
  const base = { env: browserProcessEnvWithoutProxy() };
  if (proxy === null) return { ...base, args: [...baseArgs, "--no-proxy-server"] };
  return { ...base, proxy, args: baseArgs };
}

function chromiumLowResourceArgs() {
  return [];
}

function browserProcessEnvWithoutProxy() {
  const blocked = new Set(["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "all_proxy", "no_proxy"]);
  const env = {};
  for (const [key, value] of Object.entries(process.env)) {
    if (!blocked.has(key) && value !== undefined) env[key] = value;
  }
  return env;
}

function noProxyMatches(hostname, rawList) {
  const host = String(hostname || "").toLowerCase();
  if (!host) return false;
  return String(rawList || "").split(",").some((raw) => {
    let item = raw.trim().toLowerCase();
    if (!item) return false;
    if (item === "*") return true;
    item = item.replace(/^\*\./u, ".");
    const portIndex = item.lastIndexOf(":");
    if (portIndex > -1 && !item.includes("]")) item = item.slice(0, portIndex);
    if (item.startsWith(".")) return host === item.slice(1) || host.endsWith(item);
    return host === item;
  });
}

function publicNetwork(proxy) {
  return {
    proxy: proxy === null ? { enabled: false, source: "env", valuesRedacted: true } : {
      enabled: true,
      source: "env",
      server: publicProxyServer(proxy.server),
      valuesRedacted: true,
    },
    browser: {
      proxyMode: proxy === null ? "direct-no-proxy-server" : "explicit-playwright-proxy",
      requestedProxyMode: browserProxyMode,
      proxyEnvCleared: true,
      valuesRedacted: true,
    },
    valuesRedacted: true,
  };
}

function parseBrowserProxyMode(raw) {
  if (raw === "auto" || raw === "direct") return raw;
  return "auto";
}

function publicProxyServer(raw) {
  try {
    const parsed = new URL(String(raw || ""));
    parsed.username = "";
    parsed.password = "";
    const value = parsed.toString();
    if (parsed.pathname === "/" && parsed.search === "" && parsed.hash === "") return value.replace(/\/$/u, "");
    return value;
  } catch {
    return String(raw || "").replace(/\/\/[^/@]+@/u, "//[redacted]@");
  }
}

async function gotoTarget(rawTarget, options = {}) {
  const target = new URL(String(rawTarget || targetPath), baseUrl).toString();
  const beforeUrl = currentPageUrl();
  const attempts = [];
  const maxAttempts = Number.isFinite(Number(options.maxAttempts)) ? Math.max(1, Number(options.maxAttempts)) : navigationMaxAttempts;
  const navigationTimeoutMs = Number.isFinite(Number(options.navigationTimeoutMs)) ? Math.max(1, Number(options.navigationTimeoutMs)) : 45000;
  const readinessTimeoutMs = Number.isFinite(Number(options.readinessTimeoutMs)) ? Math.max(1, Number(options.readinessTimeoutMs)) : 15000;
  const settleMs = Number.isFinite(Number(options.settleMs)) ? Math.max(0, Number(options.settleMs)) : 1000;
  const lateReadinessTimeoutMs = Number.isFinite(Number(options.lateReadinessTimeoutMs)) ? Math.max(0, Number(options.lateReadinessTimeoutMs)) : 5000;
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    try {
      const response = await page.goto(target, { waitUntil: "domcontentloaded", timeout: navigationTimeoutMs });
      if (settleMs > 0) await page.waitForTimeout(settleMs).catch(() => {});
      const httpStatus = response ? response.status() : null;
      const readiness = await waitForTargetPageReady(page, target, { timeoutMs: readinessTimeoutMs });
      if (!readiness.ok) {
        const pageProvenance = await refreshPageProvenance("goto-degraded", httpStatus).catch(() => null);
        attempts.push({ attempt, ok: false, degraded: true, httpStatus, readiness, failureKind: readiness.reason || "workbench-app-not-ready" });
        if (!readinessRetryable(readiness)) {
          return { beforeUrl, afterUrl: currentPageUrl(), httpStatus, pageId, degraded: true, degradedReason: readiness.reason || "workbench-app-not-ready", pageProvenance: compactPageProvenance(pageProvenance), readiness, attempts };
        }
        if (attempt >= maxAttempts) {
          return { beforeUrl, afterUrl: currentPageUrl(), httpStatus, pageId, degraded: true, degradedReason: readiness.reason || "workbench-app-not-ready", pageProvenance: compactPageProvenance(pageProvenance), readiness, attempts };
        }
        await recreateControlPageForNavigation("retryable-readiness-" + (readiness.reason || "target-not-ready"), attempt).catch((resetError) => appendJsonl(files.errors, eventRecord("navigation-page-reset-error", { commandId: activeCommandId, attempt, error: errorSummary(resetError) })));
        await page.waitForTimeout(1500 * attempt).catch(() => {});
        continue;
      }
      const pageProvenance = await refreshPageProvenance("goto", httpStatus);
      attempts.push({ attempt, ok: true, httpStatus, readiness });
      return { beforeUrl, afterUrl: currentPageUrl(), httpStatus, pageId, pageProvenance: compactPageProvenance(pageProvenance), readiness, attempts };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      attempts.push({ attempt, ok: false, failureKind: navigationFailureKind(message), message: redactErrorMessage(message), readiness: error?.navigationReadiness ?? null });
      if (lateReadinessTimeoutMs > 0 && /workbench-app-not-ready|navigation timeout|page\.goto:\s*timeout|timeout\s+\d+ms\s+exceeded/iu.test(message)) {
        const lateReadiness = await waitForTargetPageReady(page, target, { timeoutMs: lateReadinessTimeoutMs }).catch(() => null);
        if (lateReadiness?.ok) {
          const pageProvenance = await refreshPageProvenance("goto-late-ready", null);
          attempts.push({ attempt, ok: true, lateReady: true, httpStatus: null, readiness: lateReadiness });
          return { beforeUrl, afterUrl: currentPageUrl(), httpStatus: null, pageId, pageProvenance: compactPageProvenance(pageProvenance), readiness: lateReadiness, attempts };
        }
      }
      if (attempt >= maxAttempts || !isRetryableNavigationError(message)) {
        throw Object.assign(new Error(message), { attempts, target });
      }
      if (!observerPage) {
        await recreateAuthenticatedContextForNavigation("retryable-navigation-" + navigationFailureKind(message), attempt).catch((resetError) => appendJsonl(files.errors, eventRecord("navigation-context-reset-error", { commandId: activeCommandId, attempt, error: errorSummary(resetError) })));
      } else {
        await recreateControlPageForNavigation("retryable-navigation-" + navigationFailureKind(message), attempt).catch((resetError) => appendJsonl(files.errors, eventRecord("navigation-page-reset-error", { commandId: activeCommandId, attempt, error: errorSummary(resetError) })));
      }
      await page.waitForTimeout(1500 * attempt).catch(() => {});
    }
  }
  return { beforeUrl, afterUrl: currentPageUrl(), httpStatus: null, pageId, attempts };
}

async function recreateControlPageForNavigation(reason, attempt) {
  const before = currentPageUrl();
  if (page && !page.isClosed()) await withHardTimeout(page.close(), 3000, "control page close exceeded 3000ms").catch((error) => appendJsonl(files.errors, eventRecord("control-page-close-timeout", { reason, attempt, error: errorSummary(error), pageRole: "control", pageId, pageEpoch: controlPageEpoch })));
  controlPageEpoch += 1;
  page = await context.newPage();
  attachPassiveListeners(page, "control", pageId);
  currentPageProvenance = null;
  await appendJsonl(files.control, eventRecord("page-recreated", { reason, attempt, beforeUrl: before, afterUrl: currentPageUrl(), pageRole: "control", pageId, pageEpoch: controlPageEpoch, valuesRedacted: true }));
}

async function recreateAuthenticatedContextForNavigation(reason, attempt) {
  const before = currentPageUrl();
  if (page && !page.isClosed()) await page.close().catch(() => {});
  if (observerPage && !observerPage.isClosed()) await observerPage.close().catch(() => {});
  observerPage = null;
  if (context) await context.close().catch(() => {});
  context = await browser.newContext({ viewport, ...(playwrightProxy === null ? {} : { proxy: playwrightProxy }) });
  auth = await authenticate(context);
  page = await context.newPage();
  attachPassiveListeners(page, "control", pageId);
  currentPageProvenance = null;
  await appendJsonl(files.control, eventRecord("context-recreated", { reason, attempt, beforeUrl: before, afterUrl: currentPageUrl(), pageRole: "control", pageId, auth: publicAuth(auth), valuesRedacted: true }));
}

async function refreshPageProvenance(reason, httpStatus = null) {
  if (!page || page.isClosed()) return currentPageProvenance;
  const observed = await page.evaluate(() => {
    const assetPath = (raw) => {
      if (!raw) return null;
      try {
        const url = new URL(raw, location.href);
        const keys = Array.from(url.searchParams.keys()).sort();
        return url.pathname + (keys.length > 0 ? "?keys=" + keys.join(",") : "");
      } catch {
        return null;
      }
    };
    const meta = Array.from(document.querySelectorAll("meta[name], meta[property]")).map((element) => ({
      key: String(element.getAttribute("name") || element.getAttribute("property") || "").slice(0, 120),
      content: String(element.getAttribute("content") || "").slice(0, 200),
    })).filter((item) => item.key).sort((a, b) => a.key.localeCompare(b.key));
    const navigation = performance.getEntriesByType("navigation")[0] || null;
    return {
      url: location.href,
      path: location.pathname,
      title: document.title,
      readyState: document.readyState,
      timeOrigin: Math.round(performance.timeOrigin || 0),
      navigationStartTime: navigation ? Math.round(navigation.startTime) : null,
      scripts: Array.from(document.scripts).map((element) => assetPath(element.src)).filter(Boolean).sort(),
      stylesheets: Array.from(document.querySelectorAll('link[rel~="stylesheet"][href]')).map((element) => assetPath(element.href)).filter(Boolean).sort(),
      meta,
    };
  }).catch((error) => ({ error: errorSummary(error), url: currentPageUrl(), path: null, scripts: [], stylesheets: [], meta: [] }));
  pageLoadSeq += 1;
  currentPageProvenance = normalizePageProvenance(observed, { reason, httpStatus, pageLoadSeq });
  await appendJsonl(files.control, eventRecord("page-provenance", { reason, httpStatus, pageProvenance: compactPageProvenance(currentPageProvenance) }));
  return currentPageProvenance;
}

function normalizePageProvenance(value, options = {}) {
  const scripts = Array.isArray(value?.scripts) ? value.scripts.map(String).filter(Boolean) : [];
  const stylesheets = Array.isArray(value?.stylesheets) ? value.stylesheets.map(String).filter(Boolean) : [];
  const meta = Array.isArray(value?.meta) ? value.meta.map((item) => ({
    key: String(item?.key || "").slice(0, 120),
    contentHash: sha256Text(String(item?.content || "")),
  })).filter((item) => item.key) : [];
  const fingerprintInput = JSON.stringify({ scripts, stylesheets, meta });
  return {
    pageLoadSeq: options.pageLoadSeq ?? pageLoadSeq,
    reason: options.reason || "sample",
    observedAt: new Date().toISOString(),
    urlPath: safeUrlPath(value?.url || currentPageUrl()),
    documentPath: value?.path || null,
    titleHash: sha256Text(String(value?.title || "")),
    documentReadyState: value?.readyState || null,
    timeOrigin: Number.isFinite(Number(value?.timeOrigin)) ? Number(value.timeOrigin) : null,
    navigationStartTime: Number.isFinite(Number(value?.navigationStartTime)) ? Number(value.navigationStartTime) : null,
    httpStatus: options.httpStatus ?? null,
    assetFingerprint: sha256Text(fingerprintInput),
    scriptCount: scripts.length,
    stylesheetCount: stylesheets.length,
    metaCount: meta.length,
    scripts: scripts.slice(0, 30),
    stylesheets: stylesheets.slice(0, 30),
    meta: meta.slice(0, 30),
    error: value?.error || null,
    valuesRedacted: true,
  };
}

function compactPageProvenance(value) {
  if (!value) return null;
  return {
    pageLoadSeq: value.pageLoadSeq ?? null,
    reason: value.reason || null,
    observedAt: value.observedAt || null,
    urlPath: value.urlPath || null,
    documentReadyState: value.documentReadyState || null,
    timeOrigin: value.timeOrigin ?? null,
    httpStatus: value.httpStatus ?? null,
    assetFingerprint: value.assetFingerprint || null,
    scriptCount: value.scriptCount ?? 0,
    stylesheetCount: value.stylesheetCount ?? 0,
    metaCount: value.metaCount ?? 0,
    scripts: Array.isArray(value.scripts) ? value.scripts.slice(0, 12) : [],
    stylesheets: Array.isArray(value.stylesheets) ? value.stylesheets.slice(0, 12) : [],
    error: value.error || null,
    valuesRedacted: true,
  };
}

function isRetryableNavigationError(message) {
  return /net::ERR_NETWORK_CHANGED|net::ERR_ABORTED|net::ERR_CONNECTION_RESET|net::ERR_NAME_NOT_RESOLVED|Navigation timeout|page\.goto:\s*timeout|timeout\s+\d+ms\s+exceeded|workbench-app-not-ready/iu.test(String(message || ""));
}

function readinessRetryable(readiness) {
  const reason = String(readiness?.reason || "");
  return !/nav-access-denied|workbench-blank-document|login-visible/iu.test(reason);
}

function navigationFailureKind(message) {
  const text = String(message || "");
  if (/net::ERR_NETWORK_CHANGED/iu.test(text)) return "net::ERR_NETWORK_CHANGED";
  if (/net::ERR_ABORTED/iu.test(text)) return "net::ERR_ABORTED";
  if (/net::ERR_CONNECTION_RESET/iu.test(text)) return "net::ERR_CONNECTION_RESET";
  if (/net::ERR_NAME_NOT_RESOLVED/iu.test(text)) return "net::ERR_NAME_NOT_RESOLVED";
  if (/Navigation timeout|page\.goto:\s*timeout|timeout\s+\d+ms\s+exceeded/iu.test(text)) return "navigation-timeout";
  if (/workbench-app-not-ready/iu.test(text)) return "workbench-app-not-ready";
  return "navigation-error";
}

function redactErrorMessage(message) {
  return String(message || "")
    .replace(/([?&](?:token|key|password|secret|authorization)=)[^&\s]+/giu, "$1[redacted]")
    .replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gu, "$1[redacted]");
}

async function waitForTargetPageReady(targetPage, targetUrl, options = {}) {
  const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Math.max(1, Number(options.timeoutMs)) : 15000;
  const targetPathname = safeUrlPath(targetUrl) || "";
  if (isProjectManagementPathname(targetPathname)) {
    const started = Date.now();
    const selectors = projectManagement.readinessSelectors;
    await targetPage.waitForFunction((input) => {
      const visible = (element) => {
        if (!element) return false;
        const rect = element.getBoundingClientRect();
        const style = window.getComputedStyle(element);
        return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
      };
      return input.selectors.some((selector) => {
        try { return visible(document.querySelector(selector)); } catch { return false; }
      });
    }, { selectors }, { timeout: timeoutMs }).catch(() => null);
    const snapshot = await projectManagementReadinessSnapshot(targetPage);
    const ok = snapshot.projectManagementVisible === true || snapshot.mdtodoVisible === true;
    return {
      ok,
      reason: ok ? "project-management-ready" : snapshot.loginVisible ? "login-visible" : "project-management-not-ready",
      durationMs: Date.now() - started,
      snapshot,
      valuesRedacted: true
    };
  }
  if (!isWorkbenchPathname(targetPathname)) return { ok: true, reason: "not-workbench-route", valuesRedacted: true };
  const started = Date.now();
  await targetPage.waitForFunction(() => {
    const visible = (element) => {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const workspace = document.querySelector("#workspace, .workbench-route");
    const login = document.querySelector("form.login-card, .login-card, [data-testid='login']");
    return Boolean(visible(workspace) || visible(login));
  }, null, { timeout: timeoutMs }).catch(() => null);
  const snapshot = await workbenchReadinessSnapshot(targetPage);
  const ok = snapshot.workbenchShellVisible === true;
  const reason = workbenchReadinessReason(snapshot, ok);
  return {
    ok,
    reason,
    durationMs: Date.now() - started,
    snapshot,
    valuesRedacted: true
  };
}

async function workbenchReadinessSnapshot(targetPage) {
  const snapshot = await targetPage.evaluate(() => {
    const visible = (element) => {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const sessionCreate = document.querySelector("#session-create");
    const sessionRail = document.querySelector("#session-sidebar");
    const sessionCollapseToggle = document.querySelector("#session-collapse-toggle");
    const params = new URLSearchParams(window.location.search || "");
    const bodyText = String(document.body?.innerText || "");
    return {
      url: window.location.href,
      path: window.location.pathname,
      search: window.location.search || "",
      blockedReason: params.get("blocked"),
      readyState: document.readyState,
      workbenchShellVisible: visible(document.querySelector("#workspace, .workbench-route")),
      sessionCreatePresent: Boolean(sessionCreate),
      sessionCreateVisible: visible(sessionCreate),
      sessionRailPresent: Boolean(sessionRail),
      sessionRailCollapsed: sessionRail ? sessionRail.getAttribute("data-collapsed") === "true" || sessionRail.classList.contains("is-collapsed") : null,
      sessionCollapseTogglePresent: Boolean(sessionCollapseToggle),
      sessionCollapseToggleVisible: visible(sessionCollapseToggle),
      sessionCollapseToggleExpanded: sessionCollapseToggle ? sessionCollapseToggle.getAttribute("aria-expanded") : null,
      commandInputPresent: visible(document.querySelector("#command-input")),
      activeTabPresent: visible(document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']")),
      warningPresent: visible(document.querySelector(".composer-warning")),
      loginVisible: visible(document.querySelector("form.login-card, .login-card, [data-testid='login']")),
      bodyTextBytes: new TextEncoder().encode(bodyText).length,
      bodyTextPreview: bodyText.slice(0, 2000),
      valuesRedacted: true
    };
  }).catch((error) => ({ error: errorSummary(error), valuesRedacted: true }));
  if (snapshot && typeof snapshot.bodyTextPreview === "string") {
    snapshot.bodyTextHash = sha256Text(snapshot.bodyTextPreview);
    delete snapshot.bodyTextPreview;
  }
  return snapshot;
}

function workbenchReadinessReason(snapshot, ok = false) {
  if (ok) return "workbench-ready";
  const blocked = String(snapshot?.blockedReason || "");
  if (blocked === "nav_access_denied") return "nav-access-denied";
  if (snapshot?.loginVisible === true) return "login-visible";
  if (
    snapshot?.readyState === "complete"
    && snapshot?.workbenchShellVisible === false
    && snapshot?.sessionRailPresent === false
    && snapshot?.commandInputPresent === false
    && (snapshot?.bodyTextBytes === 0 || snapshot?.bodyTextHash === "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
  ) {
    return "workbench-blank-document";
  }
  return "workbench-app-not-ready";
}

async function projectManagementReadinessSnapshot(targetPage) {
  const selectors = projectManagement.readinessSelectors;
  return targetPage.evaluate((input) => {
    const visible = (element) => {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const selectorStates = input.selectors.map((selector) => {
      let matched = false;
      let visibleMatched = false;
      try {
        const element = document.querySelector(selector);
        matched = Boolean(element);
        visibleMatched = visible(element);
      } catch {}
      return { selector, matched, visible: visibleMatched };
    });
    return {
      url: window.location.href,
      path: window.location.pathname,
      readyState: document.readyState,
      projectManagementVisible: visible(document.querySelector('[data-testid="project-management-root"]')),
      mdtodoVisible: visible(document.querySelector('[data-testid="project-management-mdtodo"]')),
      loginVisible: visible(document.querySelector("form.login-card, .login-card, [data-testid='login']")),
      selectorStates,
      valuesRedacted: true
    };
  }, { selectors }).catch((error) => ({ error: errorSummary(error), valuesRedacted: true }));
}

async function waitForProjectManagementCommandReady(options = {}) {
  const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Math.max(1, Number(options.timeoutMs)) : 15000;
  const started = Date.now();
  const deadline = started + timeoutMs;
  let last = null;
  while (Date.now() <= deadline) {
    last = await projectManagementCommandSnapshot();
    const path = String(last?.path || safeUrlPath(currentPageUrl()) || "");
    const needsTask = /\/tasks\//u.test(path);
    const selectedTaskReady = Boolean(last?.selectedTaskId || last?.selectedTaskRef?.hash || last?.taskBodyVisible === true || last?.launchButtonVisible === true);
    const taskCollectionReady = Number(last?.taskCount || 0) > 0;
    const baseReady = last?.pageKind === "project-management-mdtodo"
      && Number(last?.sourceCount || 0) > 0
      && Number(last?.fileCount || 0) > 0
      && (needsTask ? selectedTaskReady : taskCollectionReady);
    const taskReady = !needsTask || selectedTaskReady;
    const needsReport = /\/reports\//u.test(path);
    const reportReady = !needsReport || last?.reportPreviewVisible === true || last?.reportFullscreenVisible === true;
    if (baseReady && taskReady && reportReady) return { ok: true, reason: "project-management-command-ready", durationMs: Date.now() - started, snapshot: last, valuesRedacted: true };
    await page.waitForTimeout(250).catch(() => {});
  }
  return { ok: false, reason: "project-management-command-not-ready", durationMs: Date.now() - started, snapshot: last, valuesRedacted: true };
}

function isWorkbenchPathname(value) {
  const pathname = String(value || "");
  return pathname === "/workbench" || pathname === "/workspace" || pathname.startsWith("/workbench/") || pathname.startsWith("/workspace/");
}

function isProjectManagementPathname(value) {
  if (projectManagement.enabled !== true) return false;
  const pathname = String(value || "");
  return projectManagement.targetPaths.some((target) => pathname === target || pathname.startsWith(target + "/"));
}

function isAgentSessionCreateRequest(requestOrUrl) {
  const method = typeof requestOrUrl?.method === "function" ? requestOrUrl.method().toUpperCase() : "";
  if (method && method !== "POST") return false;
  const url = typeof requestOrUrl === "string" ? requestOrUrl : typeof requestOrUrl?.url === "function" ? requestOrUrl.url() : "";
  try {
    return new URL(url).pathname === "/v1/agent/sessions";
  } catch {
    return false;
  }
}

function requestFailureSummary(request) {
  let failure = null;
  try {
    failure = request.failure();
  } catch {}
  let urlPath = null;
  try {
    urlPath = new URL(request.url()).pathname;
  } catch {}
  return {
    method: typeof request.method === "function" ? request.method().toUpperCase() : null,
    urlPath,
    failureText: failure?.errorText || null,
    valuesRedacted: true
  };
}

async function ensureSessionRailExpanded() {
  const before = await workbenchReadinessSnapshot(page);
  if (before?.sessionCreateVisible === true) {
    return { ok: true, action: "already-visible", before, after: before, valuesRedacted: true };
  }
  const toggle = page.locator("#session-collapse-toggle").first();
  const toggleVisible = await toggle.isVisible({ timeout: 2000 }).catch(() => false);
  if (before?.sessionRailCollapsed !== true || !toggleVisible) {
    return { ok: false, action: "not-expanded", reason: before?.sessionRailCollapsed === true ? "collapse-toggle-not-visible" : "session-rail-not-collapsed", before, after: before, valuesRedacted: true };
  }
  await toggle.click();
  await page.waitForFunction(() => {
    const visible = (element) => {
      if (!element) return false;
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    const rail = document.querySelector("#session-sidebar");
    return Boolean(visible(document.querySelector("#session-create")) || (rail && rail.getAttribute("data-collapsed") === "false"));
  }, null, { timeout: 5000 }).catch(() => null);
  const after = await workbenchReadinessSnapshot(page);
  return {
    ok: after?.sessionCreateVisible === true,
    action: "expanded-session-rail",
    before,
    after,
    valuesRedacted: true
  };
}

async function clickAndWaitForAgentSessionCreate(create) {
  let removeRequestFailedListener = () => {};
  const requestFailedPromise = new Promise((resolve) => {
    let timeout = null;
    const handler = (request) => {
      if (!isAgentSessionCreateRequest(request)) return;
      removeRequestFailedListener();
      resolve({ kind: "requestfailed", requestFailure: requestFailureSummary(request) });
    };
    removeRequestFailedListener = () => {
      page.off("requestfailed", handler);
      if (timeout !== null) clearTimeout(timeout);
      timeout = null;
    };
    page.on("requestfailed", handler);
    timeout = setTimeout(() => {
      removeRequestFailedListener();
      resolve(null);
    }, 45000);
  });
  const createResponsePromise = page.waitForResponse((response) => {
    const request = response.request();
    return isAgentSessionCreateRequest(request) || isAgentSessionCreateRequest(response.url());
  }, { timeout: 45000 }).then((response) => ({ kind: "response", response })).catch((error) => ({ kind: "wait-error", waitError: errorSummary(error) }));
  await create.click();
  const outcome = await Promise.race([createResponsePromise, requestFailedPromise]);
  removeRequestFailedListener();
  return outcome ?? await createResponsePromise;
}

async function createSessionFromUi() {
  const beforeUrl = currentPageUrl();
  const before = await workbenchSessionSnapshot();
  const attempts = [];
  let createResponse = null;
  for (let attempt = 1; attempt <= 2; attempt += 1) {
    const railExpansion = await ensureSessionRailExpanded();
    const readinessBeforeClick = railExpansion.after || await workbenchReadinessSnapshot(page);
    const create = page.locator("#session-create").first();
    try {
      await create.waitFor({ state: "visible", timeout: 15000 });
    } catch (error) {
      const readinessAfterWait = await workbenchReadinessSnapshot(page);
      const createError = new Error("newSession session create button is not visible");
      createError.details = { beforeUrl, afterUrl: currentPageUrl(), before, attempts, attempt, readinessBeforeClick, readinessAfterWait, railExpansion, waitError: errorSummary(error), pageId, valuesRedacted: true };
      throw createError;
    }
    const createButtonState = await create.evaluate((element) => {
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return {
        tag: element.tagName.toLowerCase(),
        id: element.id || null,
        disabled: Boolean(element.disabled),
        ariaDisabled: element.getAttribute("aria-disabled") || null,
        visible: rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none",
        rect: { width: Math.round(rect.width), height: Math.round(rect.height) },
        valuesRedacted: true
      };
    }).catch((error) => ({ error: errorSummary(error), valuesRedacted: true }));
    const outcome = await clickAndWaitForAgentSessionCreate(create);
    if (outcome?.kind === "response") {
      createResponse = outcome.response;
      attempts.push({ attempt, outcome: "response", readinessBeforeClick, railExpansion, createButtonState, valuesRedacted: true });
      break;
    }
    const afterAttempt = await workbenchSessionSnapshot();
    attempts.push({
      attempt,
      outcome: outcome?.kind || "unknown",
      readinessBeforeClick,
      railExpansion,
      createButtonState,
      waitError: outcome?.waitError || null,
      requestFailure: outcome?.requestFailure || null,
      after: afterAttempt,
      valuesRedacted: true
    });
    if (attempt < 2 && outcome?.kind === "requestfailed") {
      await page.waitForTimeout(1500);
      continue;
    }
    const waitError = outcome?.waitError || outcome?.requestFailure || { name: outcome?.kind || "unknown", valuesRedacted: true };
    const error = new Error("newSession did not observe POST /v1/agent/sessions response after click: " + (waitError.message || waitError.failureText || waitError.name || "timeout"));
    error.details = { beforeUrl, afterUrl: currentPageUrl(), before, attempts, pageId, valuesRedacted: true };
    throw error;
  }
  if (createResponse === null) throw new Error("newSession did not produce an authoritative session create response");
  const createStatus = createResponse.status();
  let createPayload = null;
  let createPayloadError = null;
  const createBody = await boundedResponseJson(createResponse);
  createPayload = createBody.value;
  createPayloadError = createBody.error;
  const createdSessionId = sessionIdFromAgentSessionPayload(createPayload);
  const createErrorCode = errorCodeFromPayload(createPayload);
  if (createStatus < 200 || createStatus >= 300) {
    const suffix = createErrorCode ? " code=" + createErrorCode : "";
    const error = new Error("newSession POST /v1/agent/sessions failed with HTTP " + createStatus + suffix);
    error.details = { status: createStatus, statusText: createResponse.statusText(), responseParsed: createPayload !== null, responseParseError: createPayloadError, responseErrorCode: createErrorCode, valuesRedacted: true };
    throw error;
  }
  if (!createdSessionId) {
    const error = new Error("newSession did not receive an authoritative session id from POST /v1/agent/sessions");
    error.details = { status: createStatus, statusText: createResponse.statusText(), responseParsed: createPayload !== null, responseParseError: createPayloadError, responseErrorCode: createErrorCode, createdSessionId, valuesRedacted: true };
    throw error;
  }
  await page.waitForFunction((expectedSessionId) => {
    const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
    const sessionId = activeTab?.getAttribute("data-session-id") || "";
    const routeMatch = window.location.pathname.match(/\/workbench\/sessions\/([^/]+)/u) || window.location.pathname.match(/\/workspace\/sessions\/([^/]+)/u);
    const routeSessionId = routeMatch ? decodeURIComponent(routeMatch[1] || "") : "";
    const warning = document.querySelector(".composer-warning")?.textContent?.trim() || "";
    const input = document.querySelector("#command-input");
    return Boolean(activeTab && sessionId === expectedSessionId && routeSessionId === expectedSessionId && input && !input.disabled && !warning);
  }, createdSessionId, { timeout: 45000 }).catch(() => null);
  const after = await workbenchSessionSnapshot();
  const afterSessionId = after?.activeSessionId || after?.routeSessionId || "";
  const ok = Boolean(afterSessionId === createdSessionId && after?.routeSessionId === createdSessionId && after?.composerReady);
  if (!ok) {
    const error = new Error("newSession did not select the authoritative newly created workbench session");
    error.details = { beforeUrl, afterUrl: currentPageUrl(), before, after, createdSessionId, attempts, pageId, valuesRedacted: true };
    throw error;
  }
  return {
    beforeUrl,
    afterUrl: currentPageUrl(),
    ok,
    before,
    after,
    attempts,
    sessionId: createdSessionId,
    createSession: { status: createStatus, statusText: createResponse.statusText(), responseParsed: createPayload !== null, responseParseError: createPayloadError, createdSessionId, valuesRedacted: true },
    pageId
  };
}

function sessionIdFromAgentSessionPayload(payload) {
  const direct = payload?.sessionId ?? payload?.id ?? payload?.session?.sessionId ?? payload?.session?.id ?? payload?.data?.sessionId ?? payload?.data?.id ?? payload?.data?.session?.sessionId ?? payload?.data?.session?.id;
  const directText = String(direct || "").trim();
  if (/^ses_[A-Za-z0-9_-]+$/u.test(directText)) return directText;
  const match = JSON.stringify(payload ?? "").match(/\bses_[A-Za-z0-9_-]+\b/u);
  return match ? match[0] : null;
}

function errorCodeFromPayload(payload) {
  const direct = payload?.error?.code ?? payload?.code ?? payload?.data?.error?.code ?? payload?.data?.code;
  const value = String(direct || "").trim();
  return /^[A-Za-z0-9_.:-]{1,120}$/u.test(value) ? value : null;
}

function traceIdFromAgentChatPayload(payload) {
  const direct = payload?.traceId ?? payload?.turn?.traceId ?? payload?.message?.traceId ?? payload?.data?.traceId ?? payload?.data?.turn?.traceId ?? payload?.data?.message?.traceId;
  const directText = String(direct || "").trim();
  if (/^(?:trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})$/u.test(directText)) return directText;
  const match = JSON.stringify(payload ?? "").match(/\b(?:trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/u);
  return match ? match[0] : null;
}

async function workbenchSessionSnapshot(targetPage = page) {
  return targetPage.evaluate(() => {
    const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
    const routeMatch = window.location.pathname.match(/\/workbench\/sessions\/([^/]+)/u) || window.location.pathname.match(/\/workspace\/sessions\/([^/]+)/u);
    const input = document.querySelector("#command-input");
    const warning = document.querySelector(".composer-warning")?.textContent?.trim() || null;
    const visible = (element) => {
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    return {
      url: window.location.href,
      routeSessionId: routeMatch ? decodeURIComponent(routeMatch[1] || "") : null,
      activeSessionId: activeTab?.getAttribute("data-session-id") || null,
      activeConversationId: activeTab?.getAttribute("data-conversation-id") || null,
      activeStatus: activeTab?.getAttribute("data-status") || null,
      tabCount: document.querySelectorAll(".session-tab").length,
      messageCount: Array.from(document.querySelectorAll('article.message-card, .message-card[data-message-id], article[data-message-id]')).filter(visible).length,
      traceRowCount: Array.from(document.querySelectorAll('[data-testid*="trace" i], [class*="trace" i], [data-trace-id], [data-testid*="event" i]')).filter(visible).length,
      loadingCount: Array.from(document.querySelectorAll('[aria-busy="true"], [data-loading="true"], [class*="loading" i], [data-testid*="loading" i]')).filter(visible).length,
      composerReady: Boolean(activeTab && input && !input.disabled && !warning),
      warning
    };
  }).catch(() => null);
}

function controlPageRecoveryTarget(snapshot, beforeUrl) {
  const sessionId = snapshot?.routeSessionId || snapshot?.activeSessionId || routeSessionIdFromUrl(beforeUrl);
  if (sessionId) return { sessionId, targetPath: "/workbench/sessions/" + encodeURIComponent(sessionId), valuesRedacted: true };
  const path = safeUrlPath(beforeUrl);
  if (isWorkbenchPathname(path || "")) return { sessionId: null, targetPath: path, valuesRedacted: true };
  return { sessionId: null, targetPath, valuesRedacted: true };
}

function controlPageProjectionMissingForCommand(snapshot, beforeUrl) {
  const path = safeUrlPath(snapshot?.url || beforeUrl);
  if (!isWorkbenchPathname(path || "")) return false;
  const routeSessionId = snapshot?.routeSessionId || routeSessionIdFromUrl(snapshot?.url || beforeUrl);
  if (!routeSessionId) return false;
  return snapshot?.activeSessionId !== routeSessionId
    && Number(snapshot?.tabCount || 0) === 0
    && Number(snapshot?.messageCount || 0) === 0
    && Number(snapshot?.traceRowCount || 0) === 0
    && snapshot?.composerReady !== true;
}

async function controlPageLivenessSnapshot(reason, timeoutMs = 1500) {
  const started = Date.now();
  return withHardTimeout(workbenchSessionSnapshot(page), timeoutMs, "control page liveness snapshot exceeded " + timeoutMs + "ms")
    .then((snapshot) => ({
      ok: snapshot !== null,
      reason,
      durationMs: Date.now() - started,
      snapshot,
      pageRole: "control",
      pageId,
      pageEpoch: controlPageEpoch,
      valuesRedacted: true
    }))
    .catch((error) => ({
      ok: false,
      reason,
      durationMs: Date.now() - started,
      error: errorSummary(error),
      pageRole: "control",
      pageId,
      pageEpoch: controlPageEpoch,
      valuesRedacted: true
    }));
}

async function recoverControlPageToTarget(reason, beforeUrl, target, liveness = null, options = {}) {
  let navigation = null;
  let hydration = null;
  let afterLiveness = null;
  const attempts = [];
  let ok = false;
  const maxAttempts = Number.isFinite(Number(options.maxAttempts)) ? Math.max(1, Number(options.maxAttempts)) : 2;
  const hydrationTimeoutMs = Number.isFinite(Number(options.hydrationTimeoutMs)) ? Math.max(1, Number(options.hydrationTimeoutMs)) : 12000;
  const hydrationHardTimeoutMs = Number.isFinite(Number(options.hydrationHardTimeoutMs)) ? Math.max(hydrationTimeoutMs, Number(options.hydrationHardTimeoutMs)) : hydrationTimeoutMs + 2000;
  const navigationOptions = options.navigation || {};
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    await recreateControlPageForNavigation(reason + "-control-page-recovery", attempt);
    try {
      navigation = await gotoTarget(target.targetPath, navigationOptions);
    } catch (error) {
      navigation = { ok: false, targetPath: target.targetPath, error: errorSummary(error), valuesRedacted: true };
    }
    if (!navigation?.error && target.sessionId) {
      hydration = await withHardTimeout(
        waitForWorkbenchSessionHydrated(page, target.sessionId, { timeoutMs: hydrationTimeoutMs }),
        hydrationHardTimeoutMs,
        "control page recovery hydration exceeded " + hydrationHardTimeoutMs + "ms"
      ).catch((error) => ({ ok: false, error: errorSummary(error), valuesRedacted: true }));
    } else {
      hydration = null;
    }
    afterLiveness = await controlPageLivenessSnapshot(reason + "-post-recovery", 3000);
    ok = !navigation?.error && afterLiveness.ok === true && (!target.sessionId || hydration?.ok === true);
    attempts.push({ attempt, ok, navigation, hydration, afterLiveness, pageId, pageEpoch: controlPageEpoch, valuesRedacted: true });
    if (ok) break;
  }
  return {
    ok,
    recovered: ok,
    reason,
    beforeUrl,
    afterUrl: currentPageUrl(),
    target,
    liveness,
    navigation,
    hydration,
    afterLiveness,
    attempts,
    pageRole: "control",
    pageId,
    pageEpoch: controlPageEpoch,
    valuesRedacted: true
  };
}

async function promoteObserverPageToControlForCommand(reason, target, liveness = null) {
  const beforeUrl = currentPageUrl();
  const beforeObserverUrl = pageUrl(observerPage);
  if (!observerPage || observerPage.isClosed()) {
    return { ok: false, promoted: false, reason, failureKind: "observer-page-unavailable", beforeUrl, observerUrl: beforeObserverUrl, target, liveness, valuesRedacted: true };
  }
  const sessionId = target?.sessionId || routeSessionIdFromUrl(beforeUrl);
  if (!sessionId) {
    return { ok: false, promoted: false, reason, failureKind: "observer-promotion-needs-session", beforeUrl, observerUrl: beforeObserverUrl, target, liveness, valuesRedacted: true };
  }
  const observerSessionId = routeSessionIdFromUrl(beforeObserverUrl);
  if (observerSessionId !== sessionId) {
    return { ok: false, promoted: false, reason, failureKind: "observer-session-mismatch", beforeUrl, observerUrl: beforeObserverUrl, observerSessionId, sessionId, target, liveness, valuesRedacted: true };
  }
  const targetUrl = new URL(target?.targetPath || ("/workbench/sessions/" + encodeURIComponent(sessionId)), baseUrl).toString();
  const readiness = await observerSessionReadiness(targetUrl, sessionId, { readinessTimeoutMs: 1000, hydrationTimeoutMs: 2000 });
  const observerComposerReady = readiness?.hydration?.snapshot?.composerReady === true;
  if (readiness.ok !== true || observerComposerReady !== true) {
    return { ok: false, promoted: false, reason, failureKind: readiness.failureKind || (observerComposerReady ? "observer-not-ready" : "observer-composer-not-ready"), beforeUrl, observerUrl: beforeObserverUrl, sessionId, target, liveness, readiness, valuesRedacted: true };
  }
  const oldControlPage = page;
  observerPageEpoch += 1;
  controlPageEpoch += 1;
  page = observerPage;
  observerPage = null;
  attachPassiveListeners(page, "control", pageId);
  currentPageProvenance = null;
  if (oldControlPage && !oldControlPage.isClosed() && oldControlPage !== page) {
    await withHardTimeout(oldControlPage.close(), 2000, "old control page close exceeded 2000ms")
      .catch((error) => appendJsonl(files.errors, eventRecord("old-control-page-close-timeout", { reason, error: errorSummary(error), pageRole: "control", pageId, pageEpoch: controlPageEpoch })));
  }
  observerPage = await context.newPage();
  attachPassiveListeners(observerPage, "observer", observerPageId);
  const observerSync = await syncObserverPageToControlSession(reason + "-observer-recreated-after-promotion", sessionId, {
    maxAttempts: 1,
    navigationTimeoutMs: 8000,
    readinessTimeoutMs: 3000,
    hydrationTimeoutMs: 3000,
    shortCircuitReadinessTimeoutMs: 1000,
    shortCircuitHydrationTimeoutMs: 1000,
  });
  return {
    ok: true,
    promoted: true,
    reason,
    beforeUrl,
    beforeObserverUrl,
    afterUrl: currentPageUrl(),
    sessionId,
    target,
    liveness,
    readiness,
    observerSync,
    pageRole: "control",
    pageId,
    pageEpoch: controlPageEpoch,
    valuesRedacted: true
  };
}

async function ensureControlPageResponsiveForCommand(reason) {
  const beforeUrl = currentPageUrl();
  const liveness = await controlPageLivenessSnapshot(reason + "-preflight", 3000);
  const projectionMissing = liveness.ok === true && controlPageProjectionMissingForCommand(liveness.snapshot, beforeUrl);
  if (liveness.ok && !projectionMissing) return { ok: true, recovered: false, reason, beforeUrl, afterUrl: currentPageUrl(), liveness, pageRole: "control", pageId, pageEpoch: controlPageEpoch, valuesRedacted: true };
  const target = controlPageRecoveryTarget(liveness.snapshot, beforeUrl);
  await appendJsonl(files.control, eventRecord(projectionMissing ? "control-page-projection-missing-before-command" : "control-page-unresponsive-before-command", {
    reason,
    beforeUrl,
    target,
    liveness,
    pageRole: "control",
    pageId,
    pageEpoch: controlPageEpoch,
    valuesRedacted: true
  }));
  const promotion = await promoteObserverPageToControlForCommand(reason + "-observer-promotion", target, liveness);
  await appendJsonl(files.control, eventRecord(promotion.ok ? "control-page-promoted-from-observer-before-command" : "control-page-observer-promotion-skipped-before-command", promotion));
  if (promotion.ok === true) return promotion;
  const recovery = await recoverControlPageToTarget(reason, beforeUrl, target, liveness, {
    maxAttempts: 1,
    navigation: { maxAttempts: 1, navigationTimeoutMs: 8000, readinessTimeoutMs: 4000, settleMs: 250, lateReadinessTimeoutMs: 1000 },
    hydrationTimeoutMs: 4000,
    hydrationHardTimeoutMs: 5000,
  });
  await appendJsonl(files.control, eventRecord(recovery.ok ? "control-page-recovered-before-command" : "control-page-recovery-failed-before-command", recovery));
  if (!recovery.ok) {
    const error = new Error("control page recovery failed before " + reason);
    error.details = recovery;
    throw error;
  }
  return recovery;
}

async function forceRecoverControlPageForCommand(reason) {
  const beforeUrl = currentPageUrl();
  const liveness = await controlPageLivenessSnapshot(reason + "-snapshot", 3000);
  const target = controlPageRecoveryTarget(liveness.snapshot, beforeUrl);
  await appendJsonl(files.control, eventRecord("control-page-forced-recovery-before-command", {
    reason,
    beforeUrl,
    target,
    liveness,
    pageRole: "control",
    pageId,
    pageEpoch: controlPageEpoch,
    valuesRedacted: true
  }));
  const promotion = await promoteObserverPageToControlForCommand(reason + "-observer-promotion", target, liveness);
  await appendJsonl(files.control, eventRecord(promotion.ok ? "control-page-forced-promoted-from-observer-before-command" : "control-page-forced-observer-promotion-skipped-before-command", promotion));
  if (promotion.ok === true) return promotion;
  const recovery = await recoverControlPageToTarget(reason, beforeUrl, target, liveness, {
    maxAttempts: 1,
    navigation: { maxAttempts: 1, navigationTimeoutMs: 8000, readinessTimeoutMs: 4000, settleMs: 250, lateReadinessTimeoutMs: 1000 },
    hydrationTimeoutMs: 4000,
    hydrationHardTimeoutMs: 5000,
  });
  await appendJsonl(files.control, eventRecord(recovery.ok ? "control-page-forced-recovered-before-command" : "control-page-forced-recovery-failed-before-command", recovery));
  return recovery;
}

async function sendPrompt(text, options = {}) {
  if (text.trim().length === 0) throw new Error("sendPrompt requires non-empty text");
  const responsePath = options.responsePath || "/v1/agent/chat";
  const requestedProvider = String(options.provider || "").trim();
  const controlRecovery = await ensureControlPageResponsiveForCommand("sendPrompt");
  const beforeUrl = currentPageUrl();
  const beforeEvidence = await promptSideEffectSnapshot();
  const providerSelection = requestedProvider ? await selectProvider(requestedProvider) : null;
  let editor = null;
  let composerRecovery = null;
  let editorWaitError = null;
  for (let attempt = 1; attempt <= 2; attempt += 1) {
    const primaryEditor = page.locator("#command-input").last();
    const candidate = await primaryEditor.isVisible().catch(() => false)
      ? primaryEditor
      : page.locator('textarea, [role="textbox"], [contenteditable="true"], input[type="text"]').last();
    try {
      await withHardTimeout(candidate.waitFor({ state: "visible", timeout: 8000 }), 10000, "sendPrompt composer editor did not become visible within 10s");
      editor = candidate;
      break;
    } catch (error) {
      editorWaitError = error;
      if (attempt >= 2) break;
      composerRecovery = await forceRecoverControlPageForCommand("sendPrompt-composer-editor-missing");
      if (composerRecovery.ok !== true) break;
    }
  }
  if (!editor) {
    const snapshot = await controlPageLivenessSnapshot("sendPrompt-composer-editor-missing-final", 3000);
    const error = new Error("sendPrompt composer editor did not become visible");
    error.details = { beforeUrl, afterUrl: currentPageUrl(), requestedProvider: requestedProvider || null, providerSelection, controlRecovery, composerRecovery, snapshot, editorWaitError: errorSummary(editorWaitError), pageId, pageEpoch: controlPageEpoch, valuesRedacted: true };
    throw error;
  }
  editor = await fillComposerEditorWithRetry(editor, text, { beforeUrl, controlRecovery });
  const primarySubmitSelector = '#command-send, #command-submit, [data-testid="command-submit"], [data-testid="composer-submit"], [data-testid="send-command"]';
  const primarySubmit = page.locator(primarySubmitSelector).last();
  const submit = await primarySubmit.isVisible().catch(() => false)
    ? primarySubmit
    : page.locator([
    'button[type="submit"]',
    'button:has-text("发送")',
    'button:has-text("Send")',
    '[data-testid*="send" i]',
    '[aria-label*="send" i]',
    '[aria-label*="发送"]'
  ].join(", ")).last();
  await withHardTimeout(submit.waitFor({ state: "visible", timeout: 15000 }), 20000, "sendPrompt submit button did not become visible within 20s");
  if (options.expectedAction) {
    const configuredActionWaitMs = options.expectedActionWaitMs === null || options.expectedActionWaitMs === undefined || options.expectedActionWaitMs === ""
      ? null
      : Number(options.expectedActionWaitMs);
    const actionWaitMs = Number.isFinite(configuredActionWaitMs)
      ? Math.max(1000, Math.trunc(configuredActionWaitMs))
      : options.expectedAction === "turn" ? 180000 : 1000;
    const actionDeadline = Date.now() + actionWaitMs;
    let composer = null;
    while (Date.now() <= actionDeadline) {
      composer = await composerButtonState(submit);
      if (composer.action === options.expectedAction && composer.disabled !== true) break;
      await page.waitForTimeout(250);
    }
    composer = composer || await composerButtonState(submit);
    if (composer.action !== options.expectedAction || composer.disabled === true) {
      await clearComposerEditor(editor).catch(() => {});
      const sideEffect = await waitForPromptSideEffect(beforeEvidence, 200).catch(() => promptSideEffectSnapshot());
      const blocked = {
        beforeUrl,
        afterUrl: currentPageUrl(),
        textHash: sha256Text(text),
        textBytes: Buffer.byteLength(text),
        requestedProvider: requestedProvider || null,
        providerSelection,
        submitted: false,
        blocked: true,
        degradedReason: options.noActiveReason || "composer-action-mismatch",
        composer,
        chatSubmit: {
          status: null,
          statusText: null,
          urlPath: responsePath,
          responseObserved: false,
          sideEffectObserved: sideEffectHasAuthoritativePromptSubmission(sideEffect),
          sideEffect,
          actionWaitMs,
          expectedAction: options.expectedAction,
          actualAction: composer.action,
          valuesRedacted: true
        },
        controlRecovery,
        pageId,
        pageEpoch: controlPageEpoch,
        valuesRedacted: true
      };
      if (options.throwOnActionMismatch === true) {
        const error = new Error("sendPrompt composer action mismatch: expected " + options.expectedAction + " actual " + (composer.action || "null"));
        error.details = blocked;
        throw error;
      }
      return blocked;
    }
  }
  const acceptedResponsePaths = [responsePath, ...(Array.isArray(options.alternateResponsePaths) ? options.alternateResponsePaths : [])];
  const chatResponsePromise = page.waitForResponse((response) => {
    const request = response.request();
    if (request.method().toUpperCase() !== "POST") return false;
    try {
      return acceptedResponsePaths.includes(new URL(response.url()).pathname);
    } catch {
      return false;
    }
  }, { timeout: 45000 }).catch((error) => ({ waitError: errorSummary(error) }));
  await submit.click();
  const chatResponse = await chatResponsePromise;
  await page.waitForTimeout(500);
  if (chatResponse?.waitError) {
    const sideEffect = await waitForPromptSideEffect(beforeEvidence, 5000);
    if (sideEffectHasAuthoritativePromptSubmission(sideEffect)) {
      return {
        beforeUrl,
        afterUrl: currentPageUrl(),
        textHash: sha256Text(text),
        textBytes: Buffer.byteLength(text),
        requestedProvider: requestedProvider || null,
        providerSelection,
        chatSubmit: { status: null, statusText: null, urlPath: responsePath, waitError: chatResponse.waitError, sideEffectObserved: true, sideEffect },
        controlRecovery,
        pageId,
        pageEpoch: controlPageEpoch
      };
    }
    const error = new Error("sendPrompt did not observe POST " + responsePath + " response or an authoritative new turn after submit: " + (chatResponse.waitError.message || chatResponse.waitError.name || "timeout"));
    error.details = {
      beforeUrl,
      afterUrl: currentPageUrl(),
      textHash: sha256Text(text),
      textBytes: Buffer.byteLength(text),
      requestedProvider: requestedProvider || null,
      providerSelection,
      chatSubmit: { status: null, statusText: null, urlPath: responsePath, waitError: chatResponse.waitError, sideEffectObserved: false, sideEffect },
      controlRecovery,
      pageId,
      pageEpoch: controlPageEpoch,
      valuesRedacted: true
    };
    throw error;
  }
  const chatStatus = chatResponse.status();
  let chatUrlPath = responsePath;
  try {
    chatUrlPath = new URL(chatResponse.url()).pathname;
  } catch {
    chatUrlPath = responsePath;
  }
  if (chatStatus < 200 || chatStatus >= 300) {
    throw new Error("sendPrompt observed POST " + chatUrlPath + " HTTP " + chatStatus + " " + chatResponse.statusText());
  }
  let chatPayload = null;
  let chatPayloadError = null;
  const chatBody = await boundedResponseJson(chatResponse);
  chatPayload = chatBody.value;
  chatPayloadError = chatBody.error;
  const payloadText = chatPayload ? JSON.stringify(chatPayload) : "";
  const traceId = payloadText.match(/\btrc_[A-Za-z0-9_-]+\b/u)?.[0] || null;
  const otelTraceId = typeof chatPayload?.otelTrace?.traceId === "string" && /^[0-9a-f]{32}$/u.test(chatPayload.otelTrace.traceId)
    ? chatPayload.otelTrace.traceId
    : null;
  return {
    beforeUrl,
    afterUrl: currentPageUrl(),
    textHash: sha256Text(text),
    textBytes: Buffer.byteLength(text),
    requestedProvider: requestedProvider || null,
    providerSelection,
    chatSubmit: {
      status: chatStatus,
      statusText: chatResponse.statusText(),
      urlPath: chatUrlPath,
      traceId,
      otelTraceId,
      resultUrl: safeUrlPath(chatPayload?.resultUrl),
      turnUrl: safeUrlPath(chatPayload?.turnUrl),
      streamUrl: safeUrlPath(chatPayload?.streamUrl),
      responseParsed: chatPayload !== null,
      responseParseError: chatPayloadError,
      valuesRedacted: true
    },
    controlRecovery,
    pageId,
    pageEpoch: controlPageEpoch
  };
}

async function fillComposerEditorWithRetry(initialEditor, text, context = {}) {
  let editor = initialEditor;
  let lastError = null;
  let recovery = null;
  for (let attempt = 1; attempt <= 2; attempt += 1) {
    try {
      if (attempt > 1 || !editor) editor = await resolveComposerEditor(8000);
      await fillComposerEditor(editor, text, { timeoutMs: 12000 });
      const retained = await composerEditorRetainsText(editor, text);
      if (retained) return editor;
      throw new Error("sendPrompt composer editor did not retain filled text");
    } catch (error) {
      lastError = error;
      await appendJsonl(files.control, eventRecord("sendPrompt-composer-fill-retry", {
        attempt,
        willRetry: attempt < 2,
        error: errorSummary(error),
        beforeUrl: context.beforeUrl || null,
        afterUrl: currentPageUrl(),
        controlRecovery: context.controlRecovery || null,
        pageId,
        pageEpoch: controlPageEpoch,
        valuesRedacted: true
      }));
      if (attempt >= 2) break;
      recovery = await forceRecoverControlPageForCommand("sendPrompt-composer-fill-failed");
      if (recovery.ok !== true) await page.waitForTimeout(250);
      editor = null;
    }
  }
  const snapshot = await controlPageLivenessSnapshot("sendPrompt-composer-fill-failed-final", 3000);
  const error = new Error("sendPrompt composer editor fill failed");
  error.details = {
    beforeUrl: context.beforeUrl || null,
    afterUrl: currentPageUrl(),
    controlRecovery: context.controlRecovery || null,
    recovery,
    snapshot,
    fillError: errorSummary(lastError),
    pageId,
    pageEpoch: controlPageEpoch,
    valuesRedacted: true
  };
  throw error;
}

async function resolveComposerEditor(timeoutMs = 8000) {
  const primaryEditor = page.locator("#command-input").last();
  const candidate = await primaryEditor.isVisible().catch(() => false)
    ? primaryEditor
    : page.locator('textarea, [role="textbox"], [contenteditable="true"], input[type="text"]').last();
  await candidate.waitFor({ state: "visible", timeout: timeoutMs });
  return candidate;
}

async function fillComposerEditor(editor, text, options = {}) {
  const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Math.max(1000, Math.trunc(Number(options.timeoutMs))) : 30000;
  const tag = await editor.evaluate((element) => element.tagName.toLowerCase()).catch(() => "");
  const editable = await editor.evaluate((element) => element.getAttribute("contenteditable") === "true").catch(() => false);
  if (tag === "textarea" || tag === "input") await editor.fill(text, { timeout: timeoutMs });
  else if (editable) {
    await editor.click({ timeout: timeoutMs });
    await page.keyboard.insertText(text);
  } else {
    await editor.click({ timeout: timeoutMs });
    await page.keyboard.insertText(text);
  }
}

async function composerEditorRetainsText(editor, expectedText) {
  const expected = String(expectedText || "");
  return editor.evaluate((element, value) => {
    if (element instanceof HTMLTextAreaElement || element instanceof HTMLInputElement) return element.value === value;
    return (element.textContent || "") === value;
  }, expected).catch(() => false);
}

async function clearComposerEditor(editor) {
  const tag = await editor.evaluate((element) => element.tagName.toLowerCase()).catch(() => "");
  const editable = await editor.evaluate((element) => element.getAttribute("contenteditable") === "true").catch(() => false);
  if (tag === "textarea" || tag === "input") {
    await editor.fill("");
    return;
  }
  if (editable) {
    await editor.click();
    await page.keyboard.press(process.platform === "darwin" ? "Meta+A" : "Control+A").catch(() => {});
    await page.keyboard.press("Backspace").catch(() => {});
  }
}

async function composerButtonState(button) {
  return button.evaluate((element) => ({
    action: element.getAttribute("data-action") || null,
    disabled: element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true",
    text: (element.textContent || "").trim().slice(0, 80),
    title: element.getAttribute("title") || null,
    ariaLabel: element.getAttribute("aria-label") || null,
    testId: element.getAttribute("data-testid") || null,
  })).catch((error) => ({ action: null, disabled: null, error: errorSummary(error), valuesRedacted: true }));
}

function sideEffectHasAuthoritativePromptSubmission(sideEffect) {
  return Boolean(
    (Array.isArray(sideEffect?.newRunIds) && sideEffect.newRunIds.length > 0)
    || (Array.isArray(sideEffect?.newTraceIds) && sideEffect.newTraceIds.length > 0)
    || Number(sideEffect?.messageCountDelta || 0) > 0
  );
}

async function waitForPromptSideEffect(beforeEvidence, timeoutMs) {
  await page.waitForFunction((before) => {
    const current = (() => {
      const text = document.body?.innerText || "";
      const runIds = Array.from(new Set(text.match(/run_[A-Za-z0-9_:-]+/gu) || [])).slice(-20);
      const traceIds = Array.from(new Set(text.match(/trc_[A-Za-z0-9_:-]+/gu) || [])).slice(-20);
      const running = /Trace running|最近\s*\d+\s*(?:秒|分钟|分|小时)前|Code Agent\s*耗时/iu.test(text);
      const executionError = /AgentRun error|agentrun:error:|provider-stream-disconnected|provider-unavailable/iu.test(text);
      const visible = (element) => {
        const rect = element.getBoundingClientRect();
        const style = window.getComputedStyle(element);
        return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
      };
      const messageCount = Array.from(document.querySelectorAll('article.message-card, .message-card[data-message-id], article[data-message-id]')).filter(visible).length;
      const textBytes = new TextEncoder().encode(text).length;
      return { runIds, traceIds, running, executionError, messageCount, textBytes };
    })();
    const beforeRuns = Array.isArray(before?.runIds) ? before.runIds : [];
    const beforeTraces = Array.isArray(before?.traceIds) ? before.traceIds : [];
    const newRun = current.runIds.some((id) => !beforeRuns.includes(id));
    const newTrace = current.traceIds.some((id) => !beforeTraces.includes(id));
    const newMessage = current.messageCount > Number(before?.messageCount || 0);
    return newRun || newTrace || newMessage;
  }, beforeEvidence || {}, { timeout: timeoutMs }).catch(() => null);
  const after = await promptSideEffectSnapshot();
  const beforeRuns = Array.isArray(beforeEvidence?.runIds) ? beforeEvidence.runIds : [];
  const beforeTraces = Array.isArray(beforeEvidence?.traceIds) ? beforeEvidence.traceIds : [];
  const newRunIds = after.runIds.filter((id) => !beforeRuns.includes(id));
  const newTraceIds = after.traceIds.filter((id) => !beforeTraces.includes(id));
  const messageCountDelta = Math.max(0, Number(after.messageCount || 0) - Number(beforeEvidence?.messageCount || 0));
  return { ...after, newRunIds, newTraceIds, messageCountDelta, submitted: newRunIds.length > 0 || newTraceIds.length > 0 || messageCountDelta > 0, valuesRedacted: true };
}

async function promptSideEffectSnapshot() {
  return withHardTimeout(page.evaluate(() => {
    const text = document.body?.innerText || "";
    const visible = (element) => {
      const rect = element.getBoundingClientRect();
      const style = window.getComputedStyle(element);
      return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
    };
    return {
      runIds: Array.from(new Set(text.match(/run_[A-Za-z0-9_:-]+/gu) || [])).slice(-20),
      traceIds: Array.from(new Set(text.match(/trc_[A-Za-z0-9_:-]+/gu) || [])).slice(-20),
      running: /Trace running|最近\s*\d+\s*(?:秒|分钟|分|小时)前|Code Agent\s*耗时/iu.test(text),
      executionError: /AgentRun error|agentrun:error:|provider-stream-disconnected|provider-unavailable/iu.test(text),
      messageCount: Array.from(document.querySelectorAll('article.message-card, .message-card[data-message-id], article[data-message-id]')).filter(visible).length,
      textBytes: new TextEncoder().encode(text).length,
      valuesRedacted: true
    };
  }), 3000, "prompt side-effect snapshot exceeded 3000ms")
    .catch(() => ({ runIds: [], traceIds: [], running: false, executionError: false, messageCount: 0, textBytes: 0, valuesRedacted: true }));
}
`;
}
