@poncho-ai/browser 0.2.1 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/browser@0.2.1 build /Users/cesar/Dev/latitude/poncho-ai/packages/browser
2
+ > @poncho-ai/browser@0.3.1 build /home/runner/work/poncho-ai/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 21.56 KB
11
- ESM ⚡️ Build success in 122ms
10
+ ESM dist/index.js 24.62 KB
11
+ ESM ⚡️ Build success in 52ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 4275ms
14
- DTS dist/index.d.ts 3.75 KB
13
+ DTS ⚡️ Build success in 4323ms
14
+ DTS dist/index.d.ts 4.13 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @poncho-ai/browser
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`972577d`](https://github.com/cesr/poncho-ai/commit/972577d255ab43c2c56f3c3464042a8a617b7f9e)]:
8
+ - @poncho-ai/sdk@1.2.0
9
+
10
+ ## 0.3.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [`12f2845`](https://github.com/cesr/poncho-ai/commit/12f28457c20e650640ff2a1c1dbece2a6e4a9ddd) Thanks [@cesr](https://github.com/cesr)! - Add browser storage persistence (cookies/localStorage survive restarts via configured storage provider) and new `browser_content` tool for fast text extraction from pages.
15
+
3
16
  ## 0.2.1
4
17
 
5
18
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -44,6 +44,10 @@ interface ScrollInputEvent {
44
44
  x?: number;
45
45
  y?: number;
46
46
  }
47
+ interface BrowserStoragePersistence {
48
+ save(json: string): Promise<void>;
49
+ load(): Promise<string | undefined>;
50
+ }
47
51
  interface BrowserConfig {
48
52
  viewport?: ViewportOptions;
49
53
  quality?: number;
@@ -52,6 +56,7 @@ interface BrowserConfig {
52
56
  sessionName?: string;
53
57
  executablePath?: string;
54
58
  headless?: boolean;
59
+ storagePersistence?: BrowserStoragePersistence;
55
60
  }
56
61
 
57
62
  type FrameListener = (frame: BrowserFrame) => void;
@@ -89,6 +94,11 @@ declare class BrowserSession {
89
94
  click(conversationId: string, ref: string): Promise<void>;
90
95
  type(conversationId: string, ref: string, text: string): Promise<void>;
91
96
  screenshot(conversationId: string): Promise<string>;
97
+ content(conversationId: string): Promise<{
98
+ text: string;
99
+ url: string;
100
+ title: string;
101
+ }>;
92
102
  scroll(conversationId: string, direction: "up" | "down", amount?: number): Promise<void>;
93
103
  closeTab(conversationId: string): Promise<void>;
94
104
  navigate(conversationId: string, action: string): Promise<void>;
@@ -101,10 +111,12 @@ declare class BrowserSession {
101
111
  injectPaste(conversationId: string, text: string): Promise<void>;
102
112
  injectScroll(conversationId: string, event: ScrollInputEvent): Promise<void>;
103
113
  saveState(storagePath: string): Promise<void>;
114
+ private persistStorageState;
115
+ private restoreStorageState;
104
116
  close(): Promise<void>;
105
117
  private emitStatus;
106
118
  }
107
119
 
108
120
  declare function createBrowserTools(getSession: () => BrowserSession, getConversationId: () => string): ToolDefinition[];
109
121
 
110
- export { type BrowserConfig, type BrowserFrame, BrowserSession, type BrowserStatus, type KeyboardInputEvent, type MouseInputEvent, type ScreencastOptions, type ScrollInputEvent, type ViewportOptions, createBrowserTools };
122
+ export { type BrowserConfig, type BrowserFrame, BrowserSession, type BrowserStatus, type BrowserStoragePersistence, type KeyboardInputEvent, type MouseInputEvent, type ScreencastOptions, type ScrollInputEvent, type ViewportOptions, createBrowserTools };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/session.ts
2
- import { resolve } from "path";
3
- import { homedir } from "os";
4
- import { mkdir } from "fs/promises";
2
+ import { resolve, join } from "path";
3
+ import { homedir, tmpdir } from "os";
4
+ import { mkdir, readFile, unlink } from "fs/promises";
5
5
  var BrowserManagerCtor;
6
6
  async function getBrowserManagerCtor() {
7
7
  if (!BrowserManagerCtor) {
@@ -72,6 +72,7 @@ var BrowserSession = class {
72
72
  try {
73
73
  const cdp = await mgr.getCDPSession();
74
74
  await cdp.send("Debugger.disable");
75
+ await this.restoreStorageState(cdp);
75
76
  } catch {
76
77
  }
77
78
  this.manager = mgr;
@@ -281,6 +282,19 @@ var BrowserSession = class {
281
282
  this.unlock();
282
283
  }
283
284
  }
285
+ async content(conversationId) {
286
+ await this.lock();
287
+ try {
288
+ const mgr = await this.ensureManager();
289
+ await this.switchToConversation(mgr, conversationId);
290
+ const page = mgr.getPage();
291
+ const text = await page.evaluate("document.body.innerText");
292
+ const title = await page.title();
293
+ return { text: text ?? "", url: page.url(), title: title ?? "" };
294
+ } finally {
295
+ this.unlock();
296
+ }
297
+ }
284
298
  async scroll(conversationId, direction, amount) {
285
299
  await this.lock();
286
300
  try {
@@ -316,6 +330,7 @@ var BrowserSession = class {
316
330
  if (t.tabIndex > tab.tabIndex) t.tabIndex--;
317
331
  }
318
332
  } else if (this.manager?.isLaunched()) {
333
+ await this.persistStorageState();
319
334
  try {
320
335
  await this.manager.close();
321
336
  } catch {
@@ -495,11 +510,55 @@ var BrowserSession = class {
495
510
  await mkdir(resolve(storagePath, ".."), { recursive: true });
496
511
  await this.manager.saveStorageState(storagePath);
497
512
  }
513
+ async persistStorageState() {
514
+ const persistence = this.config.storagePersistence;
515
+ if (!persistence || !this.manager?.isLaunched()) return;
516
+ try {
517
+ const tmpFile = join(tmpdir(), `poncho-browser-state-${this.sessionId}-${Date.now()}.json`);
518
+ await this.manager.saveStorageState(tmpFile);
519
+ const json = await readFile(tmpFile, "utf8");
520
+ await unlink(tmpFile).catch(() => {
521
+ });
522
+ await persistence.save(json);
523
+ console.log(`[poncho][browser] Storage state persisted (${json.length} bytes)`);
524
+ } catch (err) {
525
+ console.warn("[poncho][browser] Failed to persist storage state:", err?.message ?? err);
526
+ }
527
+ }
528
+ async restoreStorageState(cdp) {
529
+ const persistence = this.config.storagePersistence;
530
+ if (!persistence) return;
531
+ try {
532
+ const json = await persistence.load();
533
+ if (!json) return;
534
+ const state = JSON.parse(json);
535
+ if (state.cookies?.length) {
536
+ await cdp.send("Network.setCookies", { cookies: state.cookies });
537
+ console.log(`[poncho][browser] Restored ${state.cookies.length} cookies`);
538
+ }
539
+ if (state.origins?.length) {
540
+ const entries = {};
541
+ for (const origin of state.origins) {
542
+ if (origin.localStorage?.length) {
543
+ entries[origin.origin] = origin.localStorage;
544
+ }
545
+ }
546
+ if (Object.keys(entries).length) {
547
+ const script = `try{const __e=${JSON.stringify(entries)};const __i=__e[location.origin];if(__i)for(const{name:n,value:v}of __i)try{localStorage.setItem(n,v)}catch{}}catch{}`;
548
+ await cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: script });
549
+ console.log(`[poncho][browser] Registered localStorage restore for ${Object.keys(entries).length} origin(s)`);
550
+ }
551
+ }
552
+ } catch (err) {
553
+ console.warn("[poncho][browser] Failed to restore storage state:", err?.message ?? err);
554
+ }
555
+ }
498
556
  async close() {
499
557
  try {
500
558
  await this.stopScreencast();
501
559
  } catch {
502
560
  }
561
+ await this.persistStorageState();
503
562
  try {
504
563
  await this.manager?.close();
505
564
  } catch {
@@ -621,6 +680,19 @@ function createBrowserTools(getSession, getConversationId) {
621
680
  return { typed: text, into: ref };
622
681
  }
623
682
  },
683
+ {
684
+ name: "browser_content",
685
+ description: "Get the visible text content of the current page. Returns the page's text as a plain string (like what you'd see if you selected all text). Use this to read articles, tables, data, or any text on the page. Much faster and cheaper than screenshots for reading content.",
686
+ inputSchema: {
687
+ type: "object",
688
+ properties: {}
689
+ },
690
+ handler: async () => {
691
+ const session = getSession();
692
+ const result = await session.content(getConversationId());
693
+ return { url: result.url, title: result.title, text: result.text };
694
+ }
695
+ },
624
696
  {
625
697
  name: "browser_screenshot",
626
698
  description: "Take a screenshot of the current page. Returns the image so you can see exactly what the page looks like. Use this when you need to see visual layout, verify actions, or read content that isn't in the accessibility tree.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/browser",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Browser automation for Poncho agents, powered by agent-browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "agent-browser": "^0.15.1",
24
- "@poncho-ai/sdk": "1.1.1"
24
+ "@poncho-ai/sdk": "1.2.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsup": "^8.0.0",
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export type {
4
4
  BrowserConfig,
5
5
  BrowserFrame,
6
6
  BrowserStatus,
7
+ BrowserStoragePersistence,
7
8
  ViewportOptions,
8
9
  ScreencastOptions,
9
10
  MouseInputEvent,
package/src/session.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { resolve } from "node:path";
2
- import { homedir } from "node:os";
3
- import { mkdir } from "node:fs/promises";
1
+ import { resolve, join } from "node:path";
2
+ import { homedir, tmpdir } from "node:os";
3
+ import { mkdir, readFile, unlink } from "node:fs/promises";
4
4
  import type {
5
5
  BrowserConfig,
6
6
  BrowserFrame,
@@ -19,7 +19,7 @@ let BrowserManagerCtor: (new () => BrowserManagerInstance) | undefined;
19
19
  interface BrowserManagerInstance {
20
20
  isLaunched(): boolean;
21
21
  launch(options: Record<string, unknown>): Promise<void>;
22
- getPage(): { url(): string; title(): Promise<string>; screenshot(opts?: Record<string, unknown>): Promise<Buffer>; goBack(): Promise<unknown>; goForward(): Promise<unknown> };
22
+ getPage(): { url(): string; title(): Promise<string>; screenshot(opts?: Record<string, unknown>): Promise<Buffer>; goBack(): Promise<unknown>; goForward(): Promise<unknown>; evaluate(fn: string | (() => unknown)): Promise<unknown> };
23
23
  getSnapshot(options?: { interactive?: boolean; compact?: boolean }): Promise<{ tree: string; refs: Record<string, unknown> }>;
24
24
  getLocatorFromRef(ref: string): { click(): Promise<void> } | null;
25
25
  getLocator(selector: string): { fill(text: string): Promise<void>; click(): Promise<void> };
@@ -134,6 +134,7 @@ export class BrowserSession {
134
134
  try {
135
135
  const cdp = await mgr.getCDPSession();
136
136
  await cdp.send("Debugger.disable");
137
+ await this.restoreStorageState(cdp);
137
138
  } catch { /* best-effort */ }
138
139
 
139
140
  this.manager = mgr;
@@ -346,6 +347,20 @@ export class BrowserSession {
346
347
  }
347
348
  }
348
349
 
350
+ async content(conversationId: string): Promise<{ text: string; url: string; title: string }> {
351
+ await this.lock();
352
+ try {
353
+ const mgr = await this.ensureManager();
354
+ await this.switchToConversation(mgr, conversationId);
355
+ const page = mgr.getPage();
356
+ const text = (await page.evaluate("document.body.innerText")) as string;
357
+ const title = await page.title();
358
+ return { text: text ?? "", url: page.url(), title: title ?? "" };
359
+ } finally {
360
+ this.unlock();
361
+ }
362
+ }
363
+
349
364
  async scroll(conversationId: string, direction: "up" | "down", amount?: number): Promise<void> {
350
365
  await this.lock();
351
366
  try {
@@ -354,8 +369,7 @@ export class BrowserSession {
354
369
  const page = mgr.getPage();
355
370
  const pixels = amount ?? 600;
356
371
  const delta = direction === "down" ? pixels : -pixels;
357
- await (page as unknown as { evaluate(fn: string): Promise<void> })
358
- .evaluate(`window.scrollBy(0, ${delta})`);
372
+ await page.evaluate(`window.scrollBy(0, ${delta})`);
359
373
  } finally {
360
374
  this.unlock();
361
375
  }
@@ -379,6 +393,7 @@ export class BrowserSession {
379
393
  if (t.tabIndex > tab.tabIndex) t.tabIndex--;
380
394
  }
381
395
  } else if (this.manager?.isLaunched()) {
396
+ await this.persistStorageState();
382
397
  try { await this.manager.close(); } catch { /* */ }
383
398
  this.manager = undefined;
384
399
  }
@@ -563,8 +578,58 @@ export class BrowserSession {
563
578
  await this.manager.saveStorageState(storagePath);
564
579
  }
565
580
 
581
+ private async persistStorageState(): Promise<void> {
582
+ const persistence = this.config.storagePersistence;
583
+ if (!persistence || !this.manager?.isLaunched()) return;
584
+ try {
585
+ const tmpFile = join(tmpdir(), `poncho-browser-state-${this.sessionId}-${Date.now()}.json`);
586
+ await this.manager.saveStorageState(tmpFile);
587
+ const json = await readFile(tmpFile, "utf8");
588
+ await unlink(tmpFile).catch(() => {});
589
+ await persistence.save(json);
590
+ console.log(`[poncho][browser] Storage state persisted (${json.length} bytes)`);
591
+ } catch (err) {
592
+ console.warn("[poncho][browser] Failed to persist storage state:", (err as Error)?.message ?? err);
593
+ }
594
+ }
595
+
596
+ private async restoreStorageState(
597
+ cdp: { send(method: string, params?: Record<string, unknown>): Promise<unknown> },
598
+ ): Promise<void> {
599
+ const persistence = this.config.storagePersistence;
600
+ if (!persistence) return;
601
+ try {
602
+ const json = await persistence.load();
603
+ if (!json) return;
604
+ const state = JSON.parse(json) as {
605
+ cookies?: Array<Record<string, unknown>>;
606
+ origins?: Array<{ origin: string; localStorage: Array<{ name: string; value: string }> }>;
607
+ };
608
+ if (state.cookies?.length) {
609
+ await cdp.send("Network.setCookies", { cookies: state.cookies });
610
+ console.log(`[poncho][browser] Restored ${state.cookies.length} cookies`);
611
+ }
612
+ if (state.origins?.length) {
613
+ const entries: Record<string, Array<{ name: string; value: string }>> = {};
614
+ for (const origin of state.origins) {
615
+ if (origin.localStorage?.length) {
616
+ entries[origin.origin] = origin.localStorage;
617
+ }
618
+ }
619
+ if (Object.keys(entries).length) {
620
+ const script = `try{const __e=${JSON.stringify(entries)};const __i=__e[location.origin];if(__i)for(const{name:n,value:v}of __i)try{localStorage.setItem(n,v)}catch{}}catch{}`;
621
+ await cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: script });
622
+ console.log(`[poncho][browser] Registered localStorage restore for ${Object.keys(entries).length} origin(s)`);
623
+ }
624
+ }
625
+ } catch (err) {
626
+ console.warn("[poncho][browser] Failed to restore storage state:", (err as Error)?.message ?? err);
627
+ }
628
+ }
629
+
566
630
  async close(): Promise<void> {
567
631
  try { await this.stopScreencast(); } catch { /* */ }
632
+ await this.persistStorageState();
568
633
  try { await this.manager?.close(); } catch { /* */ }
569
634
  this.manager = undefined;
570
635
  for (const [cid, tab] of this.tabs) {
package/src/tools.ts CHANGED
@@ -101,6 +101,22 @@ export function createBrowserTools(
101
101
  return { typed: text, into: ref };
102
102
  },
103
103
  },
104
+ {
105
+ name: "browser_content",
106
+ description:
107
+ "Get the visible text content of the current page. Returns the page's text as a plain string (like what you'd " +
108
+ "see if you selected all text). Use this to read articles, tables, data, or any text on the page. " +
109
+ "Much faster and cheaper than screenshots for reading content.",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {},
113
+ },
114
+ handler: async () => {
115
+ const session = getSession();
116
+ const result = await session.content(getConversationId());
117
+ return { url: result.url, title: result.title, text: result.text };
118
+ },
119
+ },
104
120
  {
105
121
  name: "browser_screenshot",
106
122
  description:
package/src/types.ts CHANGED
@@ -49,6 +49,11 @@ export interface ScrollInputEvent {
49
49
  y?: number;
50
50
  }
51
51
 
52
+ export interface BrowserStoragePersistence {
53
+ save(json: string): Promise<void>;
54
+ load(): Promise<string | undefined>;
55
+ }
56
+
52
57
  export interface BrowserConfig {
53
58
  viewport?: ViewportOptions;
54
59
  quality?: number;
@@ -57,4 +62,5 @@ export interface BrowserConfig {
57
62
  sessionName?: string;
58
63
  executablePath?: string;
59
64
  headless?: boolean;
65
+ storagePersistence?: BrowserStoragePersistence;
60
66
  }
@@ -1,12 +0,0 @@
1
-
2
- > @poncho-ai/browser@0.1.0 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
- include: **/*.{test,spec}.?(c|m)[jt]s?(x)
9
- 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.*
10
- No test files found, exiting with code 0
11
-
12
- watch exclude: **/node_modules/**, **/dist/**