local-operator-ui 0.25.0 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/out/main/index.js +206 -20
  2. package/package.json +5 -2
package/out/main/index.js CHANGED
@@ -14216,6 +14216,51 @@ function registerDevDriverIPC(options) {
14216
14216
  };
14217
14217
  });
14218
14218
  }
14219
+ const LAUNCHER_MISSES_BEFORE_GONE = 2;
14220
+ function createLauncherWatch(input) {
14221
+ const threshold = input.missesBeforeGone ?? LAUNCHER_MISSES_BEFORE_GONE;
14222
+ let misses = 0;
14223
+ let fired = false;
14224
+ let stopped = false;
14225
+ return {
14226
+ poll: () => {
14227
+ if (fired || stopped) return false;
14228
+ const parent = input.parentPid();
14229
+ const reparented = parent !== input.launcherPid;
14230
+ misses = reparented || !input.isAlive(input.launcherPid) ? misses + 1 : 0;
14231
+ if (misses < threshold) return false;
14232
+ fired = true;
14233
+ input.onLauncherGone({
14234
+ message: `the process that launched this run (pid ${input.launcherPid}) has exited`,
14235
+ detail: reparented ? `pid ${input.launcherPid} is gone (this process was reparented to pid ${parent})` : `pid ${input.launcherPid} is gone`
14236
+ });
14237
+ return true;
14238
+ },
14239
+ stop: () => {
14240
+ stopped = true;
14241
+ }
14242
+ };
14243
+ }
14244
+ function startLauncherWatch(input) {
14245
+ const watch = createLauncherWatch(input);
14246
+ const timer = setInterval(() => watch.poll(), input.intervalMs);
14247
+ timer.unref?.();
14248
+ return {
14249
+ poll: watch.poll,
14250
+ stop: () => {
14251
+ clearInterval(timer);
14252
+ watch.stop();
14253
+ }
14254
+ };
14255
+ }
14256
+ function isProcessAlive(pid) {
14257
+ try {
14258
+ process.kill(pid, 0);
14259
+ return true;
14260
+ } catch (error) {
14261
+ return error.code !== "ESRCH";
14262
+ }
14263
+ }
14219
14264
  const lastPickedDirectory = /* @__PURE__ */ new Map();
14220
14265
  function withRememberedDirectory(kind, options, fallbackDirectory) {
14221
14266
  if (options.defaultPath) return options;
@@ -17108,15 +17153,35 @@ const AGENT_LAUNCH_FLAGS = [
17108
17153
  "--user-data-dir",
17109
17154
  "--remote-debugging-port"
17110
17155
  ];
17156
+ const LAUNCHER_KEEP_ALIVE_ENV = "LOCAL_OPERATOR_UI_HEADLESS_KEEP_ALIVE";
17157
+ const LAUNCHER_POLL_INTERVAL_MS = 2e3;
17158
+ const LAUNCHER_EXIT_DEADLINE_MS = 1e4;
17111
17159
  const WINDOW_MIN_WIDTH = 800;
17112
17160
  const WINDOW_MIN_HEIGHT = 600;
17113
17161
  const WINDOW_MAX_EDGE = 16384;
17114
17162
  const DEFAULT_WINDOW_WIDTH = 1380;
17115
17163
  const DEFAULT_WINDOW_HEIGHT = 900;
17116
17164
  const WINDOW_BEHAVIOUR = {
17117
- normal: { show: "focus", focusable: true, backgroundThrottling: true },
17118
- inactive: { show: "inactive", focusable: true, backgroundThrottling: false },
17119
- headless: { show: "never", focusable: false, backgroundThrottling: false }
17165
+ normal: {
17166
+ show: "focus",
17167
+ focusable: true,
17168
+ backgroundThrottling: true,
17169
+ hideDock: false
17170
+ },
17171
+ inactive: {
17172
+ show: "inactive",
17173
+ focusable: true,
17174
+ backgroundThrottling: false,
17175
+ // Visible on purpose, so it keeps its tile: a run somebody wants to
17176
+ // watch has to be findable in the Dock.
17177
+ hideDock: false
17178
+ },
17179
+ headless: {
17180
+ show: "never",
17181
+ focusable: false,
17182
+ backgroundThrottling: false,
17183
+ hideDock: true
17184
+ }
17120
17185
  };
17121
17186
  function readFlag(argv, name) {
17122
17187
  for (let index = 0; index < argv.length; index += 1) {
@@ -17154,8 +17219,11 @@ function resolveWindowLaunchPlan(input = {}) {
17154
17219
  const modeValue = modeFlag.found ? modeFlag.value : env[WINDOW_MODE_ENV];
17155
17220
  const modeRaw = typeof modeValue === "string" && modeValue.trim() === "" ? void 0 : modeValue;
17156
17221
  const parsedMode = parseWindowMode(modeRaw);
17157
- const agentFlag = !modeFlag.found && modeRaw === void 0 ? AGENT_LAUNCH_FLAGS.find((flag) => readFlag(argv, flag).found) : void 0;
17158
- const assumed = agentFlag ? `${agentFlag} marks an agent-driven launch, and no window mode was named` : null;
17222
+ const unnamed = !modeFlag.found && modeRaw === void 0;
17223
+ const agentFlag = unnamed ? AGENT_LAUNCH_FLAGS.find((flag) => readFlag(argv, flag).found) : void 0;
17224
+ const platform = input.platform ?? process.platform;
17225
+ const driven = unnamed && agentFlag === void 0 && input.packaged === false && platform !== "win32" && input.stdinIsTTY !== true && input.stdoutIsTTY !== true;
17226
+ const assumed = agentFlag ? `${agentFlag} marks an agent-driven launch, and no window mode was named` : driven ? "the launch is not a packaged app and has no terminal on either stream, so it is a driven run rather than the operator using the app" : null;
17159
17227
  if (modeFlag.found && modeFlag.value === void 0) {
17160
17228
  problems.push(
17161
17229
  `${WINDOW_MODE_FLAG} needs a value: ${WINDOW_MODES.join("|")}`
@@ -17206,10 +17274,45 @@ function resolveWindowLaunchPlan(input = {}) {
17206
17274
  problems
17207
17275
  };
17208
17276
  }
17209
- function describeWindowLaunch(plan) {
17277
+ const KEEP_ALIVE_VALUES = ["1", "true", "yes", "on"];
17278
+ function resolveLauncherWatchPlan(input) {
17279
+ const env = input.env ?? {};
17280
+ const keepAlive = (env[LAUNCHER_KEEP_ALIVE_ENV] ?? "").trim().toLowerCase();
17281
+ if (KEEP_ALIVE_VALUES.includes(keepAlive)) {
17282
+ return {
17283
+ watch: false,
17284
+ launcherPid: null,
17285
+ reason: `${LAUNCHER_KEEP_ALIVE_ENV}=${keepAlive} was set, so this run outlives its launcher`
17286
+ };
17287
+ }
17288
+ if (input.mode !== "headless") {
17289
+ return {
17290
+ watch: false,
17291
+ launcherPid: null,
17292
+ reason: `window mode ${input.mode} is not launcher-bound: a person can close it`
17293
+ };
17294
+ }
17295
+ if (!Number.isInteger(input.launcherPid) || input.launcherPid <= 1) {
17296
+ return {
17297
+ watch: false,
17298
+ launcherPid: null,
17299
+ reason: "headless run already detached (no launcher to outlive)"
17300
+ };
17301
+ }
17302
+ return {
17303
+ watch: true,
17304
+ launcherPid: input.launcherPid,
17305
+ reason: `headless run launched by pid ${input.launcherPid}; it quits when that process goes`
17306
+ };
17307
+ }
17308
+ function describeWindowLaunch(plan, platform = process.platform) {
17210
17309
  const behaviour = plan.show === "never" ? "window created and never shown" : plan.show === "inactive" ? "window shown without activating the app" : "window shown and focused";
17211
- const assumption = plan.assumed ? ` (assumed: ${plan.assumed})` : "";
17212
- return `window mode ${plan.mode}${assumption}: ${plan.width}x${plan.height}, ${behaviour}, page throttling ${plan.backgroundThrottling ? "on" : "off"}`;
17310
+ const assumption = plan.assumed ? ` (mode assumed: ${plan.assumed})` : "";
17311
+ const extras = [
17312
+ plan.hideDock && platform === "darwin" ? "no Dock tile" : null
17313
+ ].filter((part) => part !== null);
17314
+ const suffix = extras.length === 0 ? "" : `, ${extras.join(", ")}`;
17315
+ return `window mode ${plan.mode}: ${plan.width}x${plan.height}, ${behaviour}, page throttling ${plan.backgroundThrottling ? "on" : "off"}${suffix}${assumption}`;
17213
17316
  }
17214
17317
  const BASE64_FILE_EXTENSIONS = ["csv", "tsv", "xls", "xlsx", "ods"];
17215
17318
  const resolveUserPath = (filePath, cwd) => {
@@ -17220,10 +17323,68 @@ const resolveUserPath = (filePath, cwd) => {
17220
17323
  return path$1.join(cwd.startsWith("~/") ? resolveUserPath(cwd) : cwd, filePath);
17221
17324
  return filePath;
17222
17325
  };
17326
+ const windowLaunch = resolveWindowLaunchPlan({
17327
+ env: launchEnv,
17328
+ argv: process.argv,
17329
+ /*
17330
+ * The facts that let a flagless launch be recognised as a run rather than as
17331
+ * the operator's app — see the `driven` block in window-mode.ts for why the
17332
+ * shape is sound and why it cannot touch the shipped app.
17333
+ *
17334
+ * `app.isPackaged` is read here, at module load, rather than inside the
17335
+ * resolver: it describes the LAUNCH (the installed `.app` is packaged, a
17336
+ * checkout and the npm CLI are not), and the resolver stays free of Electron
17337
+ * so its rules can be tested as arithmetic instead of by booting an app.
17338
+ *
17339
+ * `process.stdin`/`process.stdout` are read as the process was STARTED with
17340
+ * them, for the same reason `launchEnv` exists: a terminal attached later
17341
+ * cannot make this a person's launch.
17342
+ */
17343
+ packaged: electron.app.isPackaged,
17344
+ stdinIsTTY: process.stdin.isTTY,
17345
+ stdoutIsTTY: process.stdout.isTTY,
17346
+ platform: process.platform
17347
+ });
17348
+ for (const problem of windowLaunch.problems) {
17349
+ logger.warn(`[window-mode] ${problem}`, LogFileType.BACKEND);
17350
+ console.log(`[window-mode] ${problem}`);
17351
+ }
17352
+ if (windowLaunch.mode !== "normal") {
17353
+ console.log(`[window-mode] ${describeWindowLaunch(windowLaunch)}`);
17354
+ }
17355
+ let headlessExitDeadline = null;
17356
+ function armHeadlessExitDeadline(why) {
17357
+ if (headlessExitDeadline) return;
17358
+ const deadline2 = setTimeout(() => {
17359
+ const line = `${why}; the quit did not finish within ${LAUNCHER_EXIT_DEADLINE_MS} ms, exiting`;
17360
+ logger.error(`[window-mode] ${line}`, LogFileType.BACKEND);
17361
+ console.log(`[window-mode] ${line}`);
17362
+ electron.app.exit(0);
17363
+ }, LAUNCHER_EXIT_DEADLINE_MS);
17364
+ deadline2.unref();
17365
+ headlessExitDeadline = deadline2;
17366
+ }
17367
+ function endHeadlessRun(why, detail) {
17368
+ logger.info(`[window-mode] ${why}; quitting`, LogFileType.BACKEND);
17369
+ console.log(`[window-mode] ${why}; quitting`);
17370
+ if (detail) {
17371
+ logger.debug(
17372
+ `[window-mode] launcher probe: ${detail}`,
17373
+ LogFileType.BACKEND
17374
+ );
17375
+ }
17376
+ armHeadlessExitDeadline(why);
17377
+ electron.app.quit();
17378
+ }
17379
+ let launcherWatch = null;
17223
17380
  electron.app.setName("Local Operator");
17224
17381
  const image = electron.nativeImage.createFromPath(icon);
17225
17382
  if (process.platform === "darwin" && electron.app.dock) {
17226
- electron.app.dock.setIcon(image);
17383
+ if (windowLaunch.hideDock) {
17384
+ electron.app.dock.hide();
17385
+ } else {
17386
+ electron.app.dock.setIcon(image);
17387
+ }
17227
17388
  }
17228
17389
  const posthogClient = new posthogNode.PostHog(backendConfig.VITE_PUBLIC_POSTHOG_KEY, {
17229
17390
  host: backendConfig.VITE_PUBLIC_POSTHOG_HOST,
@@ -17545,17 +17706,6 @@ const reportBackendFailure = (message, fileType) => {
17545
17706
  }
17546
17707
  electron.dialog.showErrorBox("Backend Error", message);
17547
17708
  };
17548
- const windowLaunch = resolveWindowLaunchPlan({
17549
- env: launchEnv,
17550
- argv: process.argv
17551
- });
17552
- for (const problem of windowLaunch.problems) {
17553
- logger.warn(`[window-mode] ${problem}`, LogFileType.BACKEND);
17554
- console.log(`[window-mode] ${problem}`);
17555
- }
17556
- if (windowLaunch.mode !== "normal") {
17557
- console.log(`[window-mode] ${describeWindowLaunch(windowLaunch)}`);
17558
- }
17559
17709
  const devDriverArming = resolveDevDriverArming({
17560
17710
  env: launchEnv,
17561
17711
  windowMode: windowLaunch.mode
@@ -17656,6 +17806,29 @@ electron.app.whenReady().then(async () => {
17656
17806
  electron.app.exit(0);
17657
17807
  return;
17658
17808
  }
17809
+ const launcher = resolveLauncherWatchPlan({
17810
+ mode: windowLaunch.mode,
17811
+ launcherPid: process.ppid,
17812
+ env: launchEnv
17813
+ });
17814
+ logger.info(`[window-mode] ${launcher.reason}`, LogFileType.BACKEND);
17815
+ if (windowLaunch.mode !== "normal") {
17816
+ console.log(`[window-mode] ${launcher.reason}`);
17817
+ }
17818
+ if (launcher.watch && launcher.launcherPid !== null) {
17819
+ launcherWatch = startLauncherWatch({
17820
+ launcherPid: launcher.launcherPid,
17821
+ intervalMs: LAUNCHER_POLL_INTERVAL_MS,
17822
+ isAlive: isProcessAlive,
17823
+ parentPid: () => process.ppid,
17824
+ onLauncherGone: ({ message, detail }) => endHeadlessRun(message, detail)
17825
+ });
17826
+ }
17827
+ if (windowLaunch.mode === "headless") {
17828
+ for (const signal of ["SIGTERM", "SIGINT"]) {
17829
+ process.on(signal, () => endHeadlessRun(`received ${signal}`));
17830
+ }
17831
+ }
17659
17832
  electron.app.on("browser-window-created", (_, window) => {
17660
17833
  utils.optimizer.watchWindowShortcuts(window);
17661
17834
  });
@@ -18255,6 +18428,15 @@ electron.app.whenReady().then(async () => {
18255
18428
  electron.app.quit();
18256
18429
  });
18257
18430
  electron.app.on("window-all-closed", () => {
18431
+ if (windowLaunch.mode === "headless") {
18432
+ logger.info(
18433
+ "[window-mode] all windows closed in a headless run; quitting",
18434
+ LogFileType.BACKEND
18435
+ );
18436
+ console.log("[window-mode] all windows closed in a headless run; quitting");
18437
+ electron.app.quit();
18438
+ return;
18439
+ }
18258
18440
  if (process.platform === "darwin") {
18259
18441
  if (activeUpdateService?.quitForInFlightInstall("last window closed")) {
18260
18442
  electron.app.quit();
@@ -18319,6 +18501,10 @@ const holdQuitForSessionCookieSnapshot = createSessionCookieQuitHold({
18319
18501
  electron.app.on("before-quit", async (event) => {
18320
18502
  if (await holdQuitForSessionCookieSnapshot(event)) return;
18321
18503
  logger.info("App is about to quit", LogFileType.BACKEND);
18504
+ if (windowLaunch.mode === "headless") {
18505
+ launcherWatch?.stop();
18506
+ armHeadlessExitDeadline("the app is quitting");
18507
+ }
18322
18508
  electron.globalShortcut.unregisterAll();
18323
18509
  activeUpdateService?.quitForInFlightInstall("app quit");
18324
18510
  void stopBrowserHost();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-operator-ui",
3
- "version": "0.25.0",
3
+ "version": "0.25.2",
4
4
  "productName": "Local Operator",
5
5
  "description": "User interface for AI agent assistants that run Python code safely on your device through an intuitive chat interface. Supports local and cloud LLM models with built-in security features.",
6
6
  "main": "./out/main/index.js",
@@ -64,6 +64,8 @@
64
64
  "postinstall": "node ./bin/postinstall.js",
65
65
  "lint": "pnpm biome check src bin scripts/linux-sandbox.test.mjs",
66
66
  "lint:fix": "pnpm biome check --write src bin scripts/linux-sandbox.test.mjs",
67
+ "//lint:scripts": "`pnpm lint` names the paths it checks by hand, so the whole of `scripts/` - the proof harnesses, evidence rigs and release gates - sat outside the lint and formatter contracts, and a formatter error rode into a merged pull request green. This gate runs the same `biome check` over the `scripts/` files a change touches against a base ref (`origin/main` locally, the merge commit's first parent in CI), so a violation cannot ride in with the diff that introduces it, while the tree's 106 pre-existing offenders are burnt down file by file rather than turned red at once. Widening `pnpm lint` to name `scripts/` outright is the follow-up once that backlog is gone; scripts/check-scripts-lint.mjs holds the measurements and the full rationale.",
68
+ "lint:scripts": "node scripts/check-scripts-lint.mjs",
67
69
  "format": "pnpm biome format src",
68
70
  "format:fix": "pnpm biome format --write src",
69
71
  "prepack": "pnpm run build:npm",
@@ -71,7 +73,8 @@
71
73
  "check-types:renderer": "tsc --noEmit -p tsconfig.app.json",
72
74
  "check-types": "pnpm run check-types:main && pnpm run check-types:renderer",
73
75
  "test:session-cookies": "node scripts/session-cookie-electron.test.mjs",
74
- "test:desktop": "node scripts/run-desktop-tests.mjs scripts/slash-token.test.mjs scripts/slash-rank.test.mjs scripts/slash-submit.test.mjs scripts/slash-contract.test.mjs scripts/slash-row-format.test.mjs scripts/owned-serve-lifecycle.test.mjs scripts/linux-sandbox.test.mjs scripts/capture-evidence.test.mjs scripts/desktop-contract.test.mjs scripts/transcript-reducer.test.mjs scripts/transcript-paging.test.mjs scripts/provider-state.test.mjs scripts/stale-agent.test.mjs scripts/desktop-renderer-transport.test.mjs scripts/desktop-proxy.test.mjs scripts/session-stream-token.test.mjs scripts/session-load-recovery.test.mjs scripts/provider-grid-error-copy.test.mjs scripts/settings-time-to-error.test.mjs scripts/backend-error-surfaces.test.mjs scripts/canonical-chat.test.mjs scripts/stale-caption.test.mjs scripts/desktop-watch-lease.test.mjs scripts/session-switch.test.mjs scripts/chat-search.test.mjs scripts/palette-search.test.mjs scripts/palette-shortcut.test.mjs scripts/palette-contract.test.mjs scripts/palette-panel-request.test.mjs scripts/run-detail-model.test.mjs scripts/subagent-pulse.test.mjs scripts/chat-title.test.mjs scripts/clear-search.test.mjs scripts/tool-row.test.mjs scripts/attachment-url.test.mjs scripts/desktop-notifier.test.mjs scripts/launch-session.test.mjs scripts/desktop-feed.test.mjs scripts/viewer-record.test.mjs scripts/desktop-stream.test.mjs scripts/python-bytecode-cache.test.mjs scripts/update-affirmation.test.mjs scripts/update-robustness.test.mjs scripts/prune-python-resource.test.mjs scripts/managed-python.test.mjs scripts/usage-view.test.mjs scripts/usage-container.test.mjs scripts/session-status.test.mjs scripts/composer-readings.test.mjs scripts/composer-tabs.test.mjs scripts/ask-options.test.mjs scripts/window-mode.test.mjs scripts/evidence-manifest.test.mjs scripts/canonical-notice.test.mjs scripts/evidence-run-guard.test.mjs scripts/desktop-test-concurrency.test.mjs scripts/run-desktop-tests.test.mjs scripts/mcp-foreign-config-origin.test.mjs scripts/mentioned-files.test.mjs scripts/picker-feedback.test.mjs scripts/panel-chart-motion.test.mjs scripts/panel-info-credentials.test.mjs scripts/warm-session.test.mjs scripts/submit-latency.test.mjs scripts/completion-view-ack.test.mjs scripts/echo-delivery.test.mjs scripts/check-runtime-deps.test.mjs scripts/new-chat-row.test.mjs scripts/new-chat-row-evidence.test.mjs scripts/reconnect-page-gap.test.mjs scripts/browser-host.test.mjs scripts/check-vendored.test.mjs scripts/chat-session-status.test.mjs scripts/draft-selection.test.mjs scripts/toast-lifetime.test.mjs scripts/storybook-query-fixture.test.mjs scripts/mcp-list-query.test.mjs scripts/mcp-target-resolution.test.mjs scripts/mcp-auth-surface.test.mjs scripts/mcp-failure-routing.test.mjs scripts/daemon-discovery.test.mjs scripts/daemon-health-state.test.mjs scripts/daemon-observation.test.mjs scripts/session-cookies.test.mjs scripts/move-session.test.mjs scripts/frontend-replace.test.mjs scripts/browser-chrome.test.mjs scripts/dev-driver-gate.test.mjs scripts/logger-log-dir.test.mjs scripts/draft-splash.test.mjs scripts/suggestion-stack.test.mjs scripts/suggestion-stack-react.test.mjs scripts/notifications-off.test.mjs scripts/notification-launch.test.mjs scripts/notification-spawn-sites.test.mjs scripts/interrupt-control.test.mjs",
76
+
77
+ "test:desktop": "node scripts/run-desktop-tests.mjs scripts/slash-token.test.mjs scripts/slash-rank.test.mjs scripts/slash-submit.test.mjs scripts/slash-contract.test.mjs scripts/slash-row-format.test.mjs scripts/owned-serve-lifecycle.test.mjs scripts/launcher-watch.test.mjs scripts/linux-sandbox.test.mjs scripts/capture-evidence.test.mjs scripts/desktop-contract.test.mjs scripts/transcript-reducer.test.mjs scripts/transcript-paging.test.mjs scripts/provider-state.test.mjs scripts/stale-agent.test.mjs scripts/desktop-renderer-transport.test.mjs scripts/desktop-proxy.test.mjs scripts/session-stream-token.test.mjs scripts/session-load-recovery.test.mjs scripts/provider-grid-error-copy.test.mjs scripts/settings-time-to-error.test.mjs scripts/backend-error-surfaces.test.mjs scripts/canonical-chat.test.mjs scripts/stale-caption.test.mjs scripts/desktop-watch-lease.test.mjs scripts/session-switch.test.mjs scripts/chat-search.test.mjs scripts/palette-search.test.mjs scripts/palette-shortcut.test.mjs scripts/palette-contract.test.mjs scripts/palette-panel-request.test.mjs scripts/run-detail-model.test.mjs scripts/subagent-pulse.test.mjs scripts/chat-title.test.mjs scripts/clear-search.test.mjs scripts/tool-row.test.mjs scripts/attachment-url.test.mjs scripts/desktop-notifier.test.mjs scripts/launch-session.test.mjs scripts/desktop-feed.test.mjs scripts/viewer-record.test.mjs scripts/desktop-stream.test.mjs scripts/python-bytecode-cache.test.mjs scripts/update-affirmation.test.mjs scripts/update-robustness.test.mjs scripts/prune-python-resource.test.mjs scripts/managed-python.test.mjs scripts/usage-view.test.mjs scripts/usage-container.test.mjs scripts/session-status.test.mjs scripts/composer-readings.test.mjs scripts/composer-tabs.test.mjs scripts/ask-options.test.mjs scripts/window-mode.test.mjs scripts/evidence-manifest.test.mjs scripts/canonical-notice.test.mjs scripts/evidence-run-guard.test.mjs scripts/desktop-test-concurrency.test.mjs scripts/run-desktop-tests.test.mjs scripts/mcp-foreign-config-origin.test.mjs scripts/mentioned-files.test.mjs scripts/picker-feedback.test.mjs scripts/panel-chart-motion.test.mjs scripts/panel-info-credentials.test.mjs scripts/warm-session.test.mjs scripts/submit-latency.test.mjs scripts/completion-view-ack.test.mjs scripts/echo-delivery.test.mjs scripts/check-runtime-deps.test.mjs scripts/new-chat-row.test.mjs scripts/new-chat-row-evidence.test.mjs scripts/reconnect-page-gap.test.mjs scripts/browser-host.test.mjs scripts/check-vendored.test.mjs scripts/chat-session-status.test.mjs scripts/draft-selection.test.mjs scripts/toast-lifetime.test.mjs scripts/storybook-query-fixture.test.mjs scripts/mcp-list-query.test.mjs scripts/mcp-target-resolution.test.mjs scripts/mcp-auth-surface.test.mjs scripts/mcp-failure-routing.test.mjs scripts/daemon-discovery.test.mjs scripts/daemon-health-state.test.mjs scripts/daemon-observation.test.mjs scripts/session-cookies.test.mjs scripts/move-session.test.mjs scripts/frontend-replace.test.mjs scripts/browser-chrome.test.mjs scripts/dev-driver-gate.test.mjs scripts/logger-log-dir.test.mjs scripts/draft-splash.test.mjs scripts/suggestion-stack.test.mjs scripts/suggestion-stack-react.test.mjs scripts/notifications-off.test.mjs scripts/notification-launch.test.mjs scripts/notification-spawn-sites.test.mjs scripts/chrome-keychain.test.mjs scripts/interrupt-control.test.mjs",
75
78
  "check-themes": "node scripts/generate-theme-css.mjs --check && node scripts/contrast-contract.mjs",
76
79
  "check-vendored": "node scripts/check-vendored.mjs",
77
80
  "check-evidence": "node scripts/check-evidence.mjs",