@poncho-ai/harness 0.59.16 → 0.59.18

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/harness@0.59.16 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
2
+ > @poncho-ai/harness@0.59.18 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
3
3
  > node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
4
4
 
5
5
  [embed-docs] Generated poncho-docs.ts with 4 topics
@@ -8,9 +8,9 @@
8
8
  CLI tsup v8.5.1
9
9
  CLI Target: es2022
10
10
  ESM Build start
11
- ESM dist/index.js 571.49 KB
11
+ ESM dist/index.js 572.34 KB
12
12
  ESM dist/isolate-F2PPSUL6.js 53.82 KB
13
- ESM ⚡️ Build success in 239ms
13
+ ESM ⚡️ Build success in 214ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 6987ms
16
- DTS dist/index.d.ts 103.12 KB
15
+ DTS ⚡️ Build success in 7534ms
16
+ DTS dist/index.d.ts 104.01 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @poncho-ai/harness
2
2
 
3
+ ## 0.59.18
4
+
5
+ ### Patch Changes
6
+
7
+ - [#184](https://github.com/cesr/poncho-ai/pull/184) [`12ce2be`](https://github.com/cesr/poncho-ai/commit/12ce2be01c9d98b1d9aa634d4d8051c4c0094a44) Thanks [@cesr](https://github.com/cesr)! - Add `browser_download` so the agent can save files from the browser into the
8
+ VFS. The tool fetches a file using the page's logged-in session (so it works
9
+ for files behind a login) and writes the bytes straight to the tenant's VFS via
10
+ `ToolContext.vfs` — never through the model. `url` defaults to the current page,
11
+ or pass a same-origin link's href. The fetch runs inside the page (`evaluate`),
12
+ so it works identically for local and remote/cloud browsers (bytes return over
13
+ CDP). Capped at 25 MB. The harness browser system prompt now documents it under
14
+ a "Saving files" section.
15
+
16
+ ## 0.59.17
17
+
18
+ ### Patch Changes
19
+
20
+ - [`963a0e3`](https://github.com/cesr/poncho-ai/commit/963a0e34b9f362ab21b7b8d94835f37713d4e5da) Thanks [@cesr](https://github.com/cesr)! - Browser config: support embedding apps that own per-tenant browser sessions
21
+ and the live viewport.
22
+ - `browser.sessionName` now overrides the browser session id (previously
23
+ always `poncho-${agentId}`). Lets a multi-tenant host isolate sessions
24
+ per user even when every user shares one agent definition.
25
+ - `browser.storagePersistence` lets the host supply its own
26
+ `{ save(json), load() }` for the browser storage state (cookies +
27
+ localStorage). When provided, the harness skips its built-in file-based
28
+ persistence — so state can live in an encrypted/DB-backed per-tenant store.
29
+ - `browser.hostManagedStreaming` makes the harness skip wiring
30
+ `onFrame`/`onStatus` during `run()`, so no `browser:frame` / `browser:status`
31
+ events are emitted into the agent event stream. The host subscribes to the
32
+ `BrowserSession` listeners directly and streams frames out-of-band (and can
33
+ keep the viewport interactive while the agent is idle).
34
+
3
35
  ## 0.59.16
4
36
 
5
37
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -820,6 +820,21 @@ interface PonchoConfig extends McpConfig {
820
820
  /** Connect to an existing browser via CDP URL or port.
821
821
  * Mutually exclusive with `provider`. */
822
822
  cdpUrl?: string;
823
+ /** Host-supplied persistence for the browser's storage state
824
+ * (cookies + localStorage as a Playwright storageState JSON). When
825
+ * provided, the harness uses this instead of its built-in file-based
826
+ * persistence — letting an embedding app store session state in its
827
+ * own (e.g. encrypted, per-tenant) backend. */
828
+ storagePersistence?: {
829
+ save(json: string): Promise<void>;
830
+ load(): Promise<string | undefined>;
831
+ };
832
+ /** When true, the host owns the live viewport: the harness will NOT
833
+ * wire frame/status listeners during run() (so no `browser:frame` /
834
+ * `browser:status` events are emitted into the agent event stream).
835
+ * The host is expected to subscribe to the BrowserSession's
836
+ * `onFrame`/`onStatus` directly and stream frames out-of-band. */
837
+ hostManagedStreaming?: boolean;
823
838
  };
824
839
  }
825
840
  declare const resolveStateConfig: (config: PonchoConfig | undefined) => StateConfig | undefined;
package/dist/index.js CHANGED
@@ -10651,10 +10651,13 @@ var AgentHarness = class _AgentHarness {
10651
10651
  this._browserMod = browserMod;
10652
10652
  const browserCfg = typeof config.browser === "object" ? { ...config.browser } : {};
10653
10653
  const agentId = this.parsedAgent?.frontmatter.id ?? this.parsedAgent?.frontmatter.name ?? "default";
10654
- const sessionId = `poncho-${agentId}`;
10655
- const storagePersistence = await this.buildBrowserStoragePersistence(config, sessionId);
10656
- if (storagePersistence) {
10657
- browserCfg.storagePersistence = storagePersistence;
10654
+ const sessionName = typeof config.browser === "object" ? config.browser.sessionName : void 0;
10655
+ const sessionId = sessionName ?? `poncho-${agentId}`;
10656
+ if (!browserCfg.storagePersistence) {
10657
+ const storagePersistence = await this.buildBrowserStoragePersistence(config, sessionId);
10658
+ if (storagePersistence) {
10659
+ browserCfg.storagePersistence = storagePersistence;
10660
+ }
10658
10661
  }
10659
10662
  const session = new browserMod.BrowserSession(sessionId, browserCfg);
10660
10663
  this._browserSession = session;
@@ -10852,6 +10855,9 @@ Browser sessions (cookies, localStorage, login state) are automatically saved an
10852
10855
  - Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
10853
10856
  - The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
10854
10857
 
10858
+ ### Saving files
10859
+ To keep a file the page offers (a PDF, CSV, image, export, etc.), use \`browser_download\` to save it into the user's filesystem. It fetches with the browser's logged-in session, so it works for files behind a login. For a download link, get its href from a snapshot and pass it as \`url\`; for a file that opens in the browser, navigate to it and call \`browser_download\` with no \`url\`. The bytes go straight to the filesystem (not through the chat), so prefer this over screenshotting or copy-pasting file contents.
10860
+
10855
10861
  ### Tabs and resources
10856
10862
  Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.` : "";
10857
10863
  const mainMemory = await memoryPromise;
@@ -10982,7 +10988,8 @@ ${this.skillFingerprint}`;
10982
10988
  const browserEventQueue = [];
10983
10989
  const browserCleanups = [];
10984
10990
  const browserSession = this._browserSession;
10985
- if (browserSession) {
10991
+ const hostManagedStreaming = typeof this.loadedConfig?.browser === "object" && this.loadedConfig.browser.hostManagedStreaming === true;
10992
+ if (browserSession && !hostManagedStreaming) {
10986
10993
  browserCleanups.push(
10987
10994
  browserSession.onFrame(conversationId, makeBrowserFrameListener(browserEventQueue)),
10988
10995
  browserSession.onStatus(conversationId, makeBrowserStatusListener(browserEventQueue))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.59.16",
3
+ "version": "0.59.18",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
package/src/config.ts CHANGED
@@ -294,6 +294,21 @@ export interface PonchoConfig extends McpConfig {
294
294
  /** Connect to an existing browser via CDP URL or port.
295
295
  * Mutually exclusive with `provider`. */
296
296
  cdpUrl?: string;
297
+ /** Host-supplied persistence for the browser's storage state
298
+ * (cookies + localStorage as a Playwright storageState JSON). When
299
+ * provided, the harness uses this instead of its built-in file-based
300
+ * persistence — letting an embedding app store session state in its
301
+ * own (e.g. encrypted, per-tenant) backend. */
302
+ storagePersistence?: {
303
+ save(json: string): Promise<void>;
304
+ load(): Promise<string | undefined>;
305
+ };
306
+ /** When true, the host owns the live viewport: the harness will NOT
307
+ * wire frame/status listeners during run() (so no `browser:frame` /
308
+ * `browser:status` events are emitted into the agent event stream).
309
+ * The host is expected to subscribe to the BrowserSession's
310
+ * `onFrame`/`onStatus` directly and stream frames out-of-band. */
311
+ hostManagedStreaming?: boolean;
297
312
  };
298
313
  }
299
314
 
package/src/harness.ts CHANGED
@@ -2123,11 +2123,18 @@ export class AgentHarness {
2123
2123
  this._browserMod = browserMod;
2124
2124
  const browserCfg: Record<string, unknown> = typeof config.browser === "object" ? { ...config.browser } : {};
2125
2125
  const agentId = this.parsedAgent?.frontmatter.id ?? this.parsedAgent?.frontmatter.name ?? "default";
2126
- const sessionId = `poncho-${agentId}`;
2127
-
2128
- const storagePersistence = await this.buildBrowserStoragePersistence(config, sessionId);
2129
- if (storagePersistence) {
2130
- browserCfg.storagePersistence = storagePersistence;
2126
+ // Let an embedding app override the session id (e.g. to make it per-tenant
2127
+ // when many users share one agent definition). Falls back to the agent id.
2128
+ const sessionName = typeof config.browser === "object" ? config.browser.sessionName : undefined;
2129
+ const sessionId = sessionName ?? `poncho-${agentId}`;
2130
+
2131
+ // Only build the built-in file persistence when the host didn't supply its
2132
+ // own. A host-provided `storagePersistence` (e.g. encrypted/DB-backed) wins.
2133
+ if (!browserCfg.storagePersistence) {
2134
+ const storagePersistence = await this.buildBrowserStoragePersistence(config, sessionId);
2135
+ if (storagePersistence) {
2136
+ browserCfg.storagePersistence = storagePersistence;
2137
+ }
2131
2138
  }
2132
2139
 
2133
2140
  const session = new browserMod.BrowserSession(sessionId, browserCfg);
@@ -2369,6 +2376,9 @@ Browser sessions (cookies, localStorage, login state) are automatically saved an
2369
2376
  - Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
2370
2377
  - The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
2371
2378
 
2379
+ ### Saving files
2380
+ To keep a file the page offers (a PDF, CSV, image, export, etc.), use \`browser_download\` to save it into the user's filesystem. It fetches with the browser's logged-in session, so it works for files behind a login. For a download link, get its href from a snapshot and pass it as \`url\`; for a file that opens in the browser, navigate to it and call \`browser_download\` with no \`url\`. The bytes go straight to the filesystem (not through the chat), so prefer this over screenshotting or copy-pasting file contents.
2381
+
2372
2382
  ### Tabs and resources
2373
2383
  Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.`
2374
2384
  : "";
@@ -2577,7 +2587,14 @@ Code is wrapped in an async IIFE — use \`return\` to return a value to the too
2577
2587
  profileDir: string;
2578
2588
  isLaunched: boolean }
2579
2589
  | undefined;
2580
- if (browserSession) {
2590
+ // When the host owns the live viewport (`browser.hostManagedStreaming`),
2591
+ // it subscribes to onFrame/onStatus directly and streams frames out-of-band.
2592
+ // Skip the during-run wiring so frames don't double-emit into the agent
2593
+ // event stream (and don't flood any host-side event buffer).
2594
+ const hostManagedStreaming =
2595
+ typeof this.loadedConfig?.browser === "object" &&
2596
+ this.loadedConfig.browser.hostManagedStreaming === true;
2597
+ if (browserSession && !hostManagedStreaming) {
2581
2598
  browserCleanups.push(
2582
2599
  browserSession.onFrame(conversationId, makeBrowserFrameListener(browserEventQueue)),
2583
2600
  browserSession.onStatus(conversationId, makeBrowserStatusListener(browserEventQueue)),