@omercnet/paseo-shared-browser 4.1.6 → 4.1.7

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.
package/README.md CHANGED
@@ -40,7 +40,7 @@ organization names.
40
40
  controller when a takeover is needed, and continues the selected action after control is granted.
41
41
  Closing a tab or browser still requires its own confirmation. Actions that need a fresh frame wait
42
42
  for one before sending input.
43
- - The plugin owns `agent-browser` version `0.41.0`, its IPC directory, and the Chromium executable.
43
+ - The plugin owns `agent-browser` version `0.41.2`, its IPC directory, and the Chromium executable.
44
44
  It strips inherited `AGENT_BROWSER_*` variables and sets `AGENT_BROWSER_SOCKET_DIR`,
45
45
  `AGENT_BROWSER_IDLE_TIMEOUT_MS=0`, `AGENT_BROWSER_STREAM_PORT=0`, and
46
46
  `AGENT_BROWSER_NO_AUTO_DIALOG=1` itself.
@@ -105,7 +105,14 @@ from `/usr/bin/chromium`. Install a non-Snap Chromium build with the system pack
105
105
  adding the plugin. Set `PASEO_SHARED_BROWSER_CHROMIUM_EXECUTABLE` to an absolute path when Chromium
106
106
  is installed elsewhere. The plugin does not emulate x64 Chromium on Linux.
107
107
 
108
- On Windows, the upstream installer retains its Chrome download cache under the OS user profile.
108
+ Runtime preparation keeps no Chrome download cache. When it uses the upstream installer (every
109
+ platform except Linux ARM64, unless `PASEO_SHARED_BROWSER_CHROMIUM_EXECUTABLE` is set), the
110
+ installer runs with a private home inside the staging directory, so each preparation downloads the
111
+ current Chrome for Testing archive (about 200 MiB) and deletes the installer's copy after staging
112
+ it. On a slow network that download can exceed the 180-second install timeout; set
113
+ `PASEO_SHARED_BROWSER_CHROMIUM_EXECUTABLE` to an existing Chromium to skip it. On Windows the
114
+ override is not staged into the runtime, so keep it set in the daemon environment as well.
115
+
109
116
  Before upgrading the plugin on Windows, close active Shared Browser sessions and stop the Paseo
110
117
  daemon; Windows does not allow the installer to replace runtime executables that are still running.
111
118
 
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-shared-browser",
3
- "version": "4.1.6",
3
+ "version": "4.1.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@omercnet/paseo-shared-browser",
9
- "version": "4.1.6",
9
+ "version": "4.1.7",
10
10
  "dependencies": {
11
11
  "@getpaseo/plugin": "0.15.0",
12
12
  "@modelcontextprotocol/sdk": "1.32.2",
13
- "agent-browser": "0.41.0",
13
+ "agent-browser": "0.41.2",
14
14
  "esbuild": "0.28.2",
15
15
  "zod": "4.6.7"
16
16
  }
@@ -606,9 +606,9 @@
606
606
  }
607
607
  },
608
608
  "node_modules/agent-browser": {
609
- "version": "0.41.0",
610
- "resolved": "https://registry.npmjs.org/agent-browser/-/agent-browser-0.41.0.tgz",
611
- "integrity": "sha512-XJtBouE5+gGUKyZU1COSmnxU6MKIlmLE2oEyHzl0ohLVOhuRjx9kqQ9vOqxiiW/Gbvi4JPF4Oz777Mn3FGiHAA==",
609
+ "version": "0.41.2",
610
+ "resolved": "https://registry.npmjs.org/agent-browser/-/agent-browser-0.41.2.tgz",
611
+ "integrity": "sha512-/agCKB5rw1EEK8SKWo4zid7a5kOwlclqALC0P3c69buJvKdhrl/dJkKdfLqGGaiNIbWzRdqqT0PxZVkwVEf2fg==",
612
612
  "hasInstallScript": true,
613
613
  "license": "Apache-2.0",
614
614
  "bin": {
@@ -1233,9 +1233,9 @@
1233
1233
  }
1234
1234
  },
1235
1235
  "node_modules/i18next": {
1236
- "version": "26.7.1",
1237
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.7.1.tgz",
1238
- "integrity": "sha512-cX+3RRVczel/V2kCMTACckSGSpqgtWYEA6beXNYVms7dOQiHojdPYHD26lT4IKimNeoGWrLvtPgjftbRvmZJVw==",
1236
+ "version": "26.7.2",
1237
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.7.2.tgz",
1238
+ "integrity": "sha512-RsOY3BeaCDdkk52NeJSFAXELQ2rQXBO/tOub6L8ue2ICPfR/FRDWDOfEDg4xMPd1SqO5goVYQ6i72qWEUB/PPw==",
1239
1239
  "funding": [
1240
1240
  {
1241
1241
  "type": "individual",
@@ -1283,9 +1283,9 @@
1283
1283
  "license": "ISC"
1284
1284
  },
1285
1285
  "node_modules/ip-address": {
1286
- "version": "10.7.5",
1287
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz",
1288
- "integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==",
1286
+ "version": "10.7.6",
1287
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.6.tgz",
1288
+ "integrity": "sha512-00LYkM6byILa9eQIx6qoaXCM40xTLd56AZ4vyaPW3YTyb49VZYPo5xsiAq4tuQ54GH4R7gVdYScJ1B7B77PuTg==",
1289
1289
  "license": "MIT",
1290
1290
  "engines": {
1291
1291
  "node": ">= 12"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-shared-browser",
3
- "version": "4.1.6",
3
+ "version": "4.1.7",
4
4
  "description": "One real Chromium session per Paseo workspace, shared live across every connected client.",
5
5
  "keywords": [
6
6
  "browser",
@@ -29,6 +29,7 @@
29
29
  "index.client.tsx",
30
30
  "index.server.ts",
31
31
  "client",
32
+ "scripts/chromium-release.mjs",
32
33
  "scripts/prepare-dependencies.mjs",
33
34
  "scripts/prepare-runtime.mjs",
34
35
  "scripts/replace-runtime.mjs",
@@ -48,8 +49,8 @@
48
49
  "check:write": "biome check --write .",
49
50
  "typecheck": "tsc --noEmit",
50
51
  "prepare:runtime": "node scripts/prepare-runtime.mjs",
51
- "test": "vitest run tests/contracts.test.ts tests/lease.test.ts tests/runtime-lifecycle.test.ts tests/agent-access.test.ts tests/agent-input-safety.test.ts tests/tabs.test.ts tests/tabs-runtime-loss.test.ts tests/composer-pills.test.ts tests/sidebar.test.ts tests/pointer-mapping.test.ts tests/frame-lifecycle.test.ts tests/viewport-resync.test.ts client server shared",
52
- "test:unit": "vitest run tests/contracts.test.ts tests/lease.test.ts tests/runtime-lifecycle.test.ts tests/agent-access.test.ts tests/agent-input-safety.test.ts tests/tabs.test.ts tests/tabs-runtime-loss.test.ts tests/composer-pills.test.ts tests/sidebar.test.ts tests/pointer-mapping.test.ts tests/frame-lifecycle.test.ts tests/viewport-resync.test.ts client server shared",
52
+ "test": "vitest run tests/contracts.test.ts tests/lease.test.ts tests/runtime-lifecycle.test.ts tests/chromium-release.test.ts tests/prepare-runtime.test.ts tests/agent-access.test.ts tests/agent-input-safety.test.ts tests/tabs.test.ts tests/tabs-runtime-loss.test.ts tests/composer-pills.test.ts tests/sidebar.test.ts tests/pointer-mapping.test.ts tests/frame-lifecycle.test.ts tests/viewport-resync.test.ts client server shared",
53
+ "test:unit": "vitest run tests/contracts.test.ts tests/lease.test.ts tests/runtime-lifecycle.test.ts tests/chromium-release.test.ts tests/prepare-runtime.test.ts tests/agent-access.test.ts tests/agent-input-safety.test.ts tests/tabs.test.ts tests/tabs-runtime-loss.test.ts tests/composer-pills.test.ts tests/sidebar.test.ts tests/pointer-mapping.test.ts tests/frame-lifecycle.test.ts tests/viewport-resync.test.ts client server shared",
53
54
  "test:smoke": "vitest run tests/browser.smoke.test.ts tests/browser.drag-cancel.smoke.test.ts tests/xvfb.smoke.test.ts tests/browser.failed-open.smoke.test.ts tests/browser.background-tab.smoke.test.ts --testTimeout=30000",
54
55
  "test:tabs": "vitest run tests/browser-tabs.smoke.test.ts --testTimeout=30000",
55
56
  "test:video": "vitest run tests/video.smoke.test.ts --testTimeout=30000"
@@ -57,7 +58,7 @@
57
58
  "dependencies": {
58
59
  "@getpaseo/plugin": "0.15.0",
59
60
  "@modelcontextprotocol/sdk": "1.32.2",
60
- "agent-browser": "0.41.0",
61
+ "agent-browser": "0.41.2",
61
62
  "esbuild": "0.28.2",
62
63
  "zod": "4.6.7"
63
64
  },
@@ -0,0 +1,29 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, readdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ const execFileAsync = promisify(execFile);
7
+
8
+ /**
9
+ * Runs `agent-browser install` against a private home and returns the
10
+ * Chromium release directory it produced. agent-browser resolves its browser
11
+ * cache with Rust's std::env::home_dir(): HOME on Unix and USERPROFILE on
12
+ * Windows, so both point at installHome.
13
+ */
14
+ export async function installChromiumRelease(agentBrowser, args, installHome) {
15
+ await mkdir(installHome, { recursive: true, mode: 0o700 });
16
+ await execFileAsync(agentBrowser, args, {
17
+ env: { ...process.env, HOME: installHome, USERPROFILE: installHome },
18
+ timeout: 180_000,
19
+ windowsHide: true,
20
+ });
21
+
22
+ const browsersRoot = join(installHome, ".agent-browser", "browsers");
23
+ const releases = (await readdir(browsersRoot, { withFileTypes: true }))
24
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith("chrome-"))
25
+ .map((entry) => entry.name)
26
+ .sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
27
+ if (!releases[0]) throw new Error("agent-browser install did not produce a Chromium release");
28
+ return join(browsersRoot, releases[0]);
29
+ }
@@ -3,12 +3,13 @@ import { randomUUID } from "node:crypto";
3
3
  import { constants } from "node:fs";
4
4
  import { access, chmod, cp, lstat, mkdir, readdir, rm, symlink, writeFile } from "node:fs/promises";
5
5
  import { createRequire } from "node:module";
6
- import { arch, homedir, platform, userInfo } from "node:os";
6
+ import { arch, homedir, platform } from "node:os";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { promisify } from "node:util";
10
10
  import { build } from "esbuild";
11
- import { replaceRuntime } from "./replace-runtime.mjs";
11
+ import { installChromiumRelease } from "./chromium-release.mjs";
12
+ import { discardRuntime, replaceRuntime } from "./replace-runtime.mjs";
12
13
 
13
14
  const require = createRequire(import.meta.url);
14
15
 
@@ -18,7 +19,7 @@ function resolveDependencyRoot(packageName) {
18
19
  }
19
20
 
20
21
  const execFileAsync = promisify(execFile);
21
- const expectedVersion = "0.41.0";
22
+ const expectedVersion = "0.41.2";
22
23
  const hostPlatform = platform();
23
24
  const hostArch = arch();
24
25
  const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -163,26 +164,8 @@ async function installChromium() {
163
164
  return executable;
164
165
  }
165
166
 
166
- // Rust's dirs::home_dir() ignores HOME on Windows and resolves the profile
167
- // through SHGetKnownFolderPath. Use that cache there; Unix stays isolated.
168
- const installHome =
169
- hostPlatform === "win32" ? userInfo().homedir : join(stagingRoot, "install-home");
170
- if (hostPlatform !== "win32") {
171
- await mkdir(installHome, { recursive: true, mode: 0o700 });
172
- }
173
- await execFileAsync(runtimeEntry, ["install"], {
174
- env: hostPlatform === "win32" ? process.env : { ...process.env, HOME: installHome },
175
- timeout: 180_000,
176
- windowsHide: true,
177
- });
178
-
179
- const browsersRoot = join(installHome, ".agent-browser", "browsers");
180
- const releases = (await readdir(browsersRoot, { withFileTypes: true }))
181
- .filter((entry) => entry.isDirectory() && entry.name.startsWith("chrome-"))
182
- .map((entry) => entry.name)
183
- .sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
184
- const releaseDir = releases[0] && join(browsersRoot, releases[0]);
185
- if (!releaseDir) throw new Error("agent-browser install did not produce a Chromium release");
167
+ const installHome = join(stagingRoot, "install-home");
168
+ const releaseDir = await installChromiumRelease(runtimeEntry, ["install"], installHome);
186
169
  const chrome = installedChromiumExecutable(releaseDir);
187
170
  await requireExecutable(chrome, "Installed Chromium executable");
188
171
  await cp(releaseDir, join(stagingRoot, "chromium"), {
@@ -200,7 +183,7 @@ async function installChromium() {
200
183
  );
201
184
  await chmod(runtimeChromiumEntry, 0o700);
202
185
  }
203
- if (hostPlatform !== "win32") await rm(installHome, { recursive: true, force: true });
186
+ await rm(installHome, { recursive: true, force: true });
204
187
  return runtimeChromiumEntry;
205
188
  }
206
189
 
@@ -222,32 +205,38 @@ async function stageAgentBrowserEntry() {
222
205
  }
223
206
 
224
207
  await rm(stagingRoot, { recursive: true, force: true });
225
- await mkdir(join(runtimeModules, ".bin"), { recursive: true, mode: 0o700 });
226
- await chmod(pluginDataRoot, 0o700);
227
- await cp(packagedAgentBrowser, join(runtimeModules, "agent-browser"), {
228
- recursive: true,
229
- force: true,
230
- });
231
- await stageAgentBrowserEntry();
232
-
233
- const binaryOverride = process.env.PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY;
234
- if (binaryOverride)
235
- await validateAgentBrowser(
236
- await requireExecutable(binaryOverride, "PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY"),
208
+ try {
209
+ await mkdir(join(runtimeModules, ".bin"), { recursive: true, mode: 0o700 });
210
+ await chmod(pluginDataRoot, 0o700);
211
+ await cp(packagedAgentBrowser, join(runtimeModules, "agent-browser"), {
212
+ recursive: true,
213
+ force: true,
214
+ });
215
+ await stageAgentBrowserEntry();
216
+
217
+ const binaryOverride = process.env.PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY;
218
+ if (binaryOverride)
219
+ await validateAgentBrowser(
220
+ await requireExecutable(binaryOverride, "PASEO_SHARED_BROWSER_AGENT_BROWSER_BINARY"),
221
+ );
222
+ await validateAgentBrowser(await requireExecutable(runtimeEntry, "Packaged agent-browser entry"));
223
+ const chromiumEntry = await installChromium();
224
+ await bundleRuntimeEntries();
225
+
226
+ await writeFile(
227
+ join(stagingRoot, "package.json"),
228
+ `${JSON.stringify({ private: true, type: "commonjs" }, null, 2)}\n`,
229
+ { mode: 0o600 },
237
230
  );
238
- await validateAgentBrowser(await requireExecutable(runtimeEntry, "Packaged agent-browser entry"));
239
- const chromiumEntry = await installChromium();
240
- await bundleRuntimeEntries();
241
-
242
- await writeFile(
243
- join(stagingRoot, "package.json"),
244
- `${JSON.stringify({ private: true, type: "commonjs" }, null, 2)}\n`,
245
- { mode: 0o600 },
246
- );
247
- await applyPrivatePermissions(stagingRoot);
248
- if (hostPlatform !== "win32") {
249
- await validateChromium(await requireExecutable(chromiumEntry, "Staged Chromium executable"));
231
+ await applyPrivatePermissions(stagingRoot);
232
+ if (hostPlatform !== "win32") {
233
+ await validateChromium(await requireExecutable(chromiumEntry, "Staged Chromium executable"));
234
+ }
235
+ } catch (error) {
236
+ // A failed preparation must not leave a partial runtime, which can hold a Chromium download.
237
+ await discardRuntime(stagingRoot, error);
250
238
  }
239
+ // Outside the cleanup: replaceRuntime can reject after publishing, when the stage is the live runtime.
251
240
  await replaceRuntime(stagingRoot, runtimeRoot);
252
241
 
253
242
  console.log(`Prepared immutable Shared Browser runtime assets in ${stagingRoot}`);
@@ -13,3 +13,20 @@ export async function replaceRuntime(stagingRoot, runtimeRoot) {
13
13
  await rm(pendingPointer, { force: true });
14
14
  }
15
15
  }
16
+
17
+ /**
18
+ * Removes a staging runtime after a failed preparation and rethrows the
19
+ * preparation error. Windows file locks can outlast the retries; a cleanup
20
+ * failure is reported but never replaces the original diagnostic.
21
+ */
22
+ export async function discardRuntime(stagingRoot, preparationError, remove = rm) {
23
+ try {
24
+ await remove(stagingRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
25
+ } catch (cleanupError) {
26
+ console.warn(
27
+ `Could not remove the failed Shared Browser runtime at ${stagingRoot}:`,
28
+ cleanupError,
29
+ );
30
+ }
31
+ throw preparationError;
32
+ }
@@ -57,7 +57,7 @@ import { createScreencastSourceTime } from "./screencast-source-time";
57
57
  import { createVideoSourceRecovery } from "./video-source-recovery";
58
58
 
59
59
  const execFileAsync = promisify(execFile);
60
- export const AGENT_BROWSER_VERSION = "0.41.0";
60
+ export const AGENT_BROWSER_VERSION = "0.41.2";
61
61
  const PRIVATE_DIRECTORY_MODE = 0o700;
62
62
  const PRIVATE_FILE_MODE = 0o600;
63
63
  const DEFAULT_TIMEOUT_MS = 15_000;
@@ -23,7 +23,7 @@ vi.mock("node:child_process", () => ({
23
23
  callback: (error: null, result: { stdout: string }) => void,
24
24
  ) => {
25
25
  exec.calls.push({ file, args, env: options.env });
26
- callback(null, { stdout: args.includes("--version") ? "0.41.0\n" : "{}\n" });
26
+ callback(null, { stdout: args.includes("--version") ? "0.41.2\n" : "{}\n" });
27
27
  },
28
28
  }));
29
29
 
@@ -15,7 +15,7 @@ vi.mock("node:child_process", async (importOriginal) => ({
15
15
  _args: string[],
16
16
  _options: unknown,
17
17
  callback: (error: null, result: { stdout: string }) => void,
18
- ) => callback(null, { stdout: "0.41.0\n" }),
18
+ ) => callback(null, { stdout: "0.41.2\n" }),
19
19
  }));
20
20
 
21
21
  const temporary: string[] = [];