perplexity-user-mcp 0.8.44 → 0.8.48

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.
@@ -1 +1,11 @@
1
+ /**
2
+ * Chromium launch args for the headed login / cookie-grab window.
3
+ *
4
+ * @param {boolean} localOrigin — true when the login target is a local mock
5
+ * origin (127.0.0.1 / localhost). Those runs are already headless with no
6
+ * window, so no positioning is needed.
7
+ * @returns {string[]}
8
+ */
9
+ export function loginLaunchArgs(localOrigin: boolean): string[];
1
10
  export function minimizePageWindow(page: any): Promise<boolean>;
11
+ export const OFFSCREEN_POSITION_ARG: "--window-position=-32000,-32000";
@@ -1,3 +1,20 @@
1
+ /**
2
+ * True when a LIVE daemon owns the SAME profile's browser session the probe
3
+ * would launch against. The daemon holds the `browser-data` profile via a
4
+ * long-lived launchPersistentContext; an independent probe launching its own
5
+ * context on the SAME --user-data-dir collides on the Chromium singleton lock
6
+ * (issue #8 — Windows exitCode 21). When this returns true the probe must NOT
7
+ * launch its own browser; the daemon's real auth state is already surfaced by
8
+ * the `profiles` check (daemon-status.json).
9
+ *
10
+ * The daemon.lock is global (one daemon) and records no profile, but the
11
+ * daemon follows the active-profile pointer — it only holds the lock for the
12
+ * ACTIVE profile. The probe's PerplexityClient launches against
13
+ * `PERPLEXITY_PROFILE || active`, so a probe explicitly targeting a DIFFERENT
14
+ * profile uses a different browser-data dir and will NOT collide. Guard only
15
+ * when the probe's effective profile matches the active profile.
16
+ */
17
+ export function liveDaemonOwnsProfile(): boolean;
1
18
  export function run(opts?: {}): Promise<{
2
19
  category: string;
3
20
  name: string;
@@ -1,7 +1,25 @@
1
+ import {
2
+ isStale,
3
+ read
4
+ } from "../chunk-NMKNEEZB.mjs";
5
+ import {
6
+ getActiveName
7
+ } from "../chunk-E3GRJXXJ.mjs";
1
8
  import "../chunk-4UEJOM6W.mjs";
2
9
 
3
10
  // src/checks/probe.js
4
11
  var CATEGORY = "probe";
12
+ function liveDaemonOwnsProfile() {
13
+ try {
14
+ const record = read();
15
+ if (!record || isStale(record)) return false;
16
+ const active = getActiveName() ?? "default";
17
+ const probeProfile = process.env.PERPLEXITY_PROFILE ?? active;
18
+ return probeProfile === active;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
5
23
  async function defaultSearch({ timeoutMs }) {
6
24
  const { PerplexityClient } = await import("../client.mjs");
7
25
  const client = new PerplexityClient();
@@ -38,6 +56,16 @@ async function run(opts = {}) {
38
56
  if (!opts.probe) {
39
57
  return [{ category: CATEGORY, name: "probe-search", status: "skip", message: "skipped (pass --probe to enable)" }];
40
58
  }
59
+ const daemonOwns = opts.daemonOwnsOverride ?? liveDaemonOwnsProfile;
60
+ if (daemonOwns()) {
61
+ return [{
62
+ category: CATEGORY,
63
+ name: "probe-search",
64
+ status: "skip",
65
+ message: "skipped \u2014 a live daemon owns this profile's browser session",
66
+ hint: "An independent probe would collide with the daemon on the browser-data profile lock (issue #8). The daemon's live auth state is shown under the 'profiles' check (daemon-status). To run a standalone probe, stop the daemon first, then re-run with --probe."
67
+ }];
68
+ }
41
69
  const timeoutMs = opts.timeoutMs ?? 1e4;
42
70
  const search = opts.searchOverride ?? defaultSearch;
43
71
  try {
@@ -78,5 +106,6 @@ async function run(opts = {}) {
78
106
  }
79
107
  }
80
108
  export {
109
+ liveDaemonOwnsProfile,
81
110
  run
82
111
  };
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  getPackageVersion,
15
15
  startDaemonServer
16
- } from "./chunk-2B5OQUXR.mjs";
16
+ } from "./chunk-P6YOLJ5T.mjs";
17
17
  import {
18
18
  ensureToken,
19
19
  getTokenPath,
@@ -25,7 +25,7 @@ import {
25
25
  } from "./chunk-3LUO5ATM.mjs";
26
26
  import {
27
27
  PerplexityClient
28
- } from "./chunk-WHVJ724K.mjs";
28
+ } from "./chunk-GBHPJ7I7.mjs";
29
29
  import {
30
30
  getActiveName,
31
31
  getConfigDir
@@ -1,3 +1,6 @@
1
+ import {
2
+ OFFSCREEN_POSITION_ARG
3
+ } from "./chunk-KVV3JBSN.mjs";
1
4
  import {
2
5
  impitFetchJson,
3
6
  isImpitAvailable
@@ -36,7 +39,7 @@ import { join as join2 } from "path";
36
39
  // src/fs-utils.js
37
40
  import { unlinkSync } from "fs";
38
41
  import { join } from "path";
39
- var SINGLETON_FILES = ["SingletonLock", "SingletonCookie", "SingletonSocket"];
42
+ var SINGLETON_FILES = ["SingletonLock", "SingletonCookie", "SingletonSocket", "lockfile"];
40
43
  function clearStaleSingletonLocks(dir) {
41
44
  for (const name of SINGLETON_FILES) {
42
45
  try {
@@ -48,6 +51,42 @@ function clearStaleSingletonLocks(dir) {
48
51
  }
49
52
  }
50
53
  }
54
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
55
+ var LOCK_CONTENTION_RE = /target page, context or browser has been closed|processsingleton|profile.*(in use|already)|singletonlock|already running|exit(ed)?(?:\s+(?:with\s+)?code)?\s*21\b/i;
56
+ function isLockContentionError(err) {
57
+ if (!err) return false;
58
+ const msg = typeof err === "string" ? err : err.message ?? String(err);
59
+ return LOCK_CONTENTION_RE.test(msg);
60
+ }
61
+ async function launchWithRetry(launch, opts = {}) {
62
+ const {
63
+ retries = 3,
64
+ baseDelayMs = 200,
65
+ sleep = defaultSleep,
66
+ isRetriable = isLockContentionError,
67
+ beforeAttempt
68
+ } = opts;
69
+ let lastErr;
70
+ for (let attempt = 0; attempt <= retries; attempt++) {
71
+ if (beforeAttempt) {
72
+ try {
73
+ beforeAttempt(attempt);
74
+ } catch {
75
+ }
76
+ }
77
+ try {
78
+ return await launch(attempt);
79
+ } catch (err) {
80
+ lastErr = err;
81
+ if (attempt === retries || !isRetriable(err)) throw err;
82
+ console.error(
83
+ `[perplexity-mcp] Browser launch hit a profile-lock collision (attempt ${attempt + 1}/${retries + 1}); retrying after backoff.`
84
+ );
85
+ await sleep(baseDelayMs * 2 ** attempt);
86
+ }
87
+ }
88
+ throw lastErr;
89
+ }
51
90
 
52
91
  // src/client.ts
53
92
  function getActiveProfileName() {
@@ -102,7 +141,13 @@ function buildLaunchOptions(headless) {
102
141
  const browser = findBrowser();
103
142
  const opts = {
104
143
  headless,
105
- args: STEALTH_ARGS,
144
+ // The headed branch is the CF-solving bootstrap (Phase 1 / daemon session
145
+ // refresh). It paints a real window, which the user sees flash while they
146
+ // work (issue #9). Position it off-screen so the background refresh is
147
+ // invisible — like the already-headless search path. Headless launches
148
+ // have no window, so the positioning arg is omitted there. We move the
149
+ // window rather than shrink/hide it; see OFFSCREEN_POSITION_ARG.
150
+ args: headless ? STEALTH_ARGS : [...STEALTH_ARGS, OFFSCREEN_POSITION_ARG],
106
151
  viewport: headless ? { width: 1920, height: 1080 } : { width: 800, height: 600 },
107
152
  userAgent: USER_AGENT,
108
153
  // Strip --enable-automation (Playwright default) which is a CF red flag
@@ -574,9 +619,9 @@ var PerplexityClient = class _PerplexityClient {
574
619
  }
575
620
  console.error("[perplexity-mcp] Launching headless persistent browser...");
576
621
  const launchOpts = buildLaunchOptions(true);
577
- this.context = await chromium.launchPersistentContext(
578
- activePaths.browserData,
579
- launchOpts
622
+ this.context = await launchWithRetry(
623
+ () => chromium.launchPersistentContext(activePaths.browserData, launchOpts),
624
+ { beforeAttempt: () => clearStaleSingletonLocks(activePaths.browserData) }
580
625
  );
581
626
  this.browser = this.context.browser();
582
627
  const saved = await getSavedCookies();
@@ -619,8 +664,10 @@ var PerplexityClient = class _PerplexityClient {
619
664
  let ctx = null;
620
665
  try {
621
666
  const browserData = getActivePaths().browserData;
622
- clearStaleSingletonLocks(browserData);
623
- ctx = await chromium.launchPersistentContext(browserData, buildLaunchOptions(false));
667
+ ctx = await launchWithRetry(
668
+ () => chromium.launchPersistentContext(browserData, buildLaunchOptions(false)),
669
+ { beforeAttempt: () => clearStaleSingletonLocks(browserData) }
670
+ );
624
671
  const page = ctx.pages()[0] || await ctx.newPage();
625
672
  await page.goto(PERPLEXITY_URL, { waitUntil: "domcontentloaded", timeout: 3e4 });
626
673
  let cfResolved = false;
@@ -1980,6 +2027,7 @@ View at: ${PERPLEXITY_URL}/search/${threadSlug}`,
1980
2027
 
1981
2028
  export {
1982
2029
  readCachedAccountInfoFromDisk,
2030
+ buildLaunchOptions,
1983
2031
  listCloudThreadsViaImpit,
1984
2032
  getCloudThreadViaImpit,
1985
2033
  isExperimentalImpitSearchEnabled,
@@ -0,0 +1,32 @@
1
+ // src/browser-window.js
2
+ var OFFSCREEN_POSITION_ARG = "--window-position=-32000,-32000";
3
+ function loginLaunchArgs(localOrigin) {
4
+ if (localOrigin) return [];
5
+ return ["--start-minimized", OFFSCREEN_POSITION_ARG];
6
+ }
7
+ async function minimizePageWindow(page) {
8
+ try {
9
+ const context = page?.context?.();
10
+ if (!context || typeof context.newCDPSession !== "function") return false;
11
+ const session = await context.newCDPSession(page);
12
+ try {
13
+ const { windowId } = await session.send("Browser.getWindowForTarget");
14
+ await session.send("Browser.setWindowBounds", {
15
+ windowId,
16
+ bounds: { windowState: "minimized" }
17
+ });
18
+ return true;
19
+ } finally {
20
+ await session.detach().catch(() => {
21
+ });
22
+ }
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ export {
29
+ OFFSCREEN_POSITION_ARG,
30
+ loginLaunchArgs,
31
+ minimizePageWindow
32
+ };
@@ -11,13 +11,13 @@ import {
11
11
  import {
12
12
  hydrateCloudHistoryEntry,
13
13
  syncCloudHistory
14
- } from "./chunk-2EE7MNP2.mjs";
14
+ } from "./chunk-YD25G5AD.mjs";
15
15
  import {
16
16
  PerplexityClient,
17
17
  exportThreadViaImpit,
18
18
  readCachedAccountInfoFromDisk,
19
19
  retrieveThreadViaImpit
20
- } from "./chunk-WHVJ724K.mjs";
20
+ } from "./chunk-GBHPJ7I7.mjs";
21
21
  import {
22
22
  append,
23
23
  findPendingByThread,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ensureDaemon
3
- } from "./chunk-E75J42W5.mjs";
3
+ } from "./chunk-D2ZQGKHM.mjs";
4
4
  import {
5
5
  getPackageVersion
6
- } from "./chunk-2B5OQUXR.mjs";
6
+ } from "./chunk-P6YOLJ5T.mjs";
7
7
 
8
8
  // src/daemon/client-http.ts
9
9
  import { randomUUID } from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ensureDaemon
3
- } from "./chunk-E75J42W5.mjs";
3
+ } from "./chunk-D2ZQGKHM.mjs";
4
4
 
5
5
  // src/daemon/attach.ts
6
6
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -2,7 +2,7 @@ import {
2
2
  PerplexityClient,
3
3
  getCloudThreadViaImpit,
4
4
  listCloudThreadsViaImpit
5
- } from "./chunk-WHVJ724K.mjs";
5
+ } from "./chunk-GBHPJ7I7.mjs";
6
6
  import {
7
7
  hydrateCloudEntry,
8
8
  upsertFromCloud
package/dist/client.d.ts CHANGED
@@ -13,6 +13,18 @@ import { type SearchResult, type AccountInfo } from "./config.js";
13
13
  * (refresh.ts) and by login-runner.js after a fresh login.
14
14
  */
15
15
  export declare function readCachedAccountInfoFromDisk(): AccountInfo | null;
16
+ /**
17
+ * Build launch options for Playwright persistent context.
18
+ *
19
+ * Uses the first available system browser (Chrome > Edge > Chromium > Brave)
20
+ * for best Cloudflare fingerprinting, falling back to patchright's bundled
21
+ * Chromium. The resolved `channel` is passed through so Patchright can apply
22
+ * channel-specific stealth tweaks (important for msedge on Windows).
23
+ *
24
+ * @internal Exported only so unit tests can assert the headed/headless arg
25
+ * branches; not part of the supported `perplexity-user-mcp/client` API.
26
+ */
27
+ export declare function buildLaunchOptions(headless: boolean): Record<string, any>;
16
28
  export interface ListAskThreadsItem {
17
29
  backendUuid: string;
18
30
  contextUuid: string;
package/dist/client.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  PerplexityClient,
3
+ buildLaunchOptions,
3
4
  exportThreadViaImpit,
4
5
  getCloudThreadViaImpit,
5
6
  isExperimentalImpitSearchEnabled,
@@ -7,7 +8,8 @@ import {
7
8
  readCachedAccountInfoFromDisk,
8
9
  retrieveThreadViaImpit,
9
10
  searchPerplexityViaImpit
10
- } from "./chunk-WHVJ724K.mjs";
11
+ } from "./chunk-GBHPJ7I7.mjs";
12
+ import "./chunk-KVV3JBSN.mjs";
11
13
  import "./chunk-V4LHDNWJ.mjs";
12
14
  import "./chunk-GBI2U336.mjs";
13
15
  import "./chunk-DXR6EEZH.mjs";
@@ -17,6 +19,7 @@ import "./chunk-E3GRJXXJ.mjs";
17
19
  import "./chunk-4UEJOM6W.mjs";
18
20
  export {
19
21
  PerplexityClient,
22
+ buildLaunchOptions,
20
23
  exportThreadViaImpit,
21
24
  getCloudThreadViaImpit,
22
25
  isExperimentalImpitSearchEnabled,
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  hydrateCloudHistoryEntry,
3
3
  syncCloudHistory
4
- } from "./chunk-2EE7MNP2.mjs";
5
- import "./chunk-WHVJ724K.mjs";
4
+ } from "./chunk-YD25G5AD.mjs";
5
+ import "./chunk-GBHPJ7I7.mjs";
6
+ import "./chunk-KVV3JBSN.mjs";
6
7
  import "./chunk-V4LHDNWJ.mjs";
7
8
  import "./chunk-6E6XTHTG.mjs";
8
9
  import "./chunk-GBI2U336.mjs";
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  DaemonAttachError,
3
3
  attachToDaemon
4
- } from "../chunk-2OVLCZHU.mjs";
5
- import "../chunk-E75J42W5.mjs";
4
+ } from "../chunk-SCZQCV7M.mjs";
5
+ import "../chunk-D2ZQGKHM.mjs";
6
6
  import "../chunk-LGH5BSUY.mjs";
7
7
  import "../chunk-6YMQVLFX.mjs";
8
8
  import "../chunk-TIWHN4IW.mjs";
9
9
  import "../chunk-NMKNEEZB.mjs";
10
- import "../chunk-2B5OQUXR.mjs";
10
+ import "../chunk-P6YOLJ5T.mjs";
11
11
  import "../chunk-TSLRTZYR.mjs";
12
12
  import "../chunk-DKEJZ4FI.mjs";
13
13
  import "../chunk-3LUO5ATM.mjs";
14
- import "../chunk-2EE7MNP2.mjs";
15
- import "../chunk-WHVJ724K.mjs";
14
+ import "../chunk-YD25G5AD.mjs";
15
+ import "../chunk-GBHPJ7I7.mjs";
16
+ import "../chunk-KVV3JBSN.mjs";
16
17
  import "../chunk-V4LHDNWJ.mjs";
17
18
  import "../chunk-6E6XTHTG.mjs";
18
19
  import "../chunk-GBI2U336.mjs";
@@ -2,18 +2,19 @@ import {
2
2
  exportHistoryViaDaemon,
3
3
  hydrateCloudHistoryEntryViaDaemon,
4
4
  syncCloudHistoryViaDaemon
5
- } from "../chunk-FNHYUE22.mjs";
6
- import "../chunk-E75J42W5.mjs";
5
+ } from "../chunk-QXYMYCHC.mjs";
6
+ import "../chunk-D2ZQGKHM.mjs";
7
7
  import "../chunk-LGH5BSUY.mjs";
8
8
  import "../chunk-6YMQVLFX.mjs";
9
9
  import "../chunk-TIWHN4IW.mjs";
10
10
  import "../chunk-NMKNEEZB.mjs";
11
- import "../chunk-2B5OQUXR.mjs";
11
+ import "../chunk-P6YOLJ5T.mjs";
12
12
  import "../chunk-TSLRTZYR.mjs";
13
13
  import "../chunk-DKEJZ4FI.mjs";
14
14
  import "../chunk-3LUO5ATM.mjs";
15
- import "../chunk-2EE7MNP2.mjs";
16
- import "../chunk-WHVJ724K.mjs";
15
+ import "../chunk-YD25G5AD.mjs";
16
+ import "../chunk-GBHPJ7I7.mjs";
17
+ import "../chunk-KVV3JBSN.mjs";
17
18
  import "../chunk-V4LHDNWJ.mjs";
18
19
  import "../chunk-6E6XTHTG.mjs";
19
20
  import "../chunk-GBI2U336.mjs";
@@ -2,10 +2,10 @@ import {
2
2
  exportHistoryViaDaemon,
3
3
  hydrateCloudHistoryEntryViaDaemon,
4
4
  syncCloudHistoryViaDaemon
5
- } from "../chunk-FNHYUE22.mjs";
5
+ } from "../chunk-QXYMYCHC.mjs";
6
6
  import {
7
7
  attachToDaemon
8
- } from "../chunk-2OVLCZHU.mjs";
8
+ } from "../chunk-SCZQCV7M.mjs";
9
9
  import {
10
10
  disableDaemonTunnel,
11
11
  enableDaemonTunnel,
@@ -21,7 +21,7 @@ import {
21
21
  rotateDaemonToken,
22
22
  startDaemon,
23
23
  stopDaemon
24
- } from "../chunk-E75J42W5.mjs";
24
+ } from "../chunk-D2ZQGKHM.mjs";
25
25
  import "../chunk-LGH5BSUY.mjs";
26
26
  import {
27
27
  extractTunnelUrl,
@@ -43,7 +43,7 @@ import {
43
43
  } from "../chunk-NMKNEEZB.mjs";
44
44
  import {
45
45
  startDaemonServer
46
- } from "../chunk-2B5OQUXR.mjs";
46
+ } from "../chunk-P6YOLJ5T.mjs";
47
47
  import {
48
48
  appendAuditEntry,
49
49
  getAuditLogPath,
@@ -57,8 +57,9 @@ import {
57
57
  rotateToken
58
58
  } from "../chunk-DKEJZ4FI.mjs";
59
59
  import "../chunk-3LUO5ATM.mjs";
60
- import "../chunk-2EE7MNP2.mjs";
61
- import "../chunk-WHVJ724K.mjs";
60
+ import "../chunk-YD25G5AD.mjs";
61
+ import "../chunk-GBHPJ7I7.mjs";
62
+ import "../chunk-KVV3JBSN.mjs";
62
63
  import "../chunk-V4LHDNWJ.mjs";
63
64
  import "../chunk-6E6XTHTG.mjs";
64
65
  import "../chunk-GBI2U336.mjs";
@@ -13,17 +13,18 @@ import {
13
13
  rotateDaemonToken,
14
14
  startDaemon,
15
15
  stopDaemon
16
- } from "../chunk-E75J42W5.mjs";
16
+ } from "../chunk-D2ZQGKHM.mjs";
17
17
  import "../chunk-LGH5BSUY.mjs";
18
18
  import "../chunk-6YMQVLFX.mjs";
19
19
  import "../chunk-TIWHN4IW.mjs";
20
20
  import "../chunk-NMKNEEZB.mjs";
21
- import "../chunk-2B5OQUXR.mjs";
21
+ import "../chunk-P6YOLJ5T.mjs";
22
22
  import "../chunk-TSLRTZYR.mjs";
23
23
  import "../chunk-DKEJZ4FI.mjs";
24
24
  import "../chunk-3LUO5ATM.mjs";
25
- import "../chunk-2EE7MNP2.mjs";
26
- import "../chunk-WHVJ724K.mjs";
25
+ import "../chunk-YD25G5AD.mjs";
26
+ import "../chunk-GBHPJ7I7.mjs";
27
+ import "../chunk-KVV3JBSN.mjs";
27
28
  import "../chunk-V4LHDNWJ.mjs";
28
29
  import "../chunk-6E6XTHTG.mjs";
29
30
  import "../chunk-GBI2U336.mjs";
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  resolveRequestResource,
3
3
  startDaemonServer
4
- } from "../chunk-2B5OQUXR.mjs";
4
+ } from "../chunk-P6YOLJ5T.mjs";
5
5
  import "../chunk-TSLRTZYR.mjs";
6
6
  import "../chunk-DKEJZ4FI.mjs";
7
- import "../chunk-2EE7MNP2.mjs";
8
- import "../chunk-WHVJ724K.mjs";
7
+ import "../chunk-YD25G5AD.mjs";
8
+ import "../chunk-GBHPJ7I7.mjs";
9
+ import "../chunk-KVV3JBSN.mjs";
9
10
  import "../chunk-V4LHDNWJ.mjs";
10
11
  import "../chunk-6E6XTHTG.mjs";
11
12
  import "../chunk-GBI2U336.mjs";
package/dist/doctor.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  formatReportMarkdown,
5
5
  rollupStatus,
6
6
  runAll
7
- } from "./chunk-KSNV3ZVY.mjs";
7
+ } from "./chunk-6CAXNBDD.mjs";
8
8
  import "./chunk-E3GRJXXJ.mjs";
9
9
  import "./chunk-4UEJOM6W.mjs";
10
10
  export {
@@ -1,8 +1,44 @@
1
1
  /**
2
- * Remove stale Chromium SingletonLock/Cookie/Socket files from a persistent
3
- * user-data-dir. Chromium silently exits with code 0 when these files claim
4
- * an active instance, so a stale lock from an unclean previous exit will
5
- * break `launchPersistentContext`. The files are recreated on every launch
6
- * and only carry exclusivity (not state), so deleting them pre-launch is safe.
2
+ * Remove stale Chromium singleton / profile-lock files from a persistent
3
+ * user-data-dir. Chromium refuses to start (POSIX: silent exit 0; Windows:
4
+ * exit 21) when these files claim an active instance, so a stale lock from an
5
+ * unclean previous exit breaks `launchPersistentContext`. The files are
6
+ * recreated on every launch and only carry exclusivity (not state), so
7
+ * deleting them pre-launch is safe. On Windows a file still held open by a
8
+ * LIVE process cannot be unlinked (EPERM/EACCES) — that throw is swallowed
9
+ * below, and the caller's retry handles the brief post-close release lag.
7
10
  */
8
11
  export function clearStaleSingletonLocks(dir: any): void;
12
+ /**
13
+ * True when `err` looks like a transient Chromium profile-lock collision that
14
+ * is worth retrying after clearing stale locks (issue #8).
15
+ * @param {unknown} err
16
+ * @returns {boolean}
17
+ */
18
+ export function isLockContentionError(err: unknown): boolean;
19
+ /**
20
+ * Run `launch` with bounded retry + exponential backoff, retrying ONLY
21
+ * transient lock-contention failures. On Windows, closing a persistent
22
+ * context does not release the profile lock synchronously, so relaunching on
23
+ * the same --user-data-dir can momentarily collide; retrying after clearing
24
+ * stale locks recovers without a full restart (issue #8). Non-lock errors
25
+ * (missing executable, network) are rethrown immediately — no pointless waits.
26
+ *
27
+ * @template T
28
+ * @param {(attempt: number) => Promise<T> | T} launch
29
+ * @param {{
30
+ * retries?: number,
31
+ * baseDelayMs?: number,
32
+ * sleep?: (ms: number) => Promise<void>,
33
+ * isRetriable?: (err: unknown) => boolean,
34
+ * beforeAttempt?: (attempt: number) => void,
35
+ * }} [opts]
36
+ * @returns {Promise<T>}
37
+ */
38
+ export function launchWithRetry<T>(launch: (attempt: number) => Promise<T> | T, opts?: {
39
+ retries?: number;
40
+ baseDelayMs?: number;
41
+ sleep?: (ms: number) => Promise<void>;
42
+ isRetriable?: (err: unknown) => boolean;
43
+ beforeAttempt?: (attempt: number) => void;
44
+ }): Promise<T>;
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  CATEGORIES,
4
4
  formatReportMarkdown,
5
5
  runAll
6
- } from "./chunk-KSNV3ZVY.mjs";
6
+ } from "./chunk-6CAXNBDD.mjs";
7
7
  import {
8
8
  buildIssueBody,
9
9
  buildIssueUrl,
@@ -12,11 +12,11 @@ import {
12
12
  } from "./chunk-DPGMKSSA.mjs";
13
13
  import {
14
14
  attachToDaemon
15
- } from "./chunk-2OVLCZHU.mjs";
15
+ } from "./chunk-SCZQCV7M.mjs";
16
16
  import {
17
17
  ensureDaemon,
18
18
  startDaemon
19
- } from "./chunk-E75J42W5.mjs";
19
+ } from "./chunk-D2ZQGKHM.mjs";
20
20
  import "./chunk-LGH5BSUY.mjs";
21
21
  import "./chunk-6YMQVLFX.mjs";
22
22
  import "./chunk-TIWHN4IW.mjs";
@@ -35,7 +35,7 @@ import {
35
35
  registerTools,
36
36
  saveToolConfig,
37
37
  watchToolConfig
38
- } from "./chunk-2B5OQUXR.mjs";
38
+ } from "./chunk-P6YOLJ5T.mjs";
39
39
  import "./chunk-TSLRTZYR.mjs";
40
40
  import "./chunk-DKEJZ4FI.mjs";
41
41
  import {
@@ -49,10 +49,11 @@ import "./chunk-HMKLWVXB.mjs";
49
49
  import {
50
50
  hydrateCloudHistoryEntry,
51
51
  syncCloudHistory
52
- } from "./chunk-2EE7MNP2.mjs";
52
+ } from "./chunk-YD25G5AD.mjs";
53
53
  import {
54
54
  PerplexityClient
55
- } from "./chunk-WHVJ724K.mjs";
55
+ } from "./chunk-GBHPJ7I7.mjs";
56
+ import "./chunk-KVV3JBSN.mjs";
56
57
  import {
57
58
  getImpitRuntimeDir,
58
59
  getModelsCacheInfo,
@@ -6,6 +6,10 @@ import {
6
6
  import {
7
7
  redact
8
8
  } from "./chunk-HMKLWVXB.mjs";
9
+ import {
10
+ loginLaunchArgs,
11
+ minimizePageWindow
12
+ } from "./chunk-KVV3JBSN.mjs";
9
13
  import {
10
14
  resolveBrowserExecutable
11
15
  } from "./chunk-DXR6EEZH.mjs";
@@ -23,30 +27,6 @@ import "./chunk-4UEJOM6W.mjs";
23
27
  // src/login-runner.js
24
28
  import { writeFileSync, existsSync, mkdirSync } from "fs";
25
29
  import { chromium } from "patchright";
26
-
27
- // src/browser-window.js
28
- async function minimizePageWindow(page) {
29
- try {
30
- const context = page?.context?.();
31
- if (!context || typeof context.newCDPSession !== "function") return false;
32
- const session = await context.newCDPSession(page);
33
- try {
34
- const { windowId } = await session.send("Browser.getWindowForTarget");
35
- await session.send("Browser.setWindowBounds", {
36
- windowId,
37
- bounds: { windowState: "minimized" }
38
- });
39
- return true;
40
- } finally {
41
- await session.detach().catch(() => {
42
- });
43
- }
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- // src/login-runner.js
50
30
  var ORIGIN = process.env.PERPLEXITY_ORIGIN || "https://www.perplexity.ai";
51
31
  var LOGIN_PATH = process.env.PERPLEXITY_LOGIN_PATH || "/account";
52
32
  var EMAIL = process.env.PERPLEXITY_EMAIL;
@@ -104,7 +84,7 @@ async function main() {
104
84
  ignoreHTTPSErrors: true,
105
85
  ...executablePath ? { executablePath } : {},
106
86
  ...channel && ["chrome", "msedge", "chromium"].includes(channel) ? { channel } : {},
107
- args: localOrigin ? [] : ["--start-minimized"]
87
+ args: loginLaunchArgs(localOrigin)
108
88
  });
109
89
  const page = await ctx.newPage();
110
90
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perplexity-user-mcp",
3
- "version": "0.8.44",
3
+ "version": "0.8.48",
4
4
  "mcpName": "io.github.Automations-Project/perplexity-user-mcp",
5
5
  "type": "module",
6
6
  "description": "Perplexity AI MCP server — browser automation for search, reasoning, research, and compute. Not affiliated with Perplexity AI, Inc.",
File without changes