@mearl/native-host 2.5.0 → 2.5.1

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.
@@ -9,6 +9,11 @@ export interface AgentBayBrowserRecord extends DaemonRecord {
9
9
  name: string;
10
10
  sessionId: string;
11
11
  imageId?: string;
12
+ persistent?: boolean;
13
+ contextId?: string;
14
+ contextName?: string;
15
+ /** Successful context sync checkpoint for retrying deletion of the current session. */
16
+ contextSyncedAt?: string;
12
17
  accessUrl?: string;
13
18
  createdAt: string;
14
19
  copiedCookies?: {
@@ -23,6 +28,16 @@ export interface AgentBaySessionLike {
23
28
  initializeAsync(options: Record<string, unknown>): Promise<boolean>;
24
29
  getEndpointUrl(): Promise<string>;
25
30
  };
31
+ callMcpTool?(toolName: string, args: Record<string, unknown>, autoGenSession?: boolean): Promise<{
32
+ success?: boolean;
33
+ errorMessage?: string;
34
+ }>;
35
+ context?: {
36
+ sync(): Promise<{
37
+ success: boolean;
38
+ errorMessage?: string;
39
+ }>;
40
+ };
26
41
  delete(syncContext?: boolean): Promise<{
27
42
  success: boolean;
28
43
  errorMessage?: string;
@@ -43,15 +58,34 @@ export interface AgentBayClientLike {
43
58
  session?: AgentBaySessionLike;
44
59
  errorMessage?: string;
45
60
  }>;
61
+ context?: {
62
+ get(name: string, create?: boolean): Promise<{
63
+ success: boolean;
64
+ context?: {
65
+ id: string;
66
+ name: string;
67
+ };
68
+ errorMessage?: string;
69
+ }>;
70
+ delete(context: {
71
+ id: string;
72
+ name: string;
73
+ }): Promise<{
74
+ success: boolean;
75
+ errorMessage?: string;
76
+ }>;
77
+ };
46
78
  }
47
79
  export interface AgentBayBrowserSummary {
48
80
  browserId: string;
49
81
  name: string;
50
82
  provider: 'agentbay';
51
83
  headless: false;
52
- persistent: false;
84
+ persistent: boolean;
53
85
  agentbaySessionId: string;
54
86
  agentbayImageId?: string;
87
+ agentbayContextId?: string;
88
+ agentbayContextName?: string;
55
89
  agentbayAccessUrl?: string;
56
90
  createdAt: string;
57
91
  copiedCookies?: AgentBayBrowserRecord['copiedCookies'];
@@ -79,6 +113,7 @@ interface AgentBayBrowserSessionInitOptions {
79
113
  export declare function agentBayBrowserId(name: string): string;
80
114
  export declare function resolveAgentBayApiKey(): string;
81
115
  export declare function resolveAgentBayImageId(value: unknown): string;
116
+ export declare function agentBayContextName(browserId: string): string;
82
117
  export declare function normalizeDesktopUserAgent(value: unknown): string;
83
118
  export declare function normalizeAgentBayAccessUrl(value: unknown): string | undefined;
84
119
  export declare function hasAgentBayBrowser(selector: string): boolean;
@@ -87,5 +122,15 @@ export declare function resolveAgentBayEndpoint(sessionId: string): Promise<stri
87
122
  export declare function keepAgentBaySessionAlive(sessionId: string): Promise<void>;
88
123
  export declare function initializeAgentBayBrowserSession(session: Pick<AgentBaySessionLike, 'browser'>, input: Pick<BrowserLaunchParams, 'url'>, cookies: TransferableCookie[], options?: AgentBayBrowserSessionInitOptions): Promise<number>;
89
124
  export declare function launchAgentBayBrowser(input: BrowserLaunchParams, deps: AgentBayBrowserManagerDeps): Promise<AgentBayBrowserSummary>;
90
- export declare function closeAgentBayBrowser(selector: string): Promise<Record<string, any>>;
125
+ export declare function releaseAgentBayBrowserResources(record: AgentBayBrowserRecord, agentBay: AgentBayClientLike, options?: {
126
+ deleteProfile?: boolean;
127
+ persistRecord?: (record: AgentBayBrowserRecord) => void;
128
+ }): Promise<{
129
+ sessionDeleted: boolean;
130
+ contextSynced?: boolean;
131
+ contextDeleted?: boolean;
132
+ }>;
133
+ export declare function closeAgentBayBrowser(selector: string, options?: {
134
+ deleteProfile?: boolean;
135
+ }): Promise<Record<string, any>>;
91
136
  export {};
package/dist/index.js CHANGED
@@ -9574,9 +9574,6 @@ function validateAgentBayInput(input) {
9574
9574
  if (input.headless === true) {
9575
9575
  throw new Error("headless=true is not supported by the AgentBay browser provider");
9576
9576
  }
9577
- if (input.persistent === true) {
9578
- throw new Error("persistent is not supported by the AgentBay CDP provider yet");
9579
- }
9580
9577
  if (input.executablePath) {
9581
9578
  throw new Error("executablePath is only supported by the local browser provider");
9582
9579
  }
@@ -9592,6 +9589,9 @@ function resolveAgentBayImageId(value) {
9592
9589
  }
9593
9590
  return imageId;
9594
9591
  }
9592
+ function agentBayContextName(browserId) {
9593
+ return `mearl-browser-${hashKey(browserId)}`;
9594
+ }
9595
9595
  function normalizeDesktopUserAgent(value) {
9596
9596
  const userAgent = typeof value === "string" ? value.trim() : "";
9597
9597
  if (!userAgent) throw new Error("Could not determine the control browser user agent");
@@ -9614,9 +9614,11 @@ function browserSummary2(record2, running) {
9614
9614
  name: record2.name,
9615
9615
  provider: "agentbay",
9616
9616
  headless: false,
9617
- persistent: false,
9617
+ persistent: record2.persistent === true,
9618
9618
  agentbaySessionId: record2.sessionId,
9619
9619
  ...record2.imageId ? { agentbayImageId: record2.imageId } : {},
9620
+ ...record2.contextId ? { agentbayContextId: record2.contextId } : {},
9621
+ ...record2.contextName ? { agentbayContextName: record2.contextName } : {},
9620
9622
  ...accessUrl ? { agentbayAccessUrl: accessUrl } : {},
9621
9623
  createdAt: record2.createdAt,
9622
9624
  ...record2.copiedCookies ? { copiedCookies: record2.copiedCookies } : {},
@@ -9651,12 +9653,26 @@ function listAgentBayBrowsers() {
9651
9653
  }
9652
9654
  async function requireSession(sessionId, apiKey) {
9653
9655
  const agentBay = await createClient(apiKey);
9656
+ return requireSessionFromClient(agentBay, sessionId);
9657
+ }
9658
+ async function requireSessionFromClient(agentBay, sessionId) {
9654
9659
  const result = await agentBay.get(sessionId);
9655
9660
  if (!result.success || !result.session) {
9656
9661
  throw new Error(result.errorMessage || `AgentBay session not found: ${sessionId}`);
9657
9662
  }
9658
9663
  return result.session;
9659
9664
  }
9665
+ async function resolvePersistentContext(agentBay, browserId) {
9666
+ if (!agentBay.context) {
9667
+ throw new Error("The installed AgentBay SDK does not support persistent browser contexts");
9668
+ }
9669
+ const name = agentBayContextName(browserId);
9670
+ const result = await agentBay.context.get(name, true);
9671
+ if (!result.success || !result.context?.id) {
9672
+ throw new Error(result.errorMessage || `Failed to create AgentBay browser context: ${name}`);
9673
+ }
9674
+ return { id: result.context.id, name: result.context.name || name };
9675
+ }
9660
9676
  async function resolveAgentBayEndpoint(sessionId) {
9661
9677
  const apiKey = resolveAgentBayApiKey();
9662
9678
  const { AgentBay, GetCdpLinkRequest } = await loadSdk();
@@ -9702,12 +9718,14 @@ async function launchAgentBayBrowser(input, deps) {
9702
9718
  const browserId = agentBayBrowserId(name);
9703
9719
  validateAgentBayInput(input);
9704
9720
  const imageId = resolveAgentBayImageId(input.imageId);
9721
+ const persistent = input.persistent === true;
9705
9722
  const userAgentMode = resolveUserAgentMode(input.userAgentMode);
9706
9723
  const copyCookieDomains = input.copyCookieDomains === void 0 ? [] : normalizeCookieDomains(input.copyCookieDomains);
9707
9724
  const apiKey = (deps.resolveApiKey ?? resolveAgentBayApiKey)();
9708
9725
  const releaseLock = acquireLaunchLock(browserId);
9709
9726
  const persistBrowserRecord = deps.persistBrowserRecord ?? ((record2) => writeRecord(keyedConfigFile(AGENTBAY_BROWSERS_SUBDIR, record2.browserId), record2));
9710
9727
  let session = null;
9728
+ let context;
9711
9729
  let hostPid = 0;
9712
9730
  let recordWritten = false;
9713
9731
  try {
@@ -9717,7 +9735,18 @@ async function launchAgentBayBrowser(input, deps) {
9717
9735
  userAgentMode === "desktop" ? deps.getBrowserUserAgent().then(normalizeDesktopUserAgent) : Promise.resolve(void 0)
9718
9736
  ]);
9719
9737
  const agentBay = await (deps.createAgentBayClient ?? createClient)(apiKey);
9720
- const created = await agentBay.create({ imageId, framework: "mearl" });
9738
+ context = persistent ? await resolvePersistentContext(agentBay, browserId) : void 0;
9739
+ const created = await agentBay.create({
9740
+ imageId,
9741
+ framework: "mearl",
9742
+ ...context ? {
9743
+ browserContext: {
9744
+ contextId: context.id,
9745
+ autoUpload: true,
9746
+ syncMode: "standard"
9747
+ }
9748
+ } : {}
9749
+ });
9721
9750
  if (!created.success || !created.session) {
9722
9751
  throw new Error(created.errorMessage || "AgentBay session creation failed");
9723
9752
  }
@@ -9757,6 +9786,8 @@ async function launchAgentBayBrowser(input, deps) {
9757
9786
  name,
9758
9787
  sessionId: session.sessionId,
9759
9788
  imageId,
9789
+ persistent,
9790
+ ...context ? { contextId: context.id, contextName: context.name } : {},
9760
9791
  ...accessUrl ? { accessUrl } : {},
9761
9792
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9762
9793
  ...copyCookieDomains.length > 0 ? { copiedCookies: { domains: copyCookieDomains, count: copiedCookieCount } } : {}
@@ -9790,6 +9821,8 @@ async function launchAgentBayBrowser(input, deps) {
9790
9821
  name,
9791
9822
  sessionId: session.sessionId,
9792
9823
  imageId,
9824
+ persistent,
9825
+ ...context ? { contextId: context.id, contextName: context.name } : {},
9793
9826
  ...accessUrl ? { accessUrl } : {},
9794
9827
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
9795
9828
  };
@@ -9827,16 +9860,72 @@ function isMissingSessionError(error48) {
9827
9860
  const message = error48 instanceof Error ? error48.message : String(error48);
9828
9861
  return /not[ -]?found|does not exist|already deleted|invalid session/i.test(message);
9829
9862
  }
9830
- async function closeAgentBayBrowser(selector) {
9831
- const record2 = findRecord(selector);
9832
- if (!record2) throw new Error(`AgentBay browser not found: ${selector}`);
9863
+ async function releaseAgentBayBrowserResources(record2, agentBay, options = {}) {
9864
+ const deleteContext = record2.persistent === true && options.deleteProfile === true;
9865
+ const syncContext = record2.persistent === true && !deleteContext;
9866
+ let contextSynced = Boolean(record2.contextSyncedAt);
9867
+ let session;
9833
9868
  try {
9834
- const session = await requireSession(record2.sessionId, resolveAgentBayApiKey());
9835
- const result = await session.delete(false);
9836
- if (!result.success) throw new Error(result.errorMessage || "AgentBay session deletion failed");
9869
+ session = await requireSessionFromClient(agentBay, record2.sessionId);
9837
9870
  } catch (error48) {
9838
9871
  if (!isMissingSessionError(error48)) throw error48;
9839
9872
  }
9873
+ if (session) {
9874
+ if (syncContext && !contextSynced) {
9875
+ if (!session.callMcpTool) {
9876
+ throw new Error("The installed AgentBay SDK cannot stop the browser before context sync");
9877
+ }
9878
+ const stopped = await session.callMcpTool("stopChrome", {}, false);
9879
+ if (stopped.success === false) {
9880
+ throw new Error(stopped.errorMessage || "AgentBay browser shutdown failed");
9881
+ }
9882
+ if (!session.context) {
9883
+ throw new Error("The installed AgentBay SDK cannot synchronize browser context state");
9884
+ }
9885
+ const synced = await session.context.sync();
9886
+ if (!synced.success) {
9887
+ throw new Error(synced.errorMessage || "AgentBay browser context sync failed");
9888
+ }
9889
+ contextSynced = true;
9890
+ record2.contextSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
9891
+ options.persistRecord?.(record2);
9892
+ }
9893
+ try {
9894
+ const result = await session.delete(false);
9895
+ if (!result.success) {
9896
+ throw new Error(result.errorMessage || "AgentBay session deletion failed");
9897
+ }
9898
+ } catch (error48) {
9899
+ if (!isMissingSessionError(error48)) throw error48;
9900
+ }
9901
+ }
9902
+ if (deleteContext) {
9903
+ if (!record2.contextId || !record2.contextName || !agentBay.context) {
9904
+ throw new Error("AgentBay browser context metadata is unavailable for profile deletion");
9905
+ }
9906
+ const result = await agentBay.context.delete({
9907
+ id: record2.contextId,
9908
+ name: record2.contextName
9909
+ });
9910
+ if (!result.success) throw new Error(result.errorMessage || "AgentBay context deletion failed");
9911
+ }
9912
+ return {
9913
+ sessionDeleted: true,
9914
+ ...syncContext ? { contextSynced } : {},
9915
+ ...deleteContext ? { contextDeleted: true } : {}
9916
+ };
9917
+ }
9918
+ async function closeAgentBayBrowser(selector, options = {}) {
9919
+ const record2 = findRecord(selector);
9920
+ if (!record2) throw new Error(`AgentBay browser not found: ${selector}`);
9921
+ const agentBay = await createClient(resolveAgentBayApiKey());
9922
+ const released = await releaseAgentBayBrowserResources(record2, agentBay, {
9923
+ ...options,
9924
+ persistRecord: (updatedRecord) => writeRecord(
9925
+ keyedConfigFile(AGENTBAY_BROWSERS_SUBDIR, updatedRecord.browserId),
9926
+ updatedRecord
9927
+ )
9928
+ });
9840
9929
  const closingOwnHost = record2.pid === process.pid;
9841
9930
  if (!closingOwnHost) await stopProcess(record2.pid, `--browser-id ${record2.browserId}`);
9842
9931
  removeRecord(keyedConfigFile(AGENTBAY_BROWSERS_SUBDIR, record2.browserId));
@@ -9849,7 +9938,8 @@ async function closeAgentBayBrowser(selector) {
9849
9938
  browserId: record2.browserId,
9850
9939
  provider: "agentbay",
9851
9940
  closed: true,
9852
- sessionDeleted: true
9941
+ ...released,
9942
+ ...record2.persistent === true ? { profileDeleted: options.deleteProfile === true } : { profileDeleted: true }
9853
9943
  };
9854
9944
  }
9855
9945
 
@@ -10006,6 +10096,8 @@ function mergeBrowserInventory(connected, managed) {
10006
10096
  ..."cdpPort" in managedBrowser ? { cdpPort: managedBrowser.cdpPort } : {},
10007
10097
  ..."agentbaySessionId" in managedBrowser ? { agentbaySessionId: managedBrowser.agentbaySessionId } : {},
10008
10098
  ..."agentbayImageId" in managedBrowser && managedBrowser.agentbayImageId ? { agentbayImageId: managedBrowser.agentbayImageId } : {},
10099
+ ..."agentbayContextId" in managedBrowser && managedBrowser.agentbayContextId ? { agentbayContextId: managedBrowser.agentbayContextId } : {},
10100
+ ..."agentbayContextName" in managedBrowser && managedBrowser.agentbayContextName ? { agentbayContextName: managedBrowser.agentbayContextName } : {},
10009
10101
  ..."agentbayAccessUrl" in managedBrowser && managedBrowser.agentbayAccessUrl ? { agentbayAccessUrl: managedBrowser.agentbayAccessUrl } : {},
10010
10102
  createdAt: managedBrowser.createdAt,
10011
10103
  ..."account" in managedBrowser ? { account: managedBrowser.account } : {},
@@ -10023,6 +10115,8 @@ function mergeBrowserInventory(connected, managed) {
10023
10115
  ..."cdpPort" in browser ? { cdpPort: browser.cdpPort } : {},
10024
10116
  ..."agentbaySessionId" in browser ? { agentbaySessionId: browser.agentbaySessionId } : {},
10025
10117
  ..."agentbayImageId" in browser && browser.agentbayImageId ? { agentbayImageId: browser.agentbayImageId } : {},
10118
+ ..."agentbayContextId" in browser && browser.agentbayContextId ? { agentbayContextId: browser.agentbayContextId } : {},
10119
+ ..."agentbayContextName" in browser && browser.agentbayContextName ? { agentbayContextName: browser.agentbayContextName } : {},
10026
10120
  ..."agentbayAccessUrl" in browser && browser.agentbayAccessUrl ? { agentbayAccessUrl: browser.agentbayAccessUrl } : {},
10027
10121
  createdAt: browser.createdAt,
10028
10122
  ..."account" in browser ? { account: browser.account } : {},
@@ -10047,7 +10141,7 @@ import os2 from "node:os";
10047
10141
  import { randomUUID as randomUUID4 } from "node:crypto";
10048
10142
 
10049
10143
  // src/version.ts
10050
- var NATIVE_HOST_VERSION = true ? "2.5.0" : "unknown";
10144
+ var NATIVE_HOST_VERSION = true ? "2.5.1" : "unknown";
10051
10145
 
10052
10146
  // src/analytics.ts
10053
10147
  var MEASUREMENT_ID = "G-W84LJDZ6PH";
@@ -31037,7 +31131,7 @@ async function handleSocketLine(socket, line) {
31037
31131
  `Managed browser name is ambiguous: ${selector}. Use the full browserId from browser_list.`
31038
31132
  );
31039
31133
  }
31040
- const result = agentBayExists ? await closeAgentBayBrowser(selector) : await closeManagedBrowser(selector, { deleteProfile: data?.deleteProfile });
31134
+ const result = agentBayExists ? await closeAgentBayBrowser(selector, { deleteProfile: data?.deleteProfile }) : await closeManagedBrowser(selector, { deleteProfile: data?.deleteProfile });
31041
31135
  socket.write(JSON.stringify({ id, success: true, data: result }) + "\n");
31042
31136
  } catch (error48) {
31043
31137
  socket.write(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/native-host",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Native Messaging Host for Mearl — bridges Chrome Extension and local socket server",
5
5
  "type": "module",
6
6
  "main": "dist/install.js",
@@ -37,9 +37,9 @@
37
37
  "@types/ws": "^8.18.1",
38
38
  "esbuild": "^0.25.2",
39
39
  "typescript": "^5.8.3",
40
- "@mearl/browser-core": "2.5.0",
41
- "@mearl/daemon-core": "2.5.0",
42
- "@mearl/setup": "2.5.0"
40
+ "@mearl/browser-core": "2.5.1",
41
+ "@mearl/daemon-core": "2.5.1",
42
+ "@mearl/setup": "2.5.1"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "rm -rf dist/ && node build.mjs",