@xbrowser/cli 0.16.0 → 1.0.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 (52) hide show
  1. package/README.md +17 -26
  2. package/dist/{browser-R7B255ML.js → browser-GITRHHFO.js} +4 -1
  3. package/dist/{browser-GWBH6OJK.js → browser-R56O3CW6.js} +3 -1
  4. package/dist/{browser-I2HJZ7IP.js → browser-ZJOZB5CR.js} +4 -2
  5. package/dist/cdp-driver-BE3FOMRN.js +2803 -0
  6. package/dist/cdp-driver-TOPYJIFL.js +47 -0
  7. package/dist/chunk-2SVQTI2O.js +2794 -0
  8. package/dist/{chunk-KDYXFLAC.js → chunk-ACFE6PKF.js} +1015 -121
  9. package/dist/chunk-BBMRDUYQ.js +260 -0
  10. package/dist/chunk-CAFNSGYM.js +4834 -0
  11. package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
  12. package/dist/{chunk-RS6YYWTK.js → chunk-JPA2ZT2R.js} +140 -72
  13. package/dist/chunk-JPHCY4TC.js +260 -0
  14. package/dist/chunk-KFQGP6VL.js +33 -0
  15. package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
  16. package/dist/chunk-OZKD3W4X.js +417 -0
  17. package/dist/chunk-PPG4D2EW.js +2796 -0
  18. package/dist/{chunk-ATFTAKMN.js → chunk-Q4IGYTKR.js} +39 -7
  19. package/dist/{chunk-F3ZWFCJJ.js → chunk-QIK2I3VQ.js} +141 -72
  20. package/dist/chunk-WJRE55TN.js +83 -0
  21. package/dist/cli.js +2358 -1086
  22. package/dist/{convert-4DUWZIKH.js → convert-LB3GJTLR.js} +4 -2
  23. package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
  24. package/dist/{daemon-client-GX2UYIW4.js → daemon-client-DRCUMNHK.js} +45 -72
  25. package/dist/{daemon-client-XWSSQBEA.js → daemon-client-UZZEHHIV.js} +8 -1
  26. package/dist/daemon-main.js +3067 -1688
  27. package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
  28. package/dist/{extract-EGRXZSSK.js → extract-BSYBM4MR.js} +2 -0
  29. package/dist/{filter-OLAE26HN.js → filter-KCFO4RSV.js} +2 -0
  30. package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
  31. package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
  32. package/dist/index.d.ts +745 -148
  33. package/dist/index.js +3488 -1719
  34. package/dist/launcher-QUJ4M2VS.js +19 -0
  35. package/dist/launcher-YARP45UY.js +19 -0
  36. package/dist/{network-store-YAF5OIBH.js → network-store-XGZ25FFC.js} +1 -0
  37. package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
  38. package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
  39. package/dist/{proxy-WKGUCH2C.js → proxy-LV4BJ5RC.js} +1 -1
  40. package/dist/session-recorder-RTDGURIJ.js +8 -0
  41. package/dist/session-recorder-YI7YYM36.js +7 -0
  42. package/dist/session-replayer-GLTUICSD.js +276 -0
  43. package/dist/site-knowledge-SYC6VCDB.js +23 -0
  44. package/package.json +6 -6
  45. package/dist/chunk-2ONMTDLK.js +0 -2050
  46. package/dist/daemon-client-3IJD6X4B.js +0 -59
  47. package/dist/network-store-2S5HATEV.js +0 -194
  48. package/dist/parse-action-dsl-DRSPBALP.js +0 -72
  49. package/dist/screenshot-CWAWMXVA.js +0 -28
  50. package/dist/screenshot-MB6R7RSS.js +0 -26
  51. package/dist/session-recorder-ILSSV2UC.js +0 -6
  52. package/dist/session-recorder-XET3DNML.js +0 -7
@@ -110,6 +110,12 @@ async function ensureDaemonRunning() {
110
110
  _ensurePromise = null;
111
111
  throw new Error("Daemon not available");
112
112
  }
113
+ for (let attempt = 0; attempt < 20; attempt++) {
114
+ const ready = await fetch(`${DAEMON_BASE}/health`, { signal: AbortSignal.timeout(1e3) }).then((r) => r.ok ? r.json() : null).then((d) => d?.status === "ok").catch(() => false);
115
+ if (ready) return;
116
+ await new Promise((r) => setTimeout(r, 200));
117
+ }
118
+ throw new Error("Daemon HTTP server not ready after 4s");
113
119
  }
114
120
  async function rpcCall(method, params = {}, timeoutMs = 1e4) {
115
121
  await ensureDaemonRunning();
@@ -120,7 +126,13 @@ async function rpcCall(method, params = {}, timeoutMs = 1e4) {
120
126
  signal: AbortSignal.timeout(timeoutMs)
121
127
  });
122
128
  if (!resp.ok) {
123
- throw new Error(`Daemon error: ${resp.statusText}`);
129
+ let detail = resp.statusText;
130
+ try {
131
+ const body = await resp.json();
132
+ if (body?.error) detail = body.error;
133
+ } catch {
134
+ }
135
+ throw new Error(`Daemon error: ${detail}`);
124
136
  }
125
137
  return resp.json();
126
138
  }
@@ -157,8 +169,8 @@ async function forwardExec(command, params, session = "default", cdpEndpoint, ti
157
169
  if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
158
170
  try {
159
171
  return await rpcCall("exec", rpcParams, timeoutMs);
160
- } catch {
161
- return { success: false, data: null, message: `Daemon error: exec failed`, duration: 0 };
172
+ } catch (e) {
173
+ return { success: false, data: null, message: e.message, duration: 0 };
162
174
  }
163
175
  }
164
176
  async function forwardChain(input, session = "default", cdpEndpoint) {
@@ -166,10 +178,27 @@ async function forwardChain(input, session = "default", cdpEndpoint) {
166
178
  if (cdpEndpoint) params.cdpEndpoint = cdpEndpoint;
167
179
  try {
168
180
  return await rpcCall("chain", params, 12e4);
169
- } catch {
170
- return { success: false, steps: [], totalDuration: 0, stoppedReason: "Daemon error" };
181
+ } catch (e) {
182
+ return { success: false, steps: [], totalDuration: 0, stoppedReason: e.message };
171
183
  }
172
184
  }
185
+ async function forwardAgentObserve(session = "default", options) {
186
+ const params = { session };
187
+ if (options?.cdpEndpoint) params.cdpEndpoint = options.cdpEndpoint;
188
+ if (options?.includeHidden !== void 0) params.includeHidden = options.includeHidden;
189
+ if (options?.limit !== void 0) params.limit = options.limit;
190
+ return rpcCall("agent:observe", params, 3e4);
191
+ }
192
+ async function forwardAgentAct(session = "default", params, cdpEndpoint) {
193
+ const rpcParams = { ...params, session };
194
+ if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
195
+ return rpcCall("agent:act", rpcParams, 3e4);
196
+ }
197
+ async function forwardAgentWait(session = "default", params, cdpEndpoint, timeoutMs = 3e4) {
198
+ const rpcParams = { ...params, session };
199
+ if (cdpEndpoint) rpcParams.cdpEndpoint = cdpEndpoint;
200
+ return rpcCall("agent:wait", rpcParams, timeoutMs + 5e3);
201
+ }
173
202
  async function forwardNetworkList(sessionName, options) {
174
203
  return rpcCall("network:list", { session: sessionName, ...options }, 3e4);
175
204
  }
@@ -206,8 +235,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
206
235
  async function forwardNetworkInspect(sessionName, id) {
207
236
  return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
208
237
  }
209
- async function forwardRecordStart(session, url) {
210
- return rpcCall("record:start", { session, url }, 15e3);
238
+ async function forwardRecordStart(session, url, cdpEndpoint) {
239
+ return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
211
240
  }
212
241
  async function forwardRecordStop(session) {
213
242
  return rpcCall("record:stop", { session }, 1e4);
@@ -244,6 +273,9 @@ export {
244
273
  forwardSessionList,
245
274
  forwardExec,
246
275
  forwardChain,
276
+ forwardAgentObserve,
277
+ forwardAgentAct,
278
+ forwardAgentWait,
247
279
  forwardNetworkList,
248
280
  forwardNetworkClear,
249
281
  forwardNetworkTop,
@@ -1,9 +1,12 @@
1
+ import {
2
+ launch
3
+ } from "./chunk-PPG4D2EW.js";
4
+
1
5
  // src/browser.ts
2
6
  import { randomUUID } from "crypto";
3
7
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
4
8
  import { join } from "path";
5
9
  import { homedir } from "os";
6
- import { chromium } from "playwright";
7
10
 
8
11
  // src/cdp-interceptor/proxy.ts
9
12
  import { WebSocketServer, WebSocket } from "ws";
@@ -1427,6 +1430,7 @@ async function resolveCDPEndpoint(raw) {
1427
1430
  }
1428
1431
 
1429
1432
  // src/browser.ts
1433
+ import { SessionStore } from "@dyyz1993/xcli-core";
1430
1434
  function logSessionEvent(event, details) {
1431
1435
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
1432
1436
  const pid = process.pid;
@@ -1439,7 +1443,7 @@ function sessionFile(name) {
1439
1443
  function ensureSessionDir() {
1440
1444
  mkdirSync(SESSION_DIR, { recursive: true });
1441
1445
  }
1442
- var sessions = /* @__PURE__ */ new Map();
1446
+ var sessions = new SessionStore();
1443
1447
  var _sharedBrowser = null;
1444
1448
  var _sharedCdpProxy = null;
1445
1449
  var IDLE_TIMEOUT_MS = (process.env.XBROWSER_IDLE_TIMEOUT ? parseInt(process.env.XBROWSER_IDLE_TIMEOUT, 10) : 30) * 60 * 1e3;
@@ -1450,7 +1454,7 @@ function resetIdleTimer() {
1450
1454
  const now = Date.now();
1451
1455
  let allIdle = true;
1452
1456
  const idleSessions = [];
1453
- for (const [, s] of sessions) {
1457
+ for (const s of sessions) {
1454
1458
  if (now - s.lastActivityAt < IDLE_TIMEOUT_MS) {
1455
1459
  allIdle = false;
1456
1460
  } else {
@@ -1473,7 +1477,7 @@ function touchSession(id) {
1473
1477
  resetIdleTimer();
1474
1478
  }
1475
1479
  process.on("exit", () => {
1476
- for (const session of sessions.values()) {
1480
+ for (const session of sessions.list()) {
1477
1481
  if (session.isCDP) {
1478
1482
  logSessionEvent("process_exit", `Session "${session.name}": CDP connection (not closing external browser).`);
1479
1483
  } else {
@@ -1556,15 +1560,21 @@ async function createBrowser(options) {
1556
1560
  const realEndpoint = await resolveCDPEndpoint(options.cdpEndpoint);
1557
1561
  if (options.intercept) {
1558
1562
  const config = typeof options.intercept === "object" ? { ...options.intercept, cdpEndpoint: realEndpoint } : { cdpEndpoint: realEndpoint };
1559
- const proxy = new CDPInterceptorProxy(config);
1560
- const proxyPort = await proxy.start();
1563
+ _sharedCdpProxy = new CDPInterceptorProxy(config);
1564
+ const proxyPort = await _sharedCdpProxy.start();
1561
1565
  console.error(`[CDP Interceptor] Proxy running on ws://localhost:${proxyPort}, forwarding to ${realEndpoint}`);
1562
- return await chromium.connectOverCDP(`ws://localhost:${proxyPort}`);
1566
+ const { browser: browser3 } = await launch({ cdpEndpoint: `ws://localhost:${proxyPort}` });
1567
+ return browser3;
1563
1568
  }
1564
- return await chromium.connectOverCDP(realEndpoint);
1569
+ const { browser: browser2 } = await launch({ cdpEndpoint: realEndpoint });
1570
+ await browser2.discoverContexts().catch((err) => {
1571
+ console.error(`[browser] discoverContexts failed: ${err.message}`);
1572
+ });
1573
+ return browser2;
1565
1574
  }
1566
1575
  const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
1567
- return await chromium.launch({ executablePath, headless: options?.headless ?? true });
1576
+ const { browser } = await launch({ executablePath, headless: options?.headless ?? true });
1577
+ return browser;
1568
1578
  }
1569
1579
  async function getBrowser(options) {
1570
1580
  if (_sharedBrowser) return _sharedBrowser;
@@ -1574,10 +1584,7 @@ async function getBrowser(options) {
1574
1584
  return _sharedBrowser;
1575
1585
  }
1576
1586
  function findSession(name) {
1577
- for (const [, session] of sessions) {
1578
- if (session.name === name) return session;
1579
- }
1580
- return void 0;
1587
+ return sessions.find(name);
1581
1588
  }
1582
1589
  function getSessionById(id) {
1583
1590
  return sessions.get(id);
@@ -1681,9 +1688,14 @@ async function findOrRestoreSession(name, cdpEndpoint) {
1681
1688
  return void 0;
1682
1689
  }
1683
1690
  const targetUrl = meta.conversationUrl || meta.url;
1684
- if (targetUrl && page.url() !== targetUrl && !page.url().includes(new URL(targetUrl).hostname)) {
1685
- await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
1686
- });
1691
+ if (targetUrl && page.url() !== targetUrl) {
1692
+ try {
1693
+ if (!page.url().includes(new URL(targetUrl).hostname)) {
1694
+ await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
1695
+ });
1696
+ }
1697
+ } catch {
1698
+ }
1687
1699
  }
1688
1700
  const session = {
1689
1701
  id: meta.id || randomUUID(),
@@ -1696,13 +1708,13 @@ async function findOrRestoreSession(name, cdpEndpoint) {
1696
1708
  isCDP: true,
1697
1709
  cdpEndpoint: ep
1698
1710
  };
1699
- for (const [existingId, existingSession] of sessions) {
1711
+ for (const existingSession of sessions.list()) {
1700
1712
  if (existingSession.name === name) {
1701
- logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingId}" during restore`);
1702
- sessions.delete(existingId);
1713
+ logSessionEvent("remove_stale", `Removing stale session name="${name}" id="${existingSession.id}" during restore`);
1714
+ sessions.removeById(existingSession.id);
1703
1715
  }
1704
1716
  }
1705
- sessions.set(session.id, session);
1717
+ sessions.set(session);
1706
1718
  resetIdleTimer();
1707
1719
  await installNetworkCapture(page, name);
1708
1720
  return session;
@@ -1715,10 +1727,15 @@ async function findOrRestoreSession(name, cdpEndpoint) {
1715
1727
  async function createEphemeralContext(options) {
1716
1728
  if (options?.cdpEndpoint) {
1717
1729
  const endpoint = await resolveCDPEndpoint(options.cdpEndpoint);
1718
- const b2 = await chromium.connectOverCDP(endpoint);
1730
+ const { browser: b2 } = await launch({ cdpEndpoint: endpoint });
1719
1731
  const contexts = b2.contexts();
1720
1732
  const ctx = contexts[0] || await b2.newContext();
1721
- const page2 = await ctx.newPage();
1733
+ const allPages = ctx.pages();
1734
+ const existingPages = allPages.filter((p) => {
1735
+ const url = p.url();
1736
+ return url !== "about:blank" && !url.startsWith("chrome://");
1737
+ });
1738
+ const page2 = existingPages.length > 0 ? existingPages[0] : allPages.length > 0 ? allPages[0] : await ctx.newPage();
1722
1739
  resetIdleTimer();
1723
1740
  ephemeralConnections.set(page2, b2);
1724
1741
  return { context: ctx, page: page2 };
@@ -1750,38 +1767,59 @@ async function closeEphemeralContext(context) {
1750
1767
  }
1751
1768
  }
1752
1769
  function getAllSessions() {
1753
- return Array.from(sessions.values());
1770
+ return sessions.list();
1754
1771
  }
1755
1772
  async function installNetworkCapture(page, sessionName) {
1756
1773
  if (process.env.XBROWSER_DAEMON_WORKER !== "1") return;
1757
- const { networkStore } = await import("./network-store-YAF5OIBH.js");
1758
- page.on("response", async (response) => {
1774
+ const { networkStore } = await import("./network-store-XGZ25FFC.js");
1775
+ const requestData = /* @__PURE__ */ new Map();
1776
+ const responseMeta = /* @__PURE__ */ new Map();
1777
+ const xbPage = page;
1778
+ xbPage.on("request", (params) => {
1759
1779
  try {
1760
- const request = response.request();
1761
- const url = response.url();
1762
- const contentType = response.headers()["content-type"] || "";
1763
- const headers = {};
1764
- for (const [k, v] of Object.entries(response.headers())) {
1765
- headers[k] = v;
1766
- }
1767
- const requestHeaders = {};
1768
- for (const [k, v] of Object.entries(request.headers())) {
1769
- requestHeaders[k] = v;
1770
- }
1780
+ const p = params;
1781
+ requestData.set(p.requestId, {
1782
+ method: p.request.method,
1783
+ headers: p.request.headers,
1784
+ postData: p.request.postData ?? null,
1785
+ resourceType: p.type
1786
+ });
1787
+ } catch {
1788
+ }
1789
+ });
1790
+ xbPage.on("response", (params) => {
1791
+ try {
1792
+ const p = params;
1793
+ responseMeta.set(p.requestId, {
1794
+ status: p.response.status,
1795
+ url: p.response.url,
1796
+ headers: p.response.headers,
1797
+ mimeType: p.response.mimeType,
1798
+ type: p.type
1799
+ });
1800
+ } catch {
1801
+ }
1802
+ });
1803
+ xbPage.on("requestfinished", async (params) => {
1804
+ try {
1805
+ const p = params;
1806
+ const meta = responseMeta.get(p.requestId);
1807
+ if (!meta) return;
1808
+ const req = requestData.get(p.requestId);
1809
+ const method = req?.method ?? "GET";
1810
+ const contentType = meta.headers["content-type"] || meta.headers["Content-Type"] || "";
1811
+ const resourceType = req?.resourceType ?? meta.type;
1812
+ const requestHeaders = req?.headers ?? {};
1771
1813
  let requestBody = void 0;
1772
- const method = request.method();
1773
1814
  const isPostLike = ["POST", "PATCH", "PUT"].includes(method);
1774
1815
  if (isPostLike && requestHeaders["content-type"]?.includes("application/json")) {
1775
- try {
1776
- const postData = request.postData();
1777
- if (postData) {
1778
- try {
1779
- requestBody = JSON.parse(postData);
1780
- } catch {
1781
- requestBody = postData;
1782
- }
1816
+ const postData = req?.postData;
1817
+ if (postData) {
1818
+ try {
1819
+ requestBody = JSON.parse(postData);
1820
+ } catch {
1821
+ requestBody = postData;
1783
1822
  }
1784
- } catch {
1785
1823
  }
1786
1824
  }
1787
1825
  let responseBody = void 0;
@@ -1789,7 +1827,11 @@ async function installNetworkCapture(page, sessionName) {
1789
1827
  const isJsonish = contentType.includes("json") || contentType.includes("javascript") || contentType.includes("text/");
1790
1828
  if (isJsonish) {
1791
1829
  try {
1792
- const text = await response.text();
1830
+ const bodyResult = await xbPage._cdpSend(
1831
+ "Network.getResponseBody",
1832
+ { requestId: p.requestId }
1833
+ );
1834
+ const text = bodyResult.body ?? "";
1793
1835
  size = text.length;
1794
1836
  if (size <= 10240) {
1795
1837
  try {
@@ -1802,8 +1844,11 @@ async function installNetworkCapture(page, sessionName) {
1802
1844
  }
1803
1845
  } else {
1804
1846
  try {
1805
- const text = await response.text();
1806
- size = text.length;
1847
+ const bodyResult = await xbPage._cdpSend(
1848
+ "Network.getResponseBody",
1849
+ { requestId: p.requestId }
1850
+ );
1851
+ size = bodyResult.body?.length ?? 0;
1807
1852
  } catch {
1808
1853
  size = 0;
1809
1854
  }
@@ -1811,17 +1856,19 @@ async function installNetworkCapture(page, sessionName) {
1811
1856
  networkStore.add(sessionName, {
1812
1857
  timestamp: Date.now(),
1813
1858
  method,
1814
- url,
1815
- path: new URL(url).pathname,
1816
- status: response.status(),
1859
+ url: meta.url,
1860
+ path: new URL(meta.url).pathname,
1861
+ status: meta.status,
1817
1862
  contentType,
1818
1863
  size,
1819
- headers,
1864
+ headers: meta.headers,
1820
1865
  body: responseBody,
1821
1866
  requestHeaders,
1822
1867
  requestBody,
1823
- resourceType: request.resourceType()
1868
+ resourceType
1824
1869
  });
1870
+ requestData.delete(p.requestId);
1871
+ responseMeta.delete(p.requestId);
1825
1872
  } catch {
1826
1873
  }
1827
1874
  });
@@ -1845,16 +1892,38 @@ async function createSession(name, url, options) {
1845
1892
  }
1846
1893
  context = contexts[0] || await b.newContext();
1847
1894
  let targetPage = null;
1848
- for (const ctx of contexts) {
1849
- const pages = ctx.pages();
1850
- for (const p of pages) {
1851
- const pUrl = p.url();
1852
- if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
1853
- targetPage = p;
1854
- break;
1895
+ const targetHostname = url ? (() => {
1896
+ try {
1897
+ return new URL(url).hostname;
1898
+ } catch {
1899
+ return "";
1900
+ }
1901
+ })() : "";
1902
+ if (targetHostname) {
1903
+ for (const ctx of contexts) {
1904
+ const pages = ctx.pages();
1905
+ for (const p of pages) {
1906
+ const pUrl = p.url();
1907
+ if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://") && pUrl.includes(targetHostname)) {
1908
+ targetPage = p;
1909
+ break;
1910
+ }
1911
+ }
1912
+ if (targetPage) break;
1913
+ }
1914
+ }
1915
+ if (!targetPage) {
1916
+ for (const ctx of contexts) {
1917
+ const pages = ctx.pages();
1918
+ for (const p of pages) {
1919
+ const pUrl = p.url();
1920
+ if (pUrl && pUrl !== "about:blank" && !pUrl.startsWith("chrome://")) {
1921
+ targetPage = p;
1922
+ break;
1923
+ }
1855
1924
  }
1925
+ if (targetPage) break;
1856
1926
  }
1857
- if (targetPage) break;
1858
1927
  }
1859
1928
  if (!targetPage && options?.cdpEndpoint) {
1860
1929
  const targets = await getCDPTargets(options.cdpEndpoint);
@@ -1895,14 +1964,14 @@ async function createSession(name, url, options) {
1895
1964
  isCDP,
1896
1965
  cdpEndpoint: options?.cdpEndpoint
1897
1966
  };
1898
- sessions.set(session.id, session);
1967
+ sessions.set(session);
1899
1968
  logSessionEvent("create_session", `name="${name}" id="${session.id}" url="${url || "(no url)"}" isCDP=${isCDP} cdpEndpoint=${options?.cdpEndpoint || "(none)"}`);
1900
1969
  resetIdleTimer();
1901
1970
  await installNetworkCapture(page, name);
1902
1971
  return session;
1903
1972
  }
1904
1973
  async function closeSessionByName(name) {
1905
- for (const [id, session] of sessions) {
1974
+ for (const session of sessions) {
1906
1975
  if (session.name === name || session.id === name) {
1907
1976
  logSessionEvent("close_session", `name="${session.name}" id="${session.id}" url="${session.page.url()}"`);
1908
1977
  if (session.isCDP) {
@@ -1921,20 +1990,20 @@ async function closeSessionByName(name) {
1921
1990
  });
1922
1991
  }
1923
1992
  }
1924
- sessions.delete(id);
1993
+ sessions.removeById(session.id);
1925
1994
  const file2 = sessionFile(session.name);
1926
1995
  try {
1927
1996
  unlinkSync(file2);
1928
1997
  } catch {
1929
1998
  }
1930
1999
  try {
1931
- const { networkStore, commandLogStore } = await import("./network-store-YAF5OIBH.js");
2000
+ const { networkStore, commandLogStore } = await import("./network-store-XGZ25FFC.js");
1932
2001
  networkStore.clear(session.name);
1933
2002
  commandLogStore.clear(session.name);
1934
2003
  } catch {
1935
2004
  }
1936
2005
  try {
1937
- const { SessionRecorder } = await import("./session-recorder-ILSSV2UC.js");
2006
+ const { SessionRecorder } = await import("./session-recorder-YI7YYM36.js");
1938
2007
  SessionRecorder.cleanup(session.name);
1939
2008
  } catch {
1940
2009
  }
@@ -1949,9 +2018,9 @@ async function closeSessionByName(name) {
1949
2018
  return false;
1950
2019
  }
1951
2020
  async function closeAllSessions() {
1952
- const names = [...sessions.values()].map((s) => `${s.name}(${s.page.url()})`).join(", ");
2021
+ const names = sessions.list().map((s) => `${s.name}(${s.page.url()})`).join(", ");
1953
2022
  if (names) logSessionEvent("close_all_sessions", `Closing ${sessions.size} sessions: ${names}`);
1954
- for (const [id, session] of sessions) {
2023
+ for (const session of sessions.list()) {
1955
2024
  try {
1956
2025
  if (!session.isCDP) {
1957
2026
  await session.context.close();
@@ -1960,9 +2029,9 @@ async function closeAllSessions() {
1960
2029
  await session.browser.close().catch(() => {
1961
2030
  });
1962
2031
  }
1963
- sessions.delete(id);
2032
+ sessions.removeById(session.id);
1964
2033
  } catch {
1965
- sessions.delete(id);
2034
+ sessions.removeById(session.id);
1966
2035
  }
1967
2036
  }
1968
2037
  }
@@ -2000,7 +2069,7 @@ async function ensureProcessCanExit() {
2000
2069
  clearTimeout(idleTimer);
2001
2070
  idleTimer = null;
2002
2071
  }
2003
- for (const session of sessions.values()) {
2072
+ for (const session of sessions.list()) {
2004
2073
  if (session.browser) {
2005
2074
  if (session.isCDP) {
2006
2075
  await session.browser.close().catch(() => {
@@ -0,0 +1,83 @@
1
+ // src/daemon/daemon.ts
2
+ import { spawn } from "child_process";
3
+ import { join, dirname } from "path";
4
+ import { homedir } from "os";
5
+ import { fileURLToPath } from "url";
6
+ import { stopDaemon as xcliStopDaemon, isDaemonRunning, getDaemonStatus, killAllDaemon } from "@dyyz1993/xcli-core";
7
+ var CONFIG_DIR = join(homedir(), ".xbrowser");
8
+ var __dirname = dirname(fileURLToPath(import.meta.url));
9
+ var WORKER_PATH = join(__dirname, "daemon-main.js");
10
+ function getDaemonConfig() {
11
+ return {
12
+ configDir: CONFIG_DIR,
13
+ workerEntryPath: WORKER_PATH,
14
+ basePort: 9224
15
+ };
16
+ }
17
+ async function startDaemonProcess(port = 9224) {
18
+ const config = getDaemonConfig();
19
+ if (isDaemonRunning(config)) {
20
+ const status = getDaemonStatus(config);
21
+ if (status.port === port && status.pid) {
22
+ return { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
23
+ }
24
+ await xcliStopDaemon(config);
25
+ }
26
+ const child = spawn("node", [WORKER_PATH], {
27
+ detached: true,
28
+ stdio: "ignore",
29
+ env: {
30
+ ...process.env,
31
+ XBROWSER_DAEMON_PORT: String(port)
32
+ }
33
+ });
34
+ child.unref();
35
+ return new Promise((resolve, reject) => {
36
+ let resolved = false;
37
+ const timeout = setTimeout(() => {
38
+ if (!resolved) {
39
+ resolved = true;
40
+ reject(new Error("Daemon start timeout after 15s"));
41
+ }
42
+ }, 15e3);
43
+ const checkInterval = setInterval(() => {
44
+ if (isDaemonRunning(config)) {
45
+ const s = getDaemonStatus(config);
46
+ if (s.port === port && s.pid) {
47
+ resolved = true;
48
+ clearTimeout(timeout);
49
+ clearInterval(checkInterval);
50
+ resolve({ pid: s.pid, port: s.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
51
+ }
52
+ }
53
+ }, 200);
54
+ child.on("error", (err) => {
55
+ if (!resolved) {
56
+ resolved = true;
57
+ clearTimeout(timeout);
58
+ clearInterval(checkInterval);
59
+ reject(err);
60
+ }
61
+ });
62
+ });
63
+ }
64
+ function getDaemonProcessStatus() {
65
+ const config = getDaemonConfig();
66
+ const running = isDaemonRunning(config);
67
+ if (!running) {
68
+ return { running: false, pid: 0, port: 0, info: null };
69
+ }
70
+ const status = getDaemonStatus(config);
71
+ return {
72
+ running: true,
73
+ pid: status.pid,
74
+ port: status.port,
75
+ info: { pid: status.pid, port: status.port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
76
+ };
77
+ }
78
+
79
+ export {
80
+ getDaemonConfig,
81
+ startDaemonProcess,
82
+ getDaemonProcessStatus
83
+ };