@poncho-ai/browser 0.6.2 → 0.6.4

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,5 +1,5 @@
1
1
 
2
- > @poncho-ai/browser@0.6.2 build /home/runner/work/poncho-ai/poncho-ai/packages/browser
2
+ > @poncho-ai/browser@0.6.4 build /Users/cesar/Dev/latitude/poncho-ai/packages/browser
3
3
  > tsup src/index.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -7,8 +7,8 @@
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  ESM Build start
10
- ESM dist/index.js 41.37 KB
11
- ESM ⚡️ Build success in 75ms
10
+ ESM dist/index.js 45.65 KB
11
+ ESM ⚡️ Build success in 112ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 4983ms
14
- DTS dist/index.d.ts 13.59 KB
13
+ DTS ⚡️ Build success in 1828ms
14
+ DTS dist/index.d.ts 13.72 KB
@@ -0,0 +1,12 @@
1
+
2
+ > @poncho-ai/browser@0.6.2 test /Users/cesar/Dev/latitude/poncho-ai/packages/browser
3
+ > vitest --passWithNoTests
4
+
5
+
6
+  RUN  v1.6.1 /Users/cesar/Dev/latitude/poncho-ai/packages/browser
7
+
8
+ No test files found, exiting with code 0
9
+ include: **/*.{test,spec}.?(c|m)[jt]s?(x)
10
+
11
+ exclude: **/node_modules/**, **/dist/**, **/cypress/**, **/.{idea,git,cache,output,temp}/**, **/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*
12
+ watch exclude: **/node_modules/**, **/dist/**
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @poncho-ai/browser
2
2
 
3
+ ## 0.6.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b5af10a`](https://github.com/cesr/poncho-ai/commit/b5af10a2f7b0023c683f14cd465105f8ddfff0ee) Thanks [@cesr](https://github.com/cesr)! - Fix browser cookie restore failing with "Invalid parameters" by sanitizing Playwright-format cookies to CDP-compatible format before calling Network.setCookies. Falls back to per-cookie restore when batch call fails.
8
+
9
+ ## 0.6.3
10
+
11
+ ### Patch Changes
12
+
13
+ - [`35f3f54`](https://github.com/cesr/poncho-ai/commit/35f3f54b17ff50253ab01dbcfe19c643dd6c7e00) Thanks [@cesr](https://github.com/cesr)! - Add `browser_clear_cookies` tool for deleting browser cookies
14
+
15
+ Agents with `browser: true` can now call `browser_clear_cookies` to delete cookies from the live browser and persisted storage. Accepts an optional `url` parameter to scope deletion to a specific site (e.g. "https://example.com"); omit to clear all cookies.
16
+
3
17
  ## 0.6.2
4
18
 
5
19
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -147,6 +147,9 @@ declare class BrowserSession {
147
147
  executeJs(conversationId: string, script: string): Promise<unknown>;
148
148
  closeTab(conversationId: string): Promise<void>;
149
149
  navigate(conversationId: string, action: string): Promise<void>;
150
+ clearCookies(conversationId: string, url?: string): Promise<{
151
+ cleared: number;
152
+ }>;
150
153
  startScreencast(conversationId: string, options?: ScreencastOptions): Promise<void>;
151
154
  stopScreencast(): Promise<void>;
152
155
  onFrame(conversationId: string, listener: FrameListener): () => void;
@@ -158,6 +161,7 @@ declare class BrowserSession {
158
161
  saveState(storagePath: string): Promise<void>;
159
162
  private persistStorageState;
160
163
  private restoreStorageState;
164
+ private clearPersistedCookies;
161
165
  close(): Promise<void>;
162
166
  private emitStatus;
163
167
  }
package/dist/index.js CHANGED
@@ -186,6 +186,24 @@ async function getBrowserManagerCtor() {
186
186
  return BrowserManagerCtor;
187
187
  }
188
188
  var MAX_TABS = 8;
189
+ var VALID_SAME_SITE = ["Strict", "Lax", "None"];
190
+ function sanitizeCookieForCDP(c) {
191
+ const name = typeof c.name === "string" ? c.name : "";
192
+ const value = typeof c.value === "string" ? c.value : "";
193
+ if (!name) return null;
194
+ const out = { name, value };
195
+ if (typeof c.domain === "string" && c.domain) out.domain = c.domain;
196
+ if (typeof c.path === "string") out.path = c.path;
197
+ if (typeof c.secure === "boolean") out.secure = c.secure;
198
+ if (typeof c.httpOnly === "boolean") out.httpOnly = c.httpOnly;
199
+ if (typeof c.expires === "number" && c.expires > 0) {
200
+ out.expires = c.expires;
201
+ }
202
+ if (typeof c.sameSite === "string" && VALID_SAME_SITE.includes(c.sameSite)) {
203
+ out.sameSite = c.sameSite;
204
+ }
205
+ return out;
206
+ }
189
207
  var SAME_TAB_INIT_SCRIPT = `
190
208
  (() => {
191
209
  // Override window.open to navigate in-place
@@ -739,6 +757,34 @@ var BrowserSession = class {
739
757
  this.unlock();
740
758
  }
741
759
  }
760
+ async clearCookies(conversationId, url) {
761
+ await this.lock();
762
+ try {
763
+ const mgr = await this.ensureManager();
764
+ await this.switchToConversation(mgr, conversationId);
765
+ const cdp = await mgr.getCDPSession();
766
+ let cleared = 0;
767
+ if (url) {
768
+ const { cookies } = await cdp.send("Network.getCookies", { urls: [url] });
769
+ cleared = cookies.length;
770
+ for (const cookie of cookies) {
771
+ await cdp.send("Network.deleteCookies", {
772
+ name: cookie.name,
773
+ domain: cookie.domain,
774
+ path: cookie.path
775
+ });
776
+ }
777
+ } else {
778
+ const { cookies } = await cdp.send("Network.getCookies");
779
+ cleared = cookies.length;
780
+ await cdp.send("Network.clearBrowserCookies");
781
+ }
782
+ await this.clearPersistedCookies(url);
783
+ return { cleared };
784
+ } finally {
785
+ this.unlock();
786
+ }
787
+ }
742
788
  // -----------------------------------------------------------------------
743
789
  // Screencast (one active at a time, tied to the viewed conversation)
744
790
  // -----------------------------------------------------------------------
@@ -808,6 +854,10 @@ var BrowserSession = class {
808
854
  tab.frameListeners.add(listener);
809
855
  return () => {
810
856
  tab.frameListeners.delete(listener);
857
+ if (tab.frameListeners.size === 0 && this._screencastConversation === conversationId) {
858
+ this.stopScreencast().catch(() => {
859
+ });
860
+ }
811
861
  };
812
862
  }
813
863
  onStatus(conversationId, listener) {
@@ -913,8 +963,28 @@ var BrowserSession = class {
913
963
  if (!json) return;
914
964
  const state = JSON.parse(json);
915
965
  if (state.cookies?.length) {
916
- await cdp.send("Network.setCookies", { cookies: state.cookies });
917
- console.log(`[poncho][browser] Restored ${state.cookies.length} cookies`);
966
+ const sanitized = state.cookies.map(sanitizeCookieForCDP).filter((c) => c !== null);
967
+ if (sanitized.length) {
968
+ try {
969
+ await cdp.send("Network.setCookies", { cookies: sanitized });
970
+ } catch {
971
+ let restored = 0;
972
+ for (const cookie of sanitized) {
973
+ try {
974
+ await cdp.send("Network.setCookies", { cookies: [cookie] });
975
+ restored++;
976
+ } catch {
977
+ }
978
+ }
979
+ if (restored > 0) {
980
+ console.log(`[poncho][browser] Restored ${restored}/${sanitized.length} cookies (batch failed, fell back to individual)`);
981
+ } else {
982
+ console.warn("[poncho][browser] Could not restore any cookies");
983
+ }
984
+ return;
985
+ }
986
+ console.log(`[poncho][browser] Restored ${sanitized.length} cookies`);
987
+ }
918
988
  }
919
989
  if (state.origins?.length) {
920
990
  const entries = {};
@@ -933,6 +1003,36 @@ var BrowserSession = class {
933
1003
  console.warn("[poncho][browser] Failed to restore storage state:", err?.message ?? err);
934
1004
  }
935
1005
  }
1006
+ async clearPersistedCookies(url) {
1007
+ const persistence = this.config.storagePersistence;
1008
+ if (!persistence) return;
1009
+ try {
1010
+ const json = await persistence.load();
1011
+ if (!json) return;
1012
+ const state = JSON.parse(json);
1013
+ if (!state.cookies?.length) return;
1014
+ if (url) {
1015
+ let host;
1016
+ try {
1017
+ host = new URL(url).hostname.toLowerCase();
1018
+ } catch {
1019
+ return;
1020
+ }
1021
+ state.cookies = state.cookies.filter((c) => {
1022
+ const raw = String(c.domain ?? "").toLowerCase();
1023
+ const bare = raw.replace(/^\./, "");
1024
+ if (host === bare) return false;
1025
+ if (raw.startsWith(".") && host.endsWith("." + bare)) return false;
1026
+ return true;
1027
+ });
1028
+ } else {
1029
+ state.cookies = [];
1030
+ }
1031
+ await persistence.save(JSON.stringify(state));
1032
+ } catch (err) {
1033
+ console.warn("[poncho][browser] Failed to clear persisted cookies:", err?.message ?? err);
1034
+ }
1035
+ }
936
1036
  async close() {
937
1037
  try {
938
1038
  await this.stopScreencast();
@@ -1167,6 +1267,25 @@ function createBrowserTools(getSession, getConversationId) {
1167
1267
  return { scrolled: direction, amount: amount ?? "viewport" };
1168
1268
  }
1169
1269
  },
1270
+ {
1271
+ name: "browser_clear_cookies",
1272
+ description: `Delete browser cookies. By default clears all cookies. Pass a url to only delete cookies that would be sent to that URL (e.g. "https://example.com" removes cookies for example.com and its subdomains). Also removes them from persisted storage so they won't be restored on next launch.`,
1273
+ inputSchema: {
1274
+ type: "object",
1275
+ properties: {
1276
+ url: {
1277
+ type: "string",
1278
+ description: 'Optional URL to scope deletion to (e.g. "https://example.com"). Omit to clear all cookies.'
1279
+ }
1280
+ }
1281
+ },
1282
+ handler: async (input) => {
1283
+ const session = getSession();
1284
+ const url = input.url ? String(input.url) : void 0;
1285
+ const { cleared } = await session.clearCookies(getConversationId(), url);
1286
+ return { cleared, scope: url ?? "all" };
1287
+ }
1288
+ },
1170
1289
  {
1171
1290
  name: "browser_close",
1172
1291
  description: "Close the browser tab for this conversation. Call this when you're done with browser tasks to free resources.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/browser",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Browser automation for Poncho agents, powered by agent-browser",
5
5
  "repository": {
6
6
  "type": "git",
package/src/session.ts CHANGED
@@ -62,6 +62,37 @@ async function getBrowserManagerCtor(): Promise<new () => BrowserManagerInstance
62
62
 
63
63
  const MAX_TABS = 8;
64
64
 
65
+ const VALID_SAME_SITE = ["Strict", "Lax", "None"];
66
+
67
+ /**
68
+ * Map a Playwright-format cookie to a CDP-compatible CookieParam, stripping
69
+ * unknown fields and fixing values that newer Chrome versions reject.
70
+ */
71
+ function sanitizeCookieForCDP(c: Record<string, unknown>): Record<string, unknown> | null {
72
+ const name = typeof c.name === "string" ? c.name : "";
73
+ const value = typeof c.value === "string" ? c.value : "";
74
+ if (!name) return null;
75
+
76
+ const out: Record<string, unknown> = { name, value };
77
+
78
+ if (typeof c.domain === "string" && c.domain) out.domain = c.domain;
79
+ if (typeof c.path === "string") out.path = c.path;
80
+ if (typeof c.secure === "boolean") out.secure = c.secure;
81
+ if (typeof c.httpOnly === "boolean") out.httpOnly = c.httpOnly;
82
+
83
+ // Playwright uses -1 for session cookies; CDP expects the field to be
84
+ // absent for session cookies.
85
+ if (typeof c.expires === "number" && c.expires > 0) {
86
+ out.expires = c.expires;
87
+ }
88
+
89
+ if (typeof c.sameSite === "string" && VALID_SAME_SITE.includes(c.sameSite)) {
90
+ out.sameSite = c.sameSite;
91
+ }
92
+
93
+ return out;
94
+ }
95
+
65
96
  /**
66
97
  * Init script that forces new-tab navigations (window.open, target="_blank")
67
98
  * to open in the current tab. Runs before page scripts on every navigation.
@@ -676,6 +707,42 @@ export class BrowserSession {
676
707
  }
677
708
  }
678
709
 
710
+ async clearCookies(conversationId: string, url?: string): Promise<{ cleared: number }> {
711
+ await this.lock();
712
+ try {
713
+ const mgr = await this.ensureManager();
714
+ await this.switchToConversation(mgr, conversationId);
715
+ const cdp = await mgr.getCDPSession();
716
+
717
+ let cleared = 0;
718
+
719
+ if (url) {
720
+ const { cookies } = (await cdp.send("Network.getCookies", { urls: [url] })) as {
721
+ cookies: Array<{ name: string; domain: string; path: string }>;
722
+ };
723
+ cleared = cookies.length;
724
+ for (const cookie of cookies) {
725
+ await cdp.send("Network.deleteCookies", {
726
+ name: cookie.name,
727
+ domain: cookie.domain,
728
+ path: cookie.path,
729
+ });
730
+ }
731
+ } else {
732
+ const { cookies } = (await cdp.send("Network.getCookies")) as {
733
+ cookies: Array<unknown>;
734
+ };
735
+ cleared = cookies.length;
736
+ await cdp.send("Network.clearBrowserCookies");
737
+ }
738
+
739
+ await this.clearPersistedCookies(url);
740
+ return { cleared };
741
+ } finally {
742
+ this.unlock();
743
+ }
744
+ }
745
+
679
746
  // -----------------------------------------------------------------------
680
747
  // Screencast (one active at a time, tied to the viewed conversation)
681
748
  // -----------------------------------------------------------------------
@@ -743,7 +810,12 @@ export class BrowserSession {
743
810
  this.tabs.set(conversationId, tab);
744
811
  }
745
812
  tab.frameListeners.add(listener);
746
- return () => { tab!.frameListeners.delete(listener); };
813
+ return () => {
814
+ tab!.frameListeners.delete(listener);
815
+ if (tab!.frameListeners.size === 0 && this._screencastConversation === conversationId) {
816
+ this.stopScreencast().catch(() => {});
817
+ }
818
+ };
747
819
  }
748
820
 
749
821
  onStatus(conversationId: string, listener: StatusListener): () => void {
@@ -860,8 +932,29 @@ export class BrowserSession {
860
932
  origins?: Array<{ origin: string; localStorage: Array<{ name: string; value: string }> }>;
861
933
  };
862
934
  if (state.cookies?.length) {
863
- await cdp.send("Network.setCookies", { cookies: state.cookies });
864
- console.log(`[poncho][browser] Restored ${state.cookies.length} cookies`);
935
+ const sanitized = state.cookies
936
+ .map(sanitizeCookieForCDP)
937
+ .filter((c): c is Record<string, unknown> => c !== null);
938
+ if (sanitized.length) {
939
+ try {
940
+ await cdp.send("Network.setCookies", { cookies: sanitized });
941
+ } catch {
942
+ let restored = 0;
943
+ for (const cookie of sanitized) {
944
+ try {
945
+ await cdp.send("Network.setCookies", { cookies: [cookie] });
946
+ restored++;
947
+ } catch { /* skip this cookie */ }
948
+ }
949
+ if (restored > 0) {
950
+ console.log(`[poncho][browser] Restored ${restored}/${sanitized.length} cookies (batch failed, fell back to individual)`);
951
+ } else {
952
+ console.warn("[poncho][browser] Could not restore any cookies");
953
+ }
954
+ return;
955
+ }
956
+ console.log(`[poncho][browser] Restored ${sanitized.length} cookies`);
957
+ }
865
958
  }
866
959
  if (state.origins?.length) {
867
960
  const entries: Record<string, Array<{ name: string; value: string }>> = {};
@@ -881,6 +974,38 @@ export class BrowserSession {
881
974
  }
882
975
  }
883
976
 
977
+ private async clearPersistedCookies(url?: string): Promise<void> {
978
+ const persistence = this.config.storagePersistence;
979
+ if (!persistence) return;
980
+ try {
981
+ const json = await persistence.load();
982
+ if (!json) return;
983
+ const state = JSON.parse(json) as {
984
+ cookies?: Array<Record<string, unknown>>;
985
+ origins?: Array<{ origin: string; localStorage: Array<{ name: string; value: string }> }>;
986
+ };
987
+ if (!state.cookies?.length) return;
988
+
989
+ if (url) {
990
+ let host: string;
991
+ try { host = new URL(url).hostname.toLowerCase(); } catch { return; }
992
+ state.cookies = state.cookies.filter((c) => {
993
+ const raw = String(c.domain ?? "").toLowerCase();
994
+ const bare = raw.replace(/^\./, "");
995
+ if (host === bare) return false;
996
+ if (raw.startsWith(".") && host.endsWith("." + bare)) return false;
997
+ return true;
998
+ });
999
+ } else {
1000
+ state.cookies = [];
1001
+ }
1002
+
1003
+ await persistence.save(JSON.stringify(state));
1004
+ } catch (err) {
1005
+ console.warn("[poncho][browser] Failed to clear persisted cookies:", (err as Error)?.message ?? err);
1006
+ }
1007
+ }
1008
+
884
1009
  async close(): Promise<void> {
885
1010
  try { await this.stopScreencast(); } catch { /* */ }
886
1011
  await this.persistStorageState();
package/src/tools.ts CHANGED
@@ -221,6 +221,30 @@ export function createBrowserTools(
221
221
  return { scrolled: direction, amount: amount ?? "viewport" };
222
222
  },
223
223
  },
224
+ {
225
+ name: "browser_clear_cookies",
226
+ description:
227
+ "Delete browser cookies. By default clears all cookies. " +
228
+ "Pass a url to only delete cookies that would be sent to that URL " +
229
+ "(e.g. \"https://example.com\" removes cookies for example.com and its subdomains). " +
230
+ "Also removes them from persisted storage so they won't be restored on next launch.",
231
+ inputSchema: {
232
+ type: "object",
233
+ properties: {
234
+ url: {
235
+ type: "string",
236
+ description:
237
+ "Optional URL to scope deletion to (e.g. \"https://example.com\"). Omit to clear all cookies.",
238
+ },
239
+ },
240
+ },
241
+ handler: async (input: BrowserToolInput) => {
242
+ const session = getSession();
243
+ const url = input.url ? String(input.url) : undefined;
244
+ const { cleared } = await session.clearCookies(getConversationId(), url);
245
+ return { cleared, scope: url ?? "all" };
246
+ },
247
+ },
224
248
  {
225
249
  name: "browser_close",
226
250
  description: